如何在Fluent NHibernate中映射包含复合键引用的has-many集合?

时间:2010-10-12 13:18:16

标签: nhibernate fluent-nhibernate one-to-many

我的架构如下所示:

database schema diagram

Message.Id列是标识(1,1),MessageHeader表使用由关联的消息ID组成的组合密钥,以及唯一标识每条消息中标头的标题名称字段。

实体在代码中表示为:

public class Message {
    public virtual int Id { get; set; }
    public virtual string Sender { get; set; }
    public virtual string Recipient { get; set; }
    private IList<MessageHeader> headers = new List<MessageHeader>();

    public virtual IList<MessageHeader> Headers { 
        get { return headers; } 
        set { headers = value; }
    }

    public virtual void SetHeader(string headerName, string headerText) {
        MessageHeader header = this.Headers.FirstOrDefault(h => h.HeaderName == headerName);
        if (header == default(MessageHeader)) {
            header = new MessageHeader() {
                HeaderName = headerName,
                Message = this
            };
            this.Headers.Add(header);
            header.HeaderText = headerText;
        }
    }
}

public class MessageHeader {
    public virtual Message Message { get; set; }
    public virtual string HeaderName { get; set; }
    public virtual string HeaderText { get; set; }
    public virtual byte[] Version { get; set; }

    public override bool Equals(object obj) {
        if (Object.Equals(this, obj)) return (true);
        var that = obj as MessageHeader;
        if (that == null) return (false);
        return (this.HeaderName == that.HeaderName && this.Message.Equals(that.Message));
    }

    public override int GetHashCode() {
        return (this.HeaderName.GetHashCode() ^ this.Message.GetHashCode());
    }
}

我想使用Fluent NHibernate映射这些实体,以便在运行代码时使用:

var message = new Message() { Sender = "alf", Recipient = "bob" };
message.SetHeader("DateSent", DateTime.Now.ToString());

using (var session = NHibernateHelper.GetCurrentSession()) {
   using(var tx = session.BeginTransaction()) {
     session.Save(message);
     tx.Commit();
   }
}

NHibernate会:

  1. 插入Message实体并检索标识值。
  2. INSERT MessageHeader实体(使用步骤1中检索到的相应MessageId)
  3. 我无法完成这项工作。它要么首先尝试插入MessageHeader(因外键违反而失败),要么尝试UPDATE MessageHeader而不是INSERTing它,它不返回任何行并抛出“无法使数据库状态与会话同步”错误。

    我的FluentNH映射覆盖如下:

    public class MessageOverrides : IAutoMappingOverride<Message> {
    
        public void Override(AutoMapping<Message> map) {
            map.HasMany(message => message.Headers).KeyColumn("MessageId");
        }
    }
    
    public class MessageHeaderOverrides : IAutoMappingOverride<MessageHeader> {
        public void Override(AutoMapping<MessageHeader> map) {
            map.IgnoreProperty(header => header.Message);
            map.CompositeId()
                .KeyReference(header => header.Message, "MessageId")
                .KeyProperty(header => header.HeaderName);
            map.Version(header => header.Version)
                .CustomSqlType("timestamp").Nullable()
                .Generated.Always();
        }
    }
    

    我认为将明确的版本时间戳添加到MessageHeader表会解决这个问题,但它看起来不会......我确定某些东西需要Inverse或Cascade或其他东西,但我完全难以理解需要什么去哪里。

    谢谢,

    迪伦

2 个答案:

答案 0 :(得分:0)

这是在黑暗中拍摄,直到我可以到达我可以重现你的问题的机器......

您的HasMany是否应该同时使用两列来引用标题?

根据您的Fluent NHibernate版本,它可能是:

map.HasMany(x=> x.Headers)
  .KeyColumns.Add("MessageId", "HeaderName");

map.HasMany(x=> x.Headers)
  .Key(ke =>
  {
     ke.Columns.Add("MessageId", "HeaderName");
  });

答案 1 :(得分:0)

解决方案原来是 - 通过Twitter上的@mikehadlow - 我需要HasMany映射上的.Cascade.SaveUpdate():

public void Override(AutoMapping<Message> map) {
    map.HasMany(message => message.Headers).KeyColumn("MessageId").Cascade.SaveUpdate()
}

但事实证明,代码中其他地方的非NH相关错误阻止了它正常工作。修复了错误,如上图所示映射了它,并且它有效。 MessageHeader上的Version属性很好用但不是必需的 - 如果省略它,NH将在插入/更新之前查询数据库以找出哪一个是必需的。