NHibernate - Как обработать исключение NonUniqueObjectException? - PullRequest
2 голосов
/ 05 сентября 2010

Я получаю эту ошибку:

другой объект с таким же значением идентификатора уже был связан с сеансом: 63 объекта: 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();
    }
}

Как мне исправить эту ошибку и в чем здесь проблема?

Ответы [ 2 ]

10 голосов
/ 05 сентября 2010

Скорее всего, вы добавляете в сеанс двух сотрудников с одинаковым идентификатором.

Найдите причину, по которой два сотрудника с одинаковым идентификатором.

Используете ли вы отсоединенный (например, сериализованный) лица?Скорее всего, у вас есть объект, загруженный из базы данных (например, при загрузке команды), и другой объект, который отсоединяется и добавляется.Лучшее, что вы можете сделать, это вообще не использовать отдельный экземпляр.Используйте его идентификатор только для загрузки реального экземпляра из базы данных:

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);

Объяснение:

В памяти может быть только один экземпляр одной и той же записи базы данных.NH гарантирует, что запрос возвращает тот же экземпляр, что и в кеше сеанса.Если это не так, в одной и той же базе данных будет несколько значений в памяти.Это было бы несовместимо.

Объекты идентифицируются по их первичному ключу.Таким образом, для каждого значения первичного ключа должен быть только один экземпляр.Экземпляры помещаются в сеанс с помощью запросов, Save, Update, SaveOrUpdate или Lock.

Вы получаете проблему, когда экземпляр уже находится в сеансе (например, по запросу), и вы пытаетесь поместить другой экземпляр (например, отсоединенный / сериализованный) с тем же идентификатором в сеансе (например, с использованием обновления).

Решение 1 помещает экземпляр в сеанс до любого другого запроса.NH вернет именно этот экземпляр во всех последующих запросах!Это действительно хорошо, но вам нужно вызвать Update перед любым запросом, чтобы заставить его работать должным образом.

Решение второе использует Merge.Merge выполняет следующие действия:

  • , если экземпляр уже находится в кеше, он записывает все свойства из аргумента в тот, что находится в кеше, и возвращает одно в кеше.
  • экземпляр не находится в кэше, он извлекает его из базы данных (для чего требуется запрос).Затем он добавляет аргумент в кеш.Он также возвращает экземпляр в кэше, который совпадает с аргументом.В отличие от простого обновления, оно не обновляет значения в базе данных вслепую, а сначала извлекает его.Это позволяет NH выполнять вставку или обновление, вообще не указывать обновление (поскольку оно не изменилось) и некоторые другие особые случаи.

В конце концов, слияние никогда не вызывает проблем с уже существующимиэкземпляров.Не имеет значения, находится ли экземпляр в базе данных или нет.Вам просто нужно принять во внимание, что возвращаемое значение является экземпляром в сеансе, а не аргументом.


Редактировать: второй экземпляр 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 голосов
/ 05 сентября 2010

Я предполагаю, что у вас есть Team.Employee, который загружен не так, как employee в вашем коде. Попробуйте установить team.Employee здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...