Альтернативный идентификатор nhibernate с использованием сгенерированных свойств

** Этот вопрос был отредактирован, чтобы сделать его более простым и целенаправленным **

У сотрудника есть свойство EmployeeNumberValue, которое я хотел бы автоматически увеличивать с помощью db. Для бизнес-домена это уникальный идентификатор, назначаемый сотрудникам и используемый для их идентификации на карточках сотрудников и т. Д. Однако для базы данных это альтернативный идентификатор, а не первичный ключ.

NHib имеет задокументированную возможность под названием сгенерированные свойства. Согласно документам, «сгенерированные свойства - это свойства, значения которых генерируются базой данных. Как правило, приложениям NHibernate необходимо обновить объекты, которые содержат любые свойства, для которых база данных генерировала значения. Пометка свойств как сгенерированные, однако, позволяет приложению делегировать это ответственность перед NHibernate. По сути, всякий раз, когда NHibernate выдает SQL INSERT или UPDATE для объекта, который определил сгенерированные свойства, он сразу же после этого выдает select для получения сгенерированных значений ».

Проблема, с которой я столкнулся, заключается в том, что пока NHib выполняет дополнительный SELECT для обновления EmployeeNumberValue, он не присваивает полученное значение свойству.

Кто-нибудь может понять, почему это происходит, и что это за исправление?

Ура,
Беррил

НЕИСПРАВНОСТЬ ТЕСТА И ВЫВОДА (проверено с SQLite в памяти db):

    [Test]
    public void Employee_OnInsert_EmployeeNumberValueIsIncremented() {

        var emp1 = new Employee
        {
            FullName = _fullName,
            Department = _department,
        };
        var emp2 = new Employee
        {
            FullName = _fullName,
            Department = _department,
        };

        var session = _SessionFactory.GetCurrentSession(); 

        using (var tx = session.BeginTransaction())
        {
            session.Save(_department);
            session.Save(emp1);
            session.Save(emp2);
            tx.Commit();
        }
        Assert.That(emp1.EmployeeNumberValue, Is.EqualTo(1));
        Assert.That(emp2.EmployeeNumberValue, Is.EqualTo(2));
    }

NHibernate: INSERT INTO Employees (FirstName, LastName, DepartmentId, EmployeeId) 
        VALUES (@p0, @p1, @p2, @p3);@p0 = 'Berryl' [Type: String (0)], @p1 = 'Hesh' [Type: String (0)], @p2 = 32768 [Type: Int32 (0)], @p3 = 65536 [Type: Int32 (0)]
NHibernate: SELECT employee_.EmployeeNumberValue as Employee2_1_ FROM Employees employee_ WHERE employee_.EmployeeId=@p0;@p0 = 65536 [Type: Int32 (0)]
NHibernate: INSERT INTO Employees (FirstName, LastName, DepartmentId, EmployeeId) 
        VALUES (@p0, @p1, @p2, @p3);@p0 = 'Berryl' [Type: String (0)], @p1 = 'Hesh' [Type: String (0)], @p2 = 32768 [Type: Int32 (0)], @p3 = 65537 [Type: Int32 (0)]
NHibernate: SELECT employee_.EmployeeNumberValue as Employee2_1_ FROM Employees employee_ WHERE employee_.EmployeeId=@p0;@p0 = 65537 [Type: Int32 (0)]
Test failed: 
   Expected: 1
   But was:  0

ОБЪЕКТНАЯ МОДЕЛЬ

public class Employee : Entity, IResource
{
    public virtual long EmployeeNumberValue { get; set; }

    ...
}

КАРТИРОВАНИЕ:

  <class name="Employee" table="Employees">

<id name="Id" unsaved-value="0">
  <column name="EmployeeId" />
  <generator class="hilo" />
</id>

<property name="EmployeeNumberValue" generated="insert" insert="false" update="false" >
  <column name="EmployeeNumberValue" sql-type="int IDENTITY(1,1)" index="IDX_EmployeeNumber"  />      
</property>

...

create table Employees (
    EmployeeId INTEGER not null,
   EmployeeNumberValue int IDENTITY(1,1),
   FirstName TEXT not null,
   LastName TEXT not null,
   DepartmentId INTEGER,
   primary key (EmployeeId)
)

