2013-12-10 55 views
1

在我的對象圖中,PersonAddress有多對多的關係,並且連接表有附加列。使用Cascade.All時出現的TransientObjectException

階層結構

class Person 
{ 
    private IList<PersonAddress> _personAddresses = new List<PersonAddress>(); 

    public virtual int Id { get; set; } 
    public virtual IList<PersonAddress> PersonAddresses 
    { 
     get { return _personAddresses; } 
     set { _personAddresses = value; } 
    } 
} 

class PersonAddress 
{ 
    public virtual Person Person { get; set; } 
    public virtual Address Address { get; set; } 
    public virtual string Description { get; set; } 

    public override bool Equals(...) {...} 
    public override int GetHashCode(...) {...} 
} 

class Address 
{ 
    public virtual int Id { get; set; } 
} 

映射

class PersonMapping : ClassMapping<Person> 
{ 
    public PersonMapping() 
    { 
     Id(x => x.ID, m => m.Generator(Generators.Identity)); 

     Bag(
      x => x.PersonAddresses, 
      m => { 
       m.Cascade(Cascade.All); 
       m.Access(Accessor.Field); 
      }, 
      r => r.OneToMany() 
     ); 
    } 
} 

public class PersonAddressMapping : ClassMapping<PersonAddress> 
{ 
    public PersonAddressMapping() 
    { 
     ComposedId(map => 
     { 
      map.ManyToOne(
       x => x.Person, 
       m => { 
        m.Cascade(Cascade.All); 
       } 
      ); 

      map.ManyToOne(
       x => x.Address, 
       m => { 
        m.Cascade(Cascade.All); 
       } 
      ); 

      map.Property(x => x.Description);    
     }); 
    } 
} 

public class AddressMapping : ClassMapping<Address> 
{ 
    public AddressMapping() 
    { 
     Id(x => x.ID, m => m.Generator(Generators.Identity)); 
    } 
} 

用法

using (var session = sessionFactory.OpenSession()) 
using (var transaction = session.BeginTransaction()) 
{ 
    var person = new Person(); 
    var address = new Address(); 

    var personAddress = new PersonAddress 
    { 
     Address = address, 
     Person = person, 
     Description = "This is my home address" 
    }; 

    person.PersonAddresses.Add(personAddress); 

    session.Save(person); 

    // exception of NHibernate.TransientObjectException 
    transaction.Commit(); 
} 

異常

object references an unsaved transient instance - 
save the transient instance before flushing or set 
cascade action for the property to something that 
would make it autosave. 

Type: MyApp.Models.Address, Entity: MyApp.Models.Address 

我相信我上面的代碼不應該是有問題的,因爲我節省了Person,該級聯下到PersonAddress,然後落下下來到Address。然而,NHibernate告訴我要麼自動保存(與級聯?),要麼自己保存它。

解決方法

session.Save(person); 
session.Save(address); 

transaction.Commit(); 

然而,如實際生產代碼比短例子複雜得多,這是非常有問題的。在實際生產代碼中,我有一個Organization對象,其中包含一個Person(其中包含人員地址和地址)的列表。

有沒有辦法解決這個問題,而不必附加Save呼叫破解,因爲它是很難寫在一個通用的方法,同時嘗試我的應用程序邏輯和持久性邏輯分離的方式。

爲什麼解決辦法不會對我的方案

// where unitOfWork is a wrapper for the session 
using (var unitOfWork = unitOfWorkFactory.Create()) 
{ 
    var organization = unitOfWork.OrganizationRepository.GetById(24151); 

    organization.AddPerson(new Person { 
     PersonAddress = new PersonAddress { 
      Address = new Address(), 
      Description = "Some description" 
     } 
    }); 

    unitOfWork.Commit(); 
} 

正如你所看到的,UnitOfWorkUnitOfWorkFactory工作,OrganizationRepository都是抽象的,因此不可能爲我節省地址和人而不會泄露那些實現細節,如果持久性如我所料地下降,我認爲我應該能夠做到這一點。

我的問題是,如何堅持Address沒有明確告訴NHibernate這樣做?

回答

1

你的東西就工作...除非PersonAddress的映射將不會代表composite-id

儘管FO的事實,你可以使用Cascade.AllCompositeId映射

