我通过另一种方式解决了这个问题。这实际上是有效的,但是当使用具有多个循环引用的更复杂的数据结构时,它可能会在以后出现问题。但目前还没有必要。
我尝试添加循环引用功能ServiceStack.Text
但发现没有任何开始的意义。也许mythz可以给我一个提示?该功能应该非常容易完成。
我需要该功能来序列化我的数据模型以完全支持NHibernate
的合并函数。
我按照神话的建议忽略了属性IgnoreDataMemberAttribute
这会导致循环引用。
但这也需要在反序列化后再次重建它们,以使合并功能正常工作。
-> 这就是解决方案,现在按照我的做法进行操作:
我从一个简单的原型开始进行测试,这是一个数据模型
Customer
1->n Orders
1->n OrderDetail
.
每个类都派生自实体类。
public class Customer : Entity
{
public virtual string Name { get; set; }
public virtual string City { get; set; }
public virtual IList<Order> Orders { get; set; }
}
public class Order : Entity
{
public virtual DateTime OrderDate { get; set; }
public virtual IList<OrderDetail> OrderDetails { get; set; }
[IgnoreDataMember]
public virtual Customer Customer { get; set; }
}
public class OrderDetail : Entity
{
public virtual string ProductName { get; set; }
public virtual int Amount { get; set; }
[IgnoreDataMember]
public virtual Order Order{ get; set; }
}
如你看到的,Order
and OrderDetail
有对其父对象的反向引用,这会在序列化时导致循环引用。这可以通过忽略反向引用来解决IgnoreDataMemberAttribute
.
我现在的假设是,每个子实例Order
这是里面的Customer
的列表属性Orders
有对此的反向引用Customer
实例。
这就是我重建循环树的方法:
public static class SerializationExtensions
{
public static void UpdateChildReferences(this object input)
{
var hashDictionary = new Dictionary<int, object>();
hashDictionary.Add(input.GetHashCode(), input);
var props = input.GetType().GetProperties();
foreach (var propertyInfo in props)
{
if (propertyInfo.PropertyType.GetInterfaces()
.Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
var instanceTypesInList = propertyInfo.PropertyType.GetGenericArguments();
if(instanceTypesInList.Length != 1)
continue;
if (instanceTypesInList[0].IsSubclassOf(typeof(Entity)))
{
var list = (IList)propertyInfo.GetValue(input, null);
foreach (object t in list)
{
UpdateReferenceToParent(input, t);
UpdateChildReferences(t);
}
}
}
}
}
private static void UpdateReferenceToParent(object parent, object item)
{
var props = item.GetType().GetProperties();
var result = props.FirstOrDefault(x => x.PropertyType == parent.GetType());
if (result != null)
result.SetValue(item, parent, null);
}
}
此代码不适用于1->1目前实体引用(还不需要),但我认为它可以很容易地扩展。
现在,我可以在客户端拥有 POCO 类模型,添加/更新/删除子对象并将整个树发送回服务器。Nhibernate
足够聪明来确定哪个实体是新的/更新的/删除的。它还只更新更改的实体和更改的属性!如果删除订单,它还会删除所有订单详细信息。
为了完整性,这就是流畅的 nhibernate 映射:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Schema("YOURSCHEMA");
Table("CUSTOMER");
Id(x => x.Id, "ID").GeneratedBy.Assigned();
Map(x => x.Name, "NAM");
Map(x => x.City, "CITY");
HasMany(x => x.Orders)
.KeyColumn("CUSTOMER_ID")
.Not.LazyLoad()
.Inverse()
.Cascade.AllDeleteOrphan();
DynamicUpdate();
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
Schema("YOURSCHEMA");
Table("CUSTOMER_ORDER");
Id(x => x.Id, "ID").GeneratedBy.Assigned();
Map(x => x.OrderDate, "ORDER_DATE");
HasMany(x => x.OrderDetails)
.KeyColumn("ORDER_ID")
.Not.LazyLoad()
.Inverse()
.Cascade.AllDeleteOrphan();
References<Customer>(x => x.Customer, "CUSTOMER_ID");
DynamicUpdate();
}
}
public class OrderDetailMap : ClassMap<OrderDetail>
{
public OrderDetailMap()
{
Schema("YOURSCHEMA");
Table("ORDER_DETAIL");
Id(x => x.Id, "ID").GeneratedBy.Assigned();
Map(x => x.ProductName, "PRODUCT_NAME");
Map(x => x.Amount, "AMOUNT");
References<Order>(x => x.Order, "ORDER_ID");
DynamicUpdate();
}
}
DynamicUpdate()用于让nhibernate只更新改变的属性。
您现在只需要使用ISession.Merge(customer)
功能可以正确保存所有内容。