我用另一种方式解决了这个问题。这实际上是可行的,但是当使用具有多个循环引用的更复杂的数据结构时,它可能会在以后产生问题。但目前没有必要。
我尝试向其中添加循环引用功能,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
并且OrderDetail
有一个对其父对象的反向引用,这在序列化时会导致循环引用。这可以通过忽略后向引用来解决IgnoreDataMemberAttribute
。
我现在的假设是,其中的每个子实例Order
insideCustomer
的 list 属性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
足够聪明地确定哪个实体是新的/更新的/删除的。它也只更新更改的实体和更改的属性!如果订单被删除,它也会删除所有 OrderDetails。
这就是流畅的 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)
功能即可正确保存所有内容。