I suspect the way I am marking the column as IDENTITY is also suspect. I tried using database-object as below, but got a usage error in doing so

  <database-object>
    <create>
      ALTER TABLE Employee DROP COLUMN EmployeeNumberValue
      ALTER TABLE Employee ADD EmployeeNumberValue INT IDENTITY
    </create>
    <drop>
      ALTER TABLE Employee DROP COLUMN EmployeeNumberValue
    </drop>
  </database-object>

SQLiteException : SQLite error  "DROP": syntax error

person Berryl    schedule 31.05.2011    source источник
comment
Ваше свойство действительно называется Value, а не EmployeeNumber? Это должно работать: <property name="Value" column="EmployeeNumber" generated="insert"/>. Вы сделали эту колонку identity?   -  person Diego Mijelshon    schedule 01.06.2011
comment
@Diego, последний бит был проблемой, поэтому я изменил сопоставление, чтобы сделать идентификатор столбца таким, как показано в измененном сопоставлении. Я все еще могу запутаться в свойствах, поскольку значение не присваивается. У сотрудника есть свойство под названием EmployeeNumber, которое само по себе является объектом (компонентом) значения, имеющим свойство под названием Value.   -  person Berryl    schedule 01.06.2011
comment
@Diego - создание идентификатора столбца также немного сложно. у вас есть другой способ использования?   -  person Berryl    schedule 01.06.2011
comment
@Berryl: Почему компонент и наследование ValueObject, и все такое прочее? Просто используйте простой public virtual long EmployeeNumber { get; set; } в Employee с отображением, которое я вам дал, и он будет работать.   -  person Diego Mijelshon    schedule 02.06.2011
comment
@Diego: использование его в качестве объекта упрощает форматирование отображаемого значения. У меня более сложная, но похожая ситуация с ProjectNumbers, которые должны автоматически увеличиваться с помощью префикса, причем я хотел бы иметь возможность разрешить ситуацию с помощью компонентов. Вы можете придумать что-нибудь еще, что я могу попробовать?   -  person Berryl    schedule 02.06.2011
comment
@Berryl: форматирование отображаемого значения не входит в обязанности домена. И вы всегда можете использовать несопоставленное свойство для форматирования в любом случае.   -  person Diego Mijelshon    schedule 02.06.2011
comment
@Diego. С уважением, вы действительно не думаете, что знаете чью-то область, просто прочитав вопрос о том, как что-то сделать, используя различные техники NHib, не так ли?   -  person Berryl    schedule 02.06.2011
comment
@Diego. Ответ на это сообщение ближе к тому, что я ищу - сделайте поколение бизнес-последовательностей - это функция приложения, обрабатываемая доменом, а не уровнем доступа к данным. Как вы думаете, вы могли бы поцарапать, как слушатель может выполнять эту работу?   -  person Berryl    schedule 02.06.2011
comment
@Berryl: реализуйте и зарегистрируйте IPreInsertEventListener, используйте запрос для получения максимального значения, обычный прием, наблюдаемый в слушателях аудита, для изменения значения свойства ... и наблюдайте, как умирает масштабируемость вашего приложения.   -  person Diego Mijelshon    schedule 02.06.2011
comment
@Diego: честно и спасибо за вашу помощь. Я отредактировал этот вопрос, чтобы сосредоточиться на методе сгенерированных свойств и выяснить, почему он не работает для меня.   -  person Berryl    schedule 02.06.2011


Ответы (3)


С точки зрения дизайна, в этом случае я бы не стал полагаться на NHibernate. Я имею в виду, что в вашей модели предметной области вы хотите, чтобы сотрудник получил новый номер карты сотрудника.

В этом случае я бы разрешил создание экземпляра сотрудника только при наличии номера карты.

public class EmployeeCardNumber
{
    private string id = String.Empty;
    internal EmployeeCardNumber(string id)
    {
        this.id = id;
    }
}


public class Employee
{
    private EmployeeCardNumber employeeCardNumber;

    public EmployeeCardNumber CardNumber { ... }

    public Employee(EmployeeCardNumber employeeCardNumber)
    {
        this.employeeCardNumber = employeeCardNumber;
    }
}

