2010-09-05 65 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

相关问题