使用 Cascade.All 时出现 TransientObjectException
Posted
技术标签:
【中文标题】使用 Cascade.All 时出现 TransientObjectException【英文标题】:TransientObjectException when using Cascade.All 【发布时间】:2013-12-10 18:58:58 【问题描述】:在我的对象图中,Person
与 Address
具有多对多关系,并且连接表有额外的列。
类结构
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();
如您所见,UnitOfWork
、UnitOfWorkFactory
和 OrganizationRepository
都是抽象的,因此我不可能在不泄露实现细节的情况下同时保存地址和人员,我认为我应该这样做如果持久性如我预期的那样级联,则能够做到。
我的问题是,我如何在不明确告诉 NHibernate 这样做的情况下坚持 Address
?
【问题讨论】:
【参考方案1】:你的所有东西都可以工作......除非Person
和Address
的映射不代表composite-id
。
尽管事实上,您可以在 CompositeId
映射中使用 Cascade.All
ComposedId(map =>
map.ManyToOne( x => x.Person,
m => m.Cascade(Cascade.All); // Cascade here is not applied
这不会被应用。 <composite-id>
(doc 5.1.5) 子元素 <key-many-to-one>
不支持级联。
但是,如果PersonAddress
有一些代理键,那么所有的东西都会起作用,并且对 Person 和 Adress 的引用将映射为标准 many-to-one
和 cascade="all"
也可以在这里查看答案NHibernate - How to map composite-id with parent child reference ... 以获得更多使用代理而不是复合 id 的理由
【讨论】:
不使用代理键是否可以实现? 没有。我确定:(对不起。您仍然可以调用 session.Save(address) ... 然后确定。但是在您喜欢的场景中...不。级联不能用于复合主键插入。在这种情况下地址必须存在【参考方案2】:有一点是,Address 不是 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();
【讨论】:
当我给Address
一个对 PersonAddress
的引用并将反向标记为 true 时,我得到了同样的异常(并且当为 false 时)。
谢谢,感谢您的帮助,但是我无法使用此解决方案,因为我无法修改 PersonAddress
表,因此使用复合 id 的原因。
是的。然后查看我的映射并使用来自 Radim 的信息来获取composedId 的东西。变化不大。导入部分是 cascade.all 和 inverse 的位置。以上是关于使用 Cascade.All 时出现 TransientObjectException的主要内容,如果未能解决你的问题,请参考以下文章
Hibernate - 拥有的实体实例不再引用具有 cascade=”all-delete-orphan” 的集合
hibernate里的inverse和cascade是什么区别