2010-10-12 3 views
0

Mon schéma ressemble à ceci:Comment mapper une collection has-many contenant une référence de clé composite dans Fluent NHibernate?

database schema diagram

La colonne Message.Id est une identité (1,1), et la table de MessageHeader utilise une clé composite constitué de l'ID de message associé, et un nom d'en-tête champ qui identifie de manière unique un en-tête dans chaque message.

Les entités sont représentées dans le code comme:

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()); 
    } 
} 

Je veux cartographier ces entités utilisant Fluent NHibernate de sorte que lorsque je lance le code comme:

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 sera:

  1. INSÉRER l'entité Message et récupérer la valeur d'identité.
  2. INSERT l'entité MessageHeader (avec la MessageId appropriée récupérée à l'étape 1)

Je ne peux pas faire ce travail. Il essaye d'insérer le MessageHeader d'abord (qui échoue avec une violation de clé étrangère), ou il essaye de METTRE à JOUR le MessageHeader au lieu de l'INSÉRER, qui ne renvoie aucune ligne et lance une erreur "impossible de synchroniser l'état de la base de données avec la session".

overrides de cartographie Mon FluentNH sont les suivantes:

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(); 
    } 
} 

Je pensais ajouter l'horodatage de version explicite à la table MessageHeader résoudrait, mais il semble pas ... Je suis sûr que quelque chose doit être Inverse ou Cascade ou quelque chose, mais je suis complètement perplexe quant à ce qui doit aller où.

Merci,

Dylan

Répondre

0

La solution se révèle être - via @mikehadlow sur Twitter - que j'ai besoin. Cascade.SaveUpdate() sur la cartographie hasMany:

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

mais il est également avéré qu'un bug non lié NH-ailleurs dans le code empêchait cela de fonctionner correctement. Correction du bug, mappé comme indiqué ci-dessus, et cela a fonctionné. La propriété Version sur MessageHeader est agréable à avoir mais pas nécessaire - si vous l'omettez, NH interrogera la base de données avant une insertion/mise à jour pour savoir lequel est requis.

0

Ceci est un coup de feu dans l'obscurité jusqu'à ce que je peux arriver à une machine où je peux reproduire votre problème ...

Si votre HasMany de ne pas utiliser les deux colonnes pour référencer les en-têtes?

Selon la version Fluent NHibernate, il sera probablement:

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

ou

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