Итак, теперь вам нужно подумать о том, как создать уникальный EmployeeCardNumber.

public class EmployeeCardNumberFactory
{
    public EmployeeCardNumber CreateNew()
    {
        // in this example the card number will be a guid.
        // but you could also implement a "EmployeeCardNumberGenerator" class which will do crazy database stuff
        return new EmployeeCardNumber(Guid.NewGuid().ToString());
    }
}

Потом вы сделаете:

EmployeeCardNumber cardNumber = employeeCardNumberFactory.CreateNew();
Employee employee = new Employee(cardNumber, name, etc...);

Дополнение. Чтобы сгенерировать EmployeeCardNumber через базу данных, вы можете просто сопоставить EmployeeCardNumber с дополнительной таблицей EmployeeCardNumber, которая будет служить генератором вашей идентичности, например:

<class name="EmployeeCardNumber" table="EmployeeCardNumber">
    <id name="id" access="field" unsaved-value="0">
      <column name="EmployeeCardNumberId" />
      <generator class="identity" />
    </id>
</class>

Тогда на заводе вы могли:

public class EmployeeCardNumberFactory
{
    private IEmployeeCardNumberRepository repository = new EmployeeCardNumberRepository(); // inject...
    public EmployeeCardNumber CreateNew()
    {     
        EmployeeCardNumber cardNumber = new EmployeeCardNumber();
        repository.Save(cardNumber); // gets you a fresh id
        return cardNumber;
    }
}
person Chris    schedule 10.06.2011
comment
Это ближе к моим предпочтениям, как вы можете видеть из этого связанного вопроса SO, который, казалось, точно никого не интересуют. Мне также нравится идея отдельной таблицы для генерации последовательных идентификаторов, хотя мне все еще интересно, почему задокументированное сгенерированное свойство здесь не работает. Вы действительно использовали такой шаблон в проекте? - person Berryl; 10.06.2011
comment
Да, конечно, это более или менее предметный дизайн. Хотя искусственный идентификатор, такой как автоматически сгенерированные идентификаторы базы данных, не имеет смысла и предназначен только для технических целей (объединение, ссылочная целостность, единственный способ создать уникальную идентичность объекта), номер вашей карты служит определенной бизнес-цели в вашем бизнес-домене. Вот почему это должно быть явно смоделировано в вашей модели предметной области. Единственный недостаток, который я вижу в приведенном выше шаблоне, заключается в том, что теперь может быть 2 сотрудника с одинаковым номером карты. Чтобы позаботиться об этом, EmployeeCardNumber следует создавать только с сотрудником. - person Chris; 11.06.2011
comment
Не уверен в этом - добавление, конечно, можно отменить. Похоже, фокус здесь скорее в сохранении, возможно, в присвоении номера как части этой транзакции. - person Berryl; 11.06.2011

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

Проверьте 5.5. Созданные свойства

person Diego Mijelshon    schedule 31.05.2011
comment
Спасибо за ответ - мой вопрос был двояким. Во-первых, я думал, что подход слушателя / перехватчика позволит избежать лишнего SELECT. Во-вторых, мои попытки использовать сгенерированные не работают. Пожалуйста, посмотрите мои ОБНОВЛЕННЫЕ комментарии / сопоставление постов - person Berryl; 01.06.2011
comment
@Berryl: как бы вы это сделали без выбора? Даже с необработанным ADO.NET это невозможно, если вы не предполагаете много (и не используете некоторые неприятные блокировки) или не используете запрос внутри оператора select. - person Diego Mijelshon; 01.06.2011
comment
честно говоря, я не уверен; в другом сообщении SO подразумевалось, что это можно сделать с помощью перехватчика, но без объяснения того, как это сделать. - person Berryl; 01.06.2011

У меня был такой же сценарий, и он очень хорошо работает в продакшене.

Вот отображение (созданное Fluent NHibernate):

<property generated="insert" name="Number" update="false" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  <column name="Number" not-null="true" />
</property>

В базе данных этот столбец выглядит так:

ALTER TABLE [DeviceLink] ADD [Number] INT not null IDENTITY (1, 1)
person xelibrion    schedule 09.06.2011