2010-11-30 5 views
6

Ich habe ein Problem damit, was ein ziemlich einfacher (ich würde denken) NHibernate-Anwendungsfall sein.NHibernate legt den Fremdschlüssel in der sekundären Aktualisierung und nicht in der ursprünglichen Einfügung fest. Nicht-Null-Einschränkung in der Schlüsselspalte

Ich habe einen klassischen Elternteil und ein Kind Entität wie folgt:

public class Parent 
{ 
    public virtual int ParentId { get; set; } 
    public virtual string Name { get; set; } 
    public virtual IList<Child> Children { get; set; } 
} 

public class Child 
{ 
    public virtual int ChildId { get; set; } 
    public virtual Parent Parent { get; set; } 
    public virtual string Name { get; set; } 
} 

und Zuordnungen wie folge:

public class ParentMap : ClassMap<Parent> 
{ 
    public ParentMap() 
    { 
     Id(x => x.ParentId).GeneratedBy.Native(); 
     Map(x => x.Name); 
     HasMany(x => x.Children).KeyColumn("ParentId").Cascade.SaveUpdate(); 
    } 
} 

public class ChildMap : ClassMap<Child> 
{ 
    public ChildMap() 
    { 
     Id(x => x.ChildId).GeneratedBy.Native(); 
     Map(x => x.Name); 
     References(x => x.Parent).Column("ParentId").ReadOnly().Not.Nullable(); 
    } 
} 

Schließlich Ich habe einen einfachen Tests:

[Test] 
    public void Test_save_family() 
    { 
     var parent = new Parent(); 
     var child = new Child {Parent = parent}; 
     parent.Children = new List<Child>{child}; 

     SessionManager.WithSession(
      session => 
       { 
        session.Save(parent); 
        session.Flush(); 
       }); 

    } 

Der Test schlägt mit einer System.Data.SqlClient.SqlException fehl: Der Wert NULL kann nicht in die Spalte 'ParentId' eingefügt werden. Das ist richtig, da die Spalte nicht nullfähig ist, aber warum fügt sie null ein?

Wenn ich die NULL-Constraint entfernen, speichern die funktioniert, weil NHibernate zuerst die Eltern einfügt, dann das Kind einfügt, aktualisiert dann die ParentId Spalte auf dem untergeordneten Datensatz, wie in dieser Ausgabe gezeigt:

NHibernate: INSERT INTO [Parent] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = NULL 
NHibernate: INSERT INTO [Child] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = NULL 
NHibernate: UPDATE [Child] SET ParentId = @p0 WHERE ChildId = @p1;@p0 = 2, @p1 = 1 

Dies scheint bizarr für mich, da in fast allen Fällen Fremdschlüsselspalten dieser Art als nicht nullbar deklariert werden und daher der Fremdschlüssel beim Einfügen bereitgestellt werden muss. Warum setzt NHibernate den Fremdschlüssel nicht beim ersten Einfügen der untergeordneten Zeile und wie behebe ich das?

Antwort

7

Einige Probleme bei der Zuordnung ... Sie haben eine bidirektionale Beziehung und NHibernate muss wissen, auf welche Weise es aktualisiert werden kann. In der OO-Welt gehen Verweise nur in eine Richtung, und es gibt keine Möglichkeit für NHibernate zu wissen, dass Parent-> Children die gleiche FK wie Child-> Parent ist. Im Moment haben Sie Child-> Parent auf ReadOnly() gesetzt. Dies weist NHibernate an, diese Eigenschaft nicht zu aktualisieren. Es versucht also, das Kind (mit einem Null-Elternteil) einzufügen und dann den FK von der Elternseite zu aktualisieren. Dies funktioniert nicht, wenn Sie eine Nicht-Null-Einschränkung für Ihren FK haben. Der übliche Weg, dies zu kartieren, besteht darin, auf der Elternseite Inverse = true zu verwenden und das Kind sich Gedanken über die Persistenz machen zu lassen. (Es ist Ihre Aufgabe in dem OO-Modell, um sicherzustellen, dass die Mutter-> Kinder Sammlung den gleichen Satz von Referenzen als die Menge der Kind- enthält> Eltern Beziehungen.)

public class ParentMap : ClassMap<Parent> 
{ 
    public ParentMap() 
    { 
     Id(x => x.ParentId).GeneratedBy.Native(); 
     Map(x => x.Name); 
     HasMany(x => x.Children).KeyColumn("ParentId").Inverse().Cascade.SaveUpdate(); 
    } 
} 

public class ChildMap : ClassMap<Child> 
{ 
    public ChildMap() 
    { 
     Id(x => x.ChildId).GeneratedBy.Native(); 
     Map(x => x.Name); 
     References(x => x.Parent).Column("ParentId").Not.Nullable(); // Removed ReadOnly() 
    } 
} 

Die SQL-Anweisungen an die Datenbank geschickt beim Speichern sind jetzt:

INSERT INTO [Parent] 
      (Name) 
VALUES  ('P1' /* @p0 */) 
select SCOPE_IDENTITY() 

INSERT INTO [Child] 
      (Name, 
      ParentId) 
VALUES  ('C1' /* @p0 */, 
      1 /* @p1 */) 
select SCOPE_IDENTITY() 
+0

James, okay Entfernen der ReadOnly() Qualifier funktionierte, um den Test zu erhalten. Jetzt bin ich jedoch verwirrt über die Bedeutung von ReadOnly(). Mein Verständnis war, dass ReadOnly() bedeutet, dass NHibernate die Spalte nach der ursprünglichen Einfügung nie aktualisieren wird, nicht dass es niemals einen Wert bei der Einfügung zuweisen wird. –

+0

ReadOnly() bedeutet, dass der Wert niemals beim Einfügen oder Aktualisieren geschrieben wird. Es wird hauptsächlich für Nachschlagetabellen und berechnete Spalten verwendet. –

+0

Hmm, das wusste ich jetzt, wo du es erwähnst. Ich glaube, ich habe ReadOnly() als Eigenschaft Attribut mit ReadOnly als Entity-Attribut verwechselt. Muss alt werden. :) Danke für die Hilfe! –