首页 文章

使用Fluent NHibernate映射许多实体使用的本地化实体集合

提问于
浏览
1

我正在使用遗留数据库,我正在尝试使用Fluent NHibernate映射某种本地化表 .

该表如下所示:

DataName
[PK] TableName
[PK] FieldName
[PK] Id
[PK] LangId
Description

此表通过此实体映射:

public class DataName : EntityBase
{
    public virtual string TableName { get; set; }
    public virtual string FieldName { get; set; }
    public virtual int Id { get; set; }
    public virtual Language Language { get; set; }
    public virtual string Description { get; set; }

    ...
}

public class DataNameMap : ClassMap
{
    public DataNameMap()
    {
        Table("zDataName");

        CompositeId().KeyProperty(x => x.TableName)
            .KeyProperty(x => x.FieldName)
            .KeyProperty(x => x.Id)
            .KeyProperty(x => x.Language, "LangId").CustomType();

        Map(x => x.Description).Column("Descr").Length(2000);
    }
}

为了本地化,我有多个实体映射到“DataName”实体,映射如下所示:

public class Carrier
{

    public virtual int CarrId { get; set; }
    public virtual string CarrCode { get; set; }
    public virtual List DataNameList { get; set; }

    ...
}

public class CarrierMap : ClassMap
{
    public CarrierMap()
    {
        Table("Carriers");

        Id(x => x.CarrId).GeneratedBy.Identity();
        Map(x => x.CarrCode).Not.Nullable().Length(8);

        HasMany(x => x.DataNameList)
            .KeyColumn("Id")
            .Where("TableName = 'carriers' AND FieldName = 'name'")
            .Inverse()
            .Cascade.AllDeleteOrphan();

        ...
    }
}

“DataName”表包含如下所示的记录:

TableName       FieldName      Id        LangId      Description
---------       ---------      --        ------      -----------
Carriers        Name           1000      1           English description
Carriers        Name           1000      2           French description

我可以毫无问题地从数据库中检索此信息,但是当需要将新实体(插入或更新)保存到数据库时,NHibernate不会按正确的顺序执行操作 .

INSERT INTO DataName (TableName, FieldName, LangId, Id, Description) VALUES ('Carriers', 'Name', 1, NULL, 'English description')
INSERT INTO DataName (TableName, FieldName, LangId, Id, Description) VALUES ('Carriers', 'Name', 2, NULL, 'French description')

INSERT INTO Carriers....

For the purpose of this example, let's say the id generated by the database is 2000.

UPDATE DataName SET Id = 2000
WHERE TableName = 'Carriers' AND FieldName = 'Name' AND LangId = 1 AND Id IS NULL
UPDATE DataName SET Id = 2000
WHERE TableName = 'Carriers' AND FieldName = 'Name' AND LangId = 2 AND Id IS NULL

我尝试使用我的HasMany映射的Inverse()选项为DataNameList集合,但它似乎没有对这种特殊情况有任何影响 .

在插入“Carrier”实体后,如何告诉NHibernate持久保存“Carrier”实体然后收集“DataName”而不必发出额外的UPDATE语句?

1 回答

  • 1

    在这种情况下(可能与遗留数据库相关)NHibernate正如预期的那样正常工作 . 我会尝试解释原因 . 我们必须观察FLUSH Ordering和一列的双重用法:

    首先,会话刷新的顺序 .

    NHibernate文档sais:

    9.6 Flush(http://nhibernate.info/doc/nh/en/index.html#manipulatingdata-flushing)... SQL语句按以下顺序发出所有实体插入,按相同的顺序排列相应的对象使用ISession.Save()保存所有实体更新所有集合删除所有集合元素删除,更新和插入所有集合插入所有实体删除,以相同的顺序使用ISession.Delete()删除相应的对象

    其次,双列映射 .

    上面显示的代码段使用一列: Id (在表DataName中)有两个用途: I. 作为主键(这将在INSERT语句中处理)

    CompositeId().KeyProperty(x => x.TableName)
      .KeyProperty(x => x.FieldName)
      .KeyProperty(x => x.Id)
      ...
    

    II. 作为参考两个实体的承运人

    HasMany(x => x.DataNameList)
    .KeyColumn("Id")
    ...
    

    最后:会发生什么

    鉴于此,答案是:NHibernate必须在第一轮中插入所有实体 . 如果有一些多对一的映射,它也可以作为INSERT的一部分完成 . 所以插入 DataNameCarrier .

    但在我们的例子中,我们还需要持久化Collection( DataNameList ) . 所以现在NHibernate更新所有(刚插入的)集合项目,以提供所有者( Carrier

    建议

    所以这就是我将这种观察到的行为标记为"the expected one"的原因 . 为了避免UPDATE,我们可以将列表映射设置为 cascade="none" 并手动管理持久性(例如,调用Session.Save(Carrier),然后将Carrier分配给每个DataName并调用Session.Save(DataName),

    ...

    编辑:扩展建议

    I. settings producing INSERT and UPDATE

    通过这些设置,我能够重复您经历过C#类载波映射到DataName的行为

    private IList<DataName> _dataNames;
    public virtual IList<DataName> DataNames
    {
      get { return _dataNames ?? (_dataNames = new List<DataName>()); }
      set { _dataNames = value; }
    }
    

    XML映射(与您的流利相同,但与 false 相反)

    <bag name="DataNames" inverse="false" cascade="all-delete-orphan"
      where="TableName = 'carriers' AND FieldName = 'name'" >
      <key column="Id" />
      <one-to-many class="DataName" />
    </bag>
    

    导致相同问题的代码,您已在上面描述过:

    // create 2 DataNames
    DataName dn1 = CreateNew(LangId = 1, "English");
    DataName dn2 = CreateNew(LangId = 2, "French");
    
    // insert them into collection
    carrier.DataNames.Add(dn1);
    carrier.DataNames.Add(dn2);
    
    // persist them all
    session.Save(carrier);
    

    这导致INSERTS更新

    II. working settings, just with INSERT

    如果我进行了这些更改:1)将逆转换为 true inverse="true" 2)根据其持有者(运营商)明确设置DataName.Id

    // carrier ID must be get from the server, because of identity
    session.Save(carrier);
    
    // explicity setting of the ID
    dn1.Id = carrier.ID;
    dn2.Id = carrier.ID;
    
    // inverse set to false 
    carrier.DataNames.Add(dn1);
    carrier.DataNames.Add(dn2);
    
    // Update via the carrier entity, will trigger INSERT
    session.Update(carrier);
    

    然后只应用INSERTS语句 . 这有用吗?

    注意:如果不在SQL Server(身份)上生成ID,但由NHibernate(HiLo)协助,则可以减少往返 .

相关问题