在我的对象图中,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
列表(其中包含personaddresses和地址)。
有没有办法解决这个问题,而不必进行额外的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
?
答案 0 :(得分:1)
有一件事是Address不是PersonAddress的子代。 PersonAddress是Person和Address的子节点。你可以说因为ManyToOne。
我还会将关系的另一端从Address down映射到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();
}
}
}
答案 1 :(得分:1)
除非Person
和Address
的映射不代表composite-id
,否则您的所有内容都会有效。
尽管如此,您可以在Cascade.All
映射中使用CompositeId
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 ...以获取更多使用代理的理由,而不是复合身份