使用生成的属性的nhibernate备用id
**对该问题进行了编辑,使其更简单、更集中** Employee有一个EmployeeNumberValue属性,我希望该属性由db自动递增。对于业务域而言,这是分配给员工的唯一id,用于在员工卡上识别员工。但是,对于数据库而言,这是一个备用id,而不是主键 NHib有一个记录在案的能力叫做。 根据文档,生成的属性是由数据库生成其值的属性。通常,NHibernate应用程序需要刷新包含数据库正在为其生成值的任何属性的对象。但是,将属性标记为已生成可以让应用程序将此职责委托给NHibernate。本质上,每当NHibernate为已定义生成属性的实体发出SQL INSERT或UPDATE时,它会立即在之后发出select以检索生成的值 我遇到的问题是,当NHib进行额外选择以更新EmployeeNumber值时,它没有将检索到的值分配给属性 有人知道为什么会发生这种情况吗?解决办法是什么 干杯, 贝里尔 在内存数据库中使用SQLite进行测试和输出测试失败:使用生成的属性的nhibernate备用id,nhibernate,identity,sequences,Nhibernate,Identity,Sequences,**对该问题进行了编辑,使其更简单、更集中** Employee有一个EmployeeNumberValue属性,我希望该属性由db自动递增。对于业务域而言,这是分配给员工的唯一id,用于在员工卡上识别员工。但是,对于数据库而言,这是一个备用id,而不是主键 NHib有一个记录在案的能力叫做。 根据文档,生成的属性是由数据库生成其值的属性。通常,NHibernate应用程序需要刷新包含数据库正在为其生成值的任何属性的对象。但是,将属性标记为已生成可以让应用程序将此职责委托给NHibernate。
[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>
...
我怀疑我将专栏标记为身份的方式也值得怀疑。我尝试使用数据库对象,如下所示,但在使用过程中出现了使用错误
<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
虽然这是可行的,但最好在数据库中使用identity或触发器并映射insert时生成的属性
检查我有相同的场景,它在生产中运行得非常好 以下是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>
从设计角度来看,在这种情况下,我不会依赖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;
}
}
在这种情况下,我只允许在有卡号的情况下实例化员工
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>
谢谢你的回复——我问这个问题的动机是双重的。首先,我认为侦听器/拦截器方法可以避免额外的选择。其次,我尝试使用生成的代码似乎不起作用。请参阅我的最新帖子评论/mapping@Berryl:如果没有选择,您将如何操作?即使使用原始ADO.NET,这也是不可能的,除非您假设很多,并使用一些讨厌的锁,或者在select语句中使用查询;另一篇SO帖子暗示它可以通过拦截器完成,但没有说明如何完成。您的属性真的命名为Value而不是EmployeeNumber吗?这应该有效:。你做了列标识吗?@Diego最后一点是个问题,所以我改变了映射,使之成为列标识,如修改后的映射所示。我可能仍然对属性感到困惑,因为没有赋值。Employee有一个名为EmployeeNumber的属性,该属性本身是一个值对象组件,它有一个名为value的属性。@Diego-创建列标识也有点棘手。你有不同的使用方法吗?@Berryl:为什么要使用组件和ValueObject继承,以及所有这些东西?只要在Employee中使用一个简单的公共虚拟long EmployeeNumber{get;set;}和我给您的映射,它就会工作。@Diego:将它作为一个对象,可以很容易地格式化显示值。对于ProjectNumber,我有一个更复杂但类似的情况,它应该通过前缀自动递增,重点是我希望能够解决组件的情况。你能想到我还能尝试什么吗?这更接近我的偏好,正如你从中看到的,似乎没有人感兴趣。我也喜欢用一个单独的表来生成顺序ID的想法,尽管我仍然想知道为什么文档化生成的属性在这里不起作用。你在项目中实际使用过这样的模式吗?是的,当然,这或多或少是领域驱动的设计。虽然像自动生成的数据库id这样的人工id毫无意义,并且仅用于技术目的、连接、引用完整性,这是创建实体唯一标识的唯一方法,但您的卡号在您的业务域中用于特定的业务目的。这就是为什么应该像在域模型中那样对其进行明确建模。我在上面的模式中看到的唯一缺点是,现在可能有两名员工拥有相同的卡号。为了解决这个问题,EmployeeCardNumber应该只与员工一起实例化。对此不太确定-添加当然可以取消。看起来这里的诀窍更多的是保存,也许将数字指定为p 交易的艺术。
<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;
}
}