ComposedId(map => 
{ 
    map.ManyToOne(x => x.Person, 
      m => { m.Cascade(Cascade.All); // Cascade here is not applied 

這將不會應用內部。 <composite-id>(doc 5.1.5)子元素​​不支持級聯。

,所有的東西會工作,如果PersonAddress會有些surrogated鍵,引用ADRESScascade="all"

這裏還看到答案NHibernate - How to map composite-id with parent child reference被映射爲標準many-to-one ...得到更多的理由使用代理,而不是複合編號

+0

這可能不使用代理鍵完成? – Matthew

+0

不能,我確定:(對不起,你仍然可以調用session.Save(地址)...然後確定,但是在這種情況下你更喜歡...不,Cascade不能在複合主鍵插入時工作。在這種情況下 –

1

有一點是該地址不是PersonAddress的孩子。 PersonAddress是Person和Address的子代。你可以告訴因爲ManyToOne。

我也會將關係的另一端從Address下映射到PersonAddress。您需要這樣做,以便您可以標記INVERSE關係,因爲它看起來像您希望子PersonAddress處理關係的所有權。

下面是一個應該保存所有內容的快速映射。

public class Person 
{ 
    public virtual Guid Id { get; protected set; } 
    public virtual String Name { get; set; } 
    public virtual ICollection<PersonAddress> PersonAddresses { get; protected set; } 

    public Person() 
    { 
     PersonAddresses = new List<PersonAddress>(); 
    } 

    public virtual void AddPersonAddress(PersonAddress personAddress) 
    { 
     if (PersonAddresses.Contains(personAddress)) 
      return; 

     PersonAddresses.Add(personAddress); 
     personAddress.Person = this; 
    } 
} 

public class PersonMap : ClassMapping<Person> 
{ 
    public PersonMap() 
    { 
     Id(x => x.Id, map => 
     { 
      map.Column("Id"); 
      map.Generator(Generators.GuidComb); 
     }); 

     Property(x => x.Name); 

     Bag(x => x.PersonAddresses, map => 
     { 
      map.Table("PersonAddress"); 
      map.Key(k => 
      { 
       k.Column(col => col.Name("PersonId")); 
      }); 
      map.Cascade(Cascade.All); 
     }, 
     action => action.OneToMany()); 
    } 
} 

public class Address 
{ 
    public virtual Guid Id { get; protected set; } 
    public virtual String AddressLine1 { get; set; } 
    public virtual ICollection<PersonAddress> PersonAddresses { get; protected set; } 

    public Address() 
    { 
     PersonAddresses = new List<PersonAddress>(); 
    } 
} 

public class AddressMap : ClassMapping<Address> 
{ 
    public AddressMap() 
    { 
     Id(x => x.Id, map => 
     { 
      map.Column("Id"); 
      map.Generator(Generators.GuidComb); 
     }); 

     Property(x => x.AddressLine1); 

     Bag(x => x.PersonAddresses, map => 
     { 
      map.Inverse(true); 
      map.Table("PersonAddress"); 
      map.Key(k => 
      { 
       k.Column(col => col.Name("AddressId")); 
      }); 
      //map.Cascade(Cascade.All); 
     }, 
     action => action.OneToMany()); 
    } 
} 

public class PersonAddress 
{ 
    public virtual Guid Id { get; set; } 
    public virtual Person Person { get; set; } 
    public virtual Address Address { get; set; } 

    public virtual String Description { get; set; } 
} 

public class PersonAddressMap : ClassMapping<PersonAddress> 
{ 
    public PersonAddressMap() 
    { 
     Id(x => x.Id, map => 
     { 
      map.Column("Id"); 
      map.Generator(Generators.GuidComb); 
     }); 

     ManyToOne(x => x.Person, map => 
     { 
      map.Column("PersonId"); 
      map.NotNullable(false); 
     }); 

     ManyToOne(x => x.Address, map => 
     { 
      map.Column("AddressId"); 
      map.NotNullable(false); 
      map.Cascade(Cascade.All); 
     }); 

     Property(x => x.Description); 
    } 
} 

而且經過單元測試

[Test] 
    public void CascadeMapTest() 
    { 
     using (ISession session = SessionFactory.OpenSession()) 
     { 
      using (ITransaction tx = session.BeginTransaction()) 
      { 
       var person = new Person { Name = "Test" }; 
       person.AddPersonAddress(new PersonAddress { Address = new Address { AddressLine1 = "123 main street" }, Description = "WORK" }); 

       session.Save(person); 

       tx.Commit(); 
      } 
     } 
    } 
+0

當我給'地址'引用'PersonAddress'並且標記爲真(當虛假的時候)時,我會得到同樣的異常。 – Matthew

+0

我用完整的映射更新了答案。 – Fran

+0

謝謝,我感謝你的幫助,但是我不能使用這個解決方案,因爲我沒有修改'PersonAddress'表的可能性,因此使用複合id的原因是。 – Matthew