2010-09-05 42 views
2

我得到這個錯誤:NHibernate - 如何處理NonUniqueObjectException?

a different object with the same identifier value was already associated with the session: 63, of entity: Core.Domain.Model.Employee

在我的ASP.NET MVC控制器動作我這樣做:

public ActionResult Index(int? page) 
{ 
    int totalCount; 
    Employee[] empData = _employeeRepository.GetPagedEmployees(page ?? 1, 5, out totalCount); 

    EmployeeForm[] data = (EmployeeForm[]) Mapper<Employee[], EmployeeForm[]>(empData); 

    PagedList<EmployeeForm> list = new PagedList<EmployeeForm>(data, page ?? 1, 5, totalCount); 


    if (Request.IsAjaxRequest()) 
     return View("Grid", list); 

    return View(list); 
} 

public ActionResult Edit(int id) 
{ 
    ViewData[Keys.Teams] = MvcExtensions.CreateSelectList(
     _teamRepository.GetAll().ToList(), 
     teamVal => teamVal.Id, 
     teamText => teamText.Name); 
    return View(_employeeRepository.GetById(id) ?? new Employee()); 
} 

[HttpPost] 
public ActionResult Edit(
    Employee employee, 
    [Optional, DefaultParameterValue(0)] int teamId) 
{ 
    try 
    { 
     var team = _teamRepository.GetById(teamId); 
     if (team != null) 
     { 
      employee.AddTeam(team); 
     } 

     _employeeRepository.SaveOrUpdate(employee); 

     return Request.IsAjaxRequest() ? Index(1) : RedirectToAction("Index"); 
    } 
    catch 
    { 
     return View(); 
    } 
} 

映射文件:

員工

public sealed class EmployeeMap : ClassMap<Employee> 
{ 
    public EmployeeMap() 
    { 
     Id(p => p.Id) 
      .Column("EmployeeId") 
      .GeneratedBy.Identity(); 

     Map(p => p.EMail); 
     Map(p => p.LastName); 
     Map(p => p.FirstName); 

     HasManyToMany(p => p.GetTeams()) 
      .Access.CamelCaseField(Prefix.Underscore) 
      .Table("TeamEmployee") 
      .ParentKeyColumn("EmployeeId") 
      .ChildKeyColumn("TeamId") 
      .LazyLoad() 
      .AsSet() 
      .Cascade.SaveUpdate(); 

     HasMany(p => p.GetLoanedItems()) 
      .Access.CamelCaseField(Prefix.Underscore) 
      .Cascade.SaveUpdate() 
      .KeyColumn("EmployeeId"); 
    } 
} 

團隊:

public sealed class TeamMap : ClassMap<Team> 
{ 
    public TeamMap() 
    { 
     Id(p => p.Id) 
      .Column("TeamId") 
      .GeneratedBy.Identity(); 

     Map(p => p.Name); 

     HasManyToMany(p => p.GetEmployees()) 
      .Access.CamelCaseField(Prefix.Underscore) 
      .Table("TeamEmployee") 
      .ParentKeyColumn("TeamId") 
      .ChildKeyColumn("EmployeeId") 
      .LazyLoad() 
      .AsSet() 
      .Inverse() 
      .Cascade.SaveUpdate(); 
    } 
} 

我如何修復這個bug,什麼問題在這裏?

+0

我的回答再次更新,並增加了解釋段落。希望能幫助到你。 – 2010-09-07 19:34:11

回答

10

您很可能會在會話中添加兩名具有相同ID的員工。

找到爲什麼有兩名僱員具有相同的ID的原因。

您是否使用分離(例如序列化)實體?你最有可能從數據庫加載實體(例如,加載團隊時)以及另一個被分離並被添加的實體。你所能做的最好的是根本不使用分離的實例。僅使用其ID從數據庫中裝載真正的實例:

var employee = employeeRepository.GetById(detachedEmployee.id); 
var team = _teamRepository.GetById(teamId); 
if (team != null) 
{ 
    employee.AddTeam(team); 
} 
_employeeRepository.SaveOrUpdate(employee); 

當你想要寫在detachedEmployee於會話員工的價值觀,用session.Merge代替session.Update(或session.SaveOrUpdate):

var employee = employeeRepository.Merge(detachedEmployee); 
// ... 

這個錯誤還有其他的原因,但是我不知道你在做什麼以及你的映射文件是怎麼樣的。


編輯

這應該工作:

// put the employee into the session before any query. 
_employeeRepository.SaveOrUpdate(employee); 

var team = _teamRepository.GetById(teamId); 
if (team != null) 
{ 
    employee.AddTeam(team); 
} 

或者使用合併:

var team = _teamRepository.GetById(teamId); 
if (team != null) 
{ 
    employee.AddTeam(team); 
} 
// use merge, because there is already an instance of the 
// employee loaded in the session. 
// note the return value of merge: it returns the instance in the 
// session (but take the value from the given instance) 
employee = _employeeRepository.Merge(employee); 

Explanatio n:

存儲器中只能有一個相同數據庫「記錄」的實例。 NH確保查詢返回的實例與會話緩存中已存在的實例相同。如果不是這樣的話,那麼對於相同的數據庫字段,內存中會有多個值。這將是不一致的。

實體由其主鍵標識。所以每個主鍵值只能有一個實例。實例通過查詢,保存,更新,SaveOrUpdate或Lock進入會話。

當一個實例已經在會話中(例如,通過查詢)並且您嘗試將另一個具有相同ID的實例(例如,分離/序列化的實例)放入會話中時(例如,使用更新)。

解決方案一將實例放入會話之前的任何其他查詢。 NH將在所有後續查詢中返回此實例!這非常好,但您需要在任何查詢之前調用Update以使其正常工作。

解決方案二使用合併。合併執行以下操作:

  • 如果實例已在高速緩存中,它會將所有屬性從參數寫入高速緩存中,並返回高速緩存中的一個。
  • 是不在緩存中的實例,它從數據庫中檢索它(這需要查詢)。然後它將參數添加到緩存中。它也返回緩存中與參數相同的實例。與簡單的更新相比,它不會盲目地更新數據庫中的值,它會首先檢索它。這允許NH執行插入或更新,完全忽略更新(因爲它沒有改變)以及一些更特殊的情況。

最後,Merge對現有的實例永遠不會有問題。即使實例已經存在於數據庫中也沒有關係。你只需要考慮到返回值是會話中的實例,而不是參數。


編輯:第二Employee實例

[HttpPost] 
public ActionResult Edit(
    Employee employee, // <<== serialized (detached) instance 
    [Optional, DefaultParameterValue(0)] int teamId) 
{ 
    // ... 

    // load team and containing Employees into the session 
    var team = _teamRepository.GetById(teamId); 


    // ... 

    // store the detached employee. The employee may already be in the session, 
    // loaded by the team query above. The detached employee is another instance 
    // of an already loaded one. This is not allowed. 
    _employeeRepository.SaveOrUpdate(employee); 

    // ... 
} 
+0

有沒有在會話中找到這個方法或我如何找到它? – Rookian 2010-09-05 19:35:25

+0

找到什麼?該員工已在會議中?它很可能由團隊加載。 *你是否在序列化實體? – 2010-09-06 07:49:41

+0

我編輯了我的問題,請看看。 – Rookian 2010-09-06 17:07:38

0

我的猜測是你有一個Team.Employee,它的加載方式與你代碼中的employee不同。嘗試在這裏設置team.Employee

相關問題