代码之家  ›  专栏  ›  技术社区  ›  Dylan Beattie

如何在Fluent NHibernate中映射包含复合键引用的多个集合?

  •  0
  • Dylan Beattie  · 技术社区  · 14 年前

    我的架构如下:

    database schema diagram

    Id列是一个标识(1,1),而Message header表使用一个组合键,该组合键由关联的消息Id和一个header name字段组成,该字段唯一标识每个消息中的一个header。

    实体在代码中表示为:

    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. 插入消息实体并检索标识值。
    2. 插入MessageHeader实体(在步骤1中检索到适当的MessageId)

    我做不到。它要么尝试先插入MessageHeader(由于外键冲突而失败),要么尝试更新MessageHeader,而不是插入它,后者不返回任何行并抛出“无法将数据库状态与会话同步”错误。

    我的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表中可以解决这个问题,但它看起来不会。。。我肯定有些东西需要逆的或级联的或其他的,但是我完全不知道该去哪里。

    迪伦

    2 回复  |  直到 14 年前
        1
  •  0
  •   James Gregory    14 年前

    你应该 HasMany

    根据你流利的NHibernate版本,可能是:

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

    map.HasMany(x=> x.Headers)
      .Key(ke =>
      {
         ke.Columns.Add("MessageId", "HeaderName");
      });
    
        2
  •  0
  •   Dylan Beattie    14 年前

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

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

    但也发现代码中其他地方的一个与NH无关的bug阻止了它正常工作。修正了这个错误,如上图所示映射了它,并且成功了。MessageHeader上的Version属性很好,但不是必需的——如果省略它,NH将在插入/更新之前查询数据库,以确定需要哪个数据库。