別の方法で問題を解決しました。これは実際には機能しますが、後で複数の循環参照を持つより複雑なデータ構造を使用するときに問題が発生する可能性があります。しかし、今のところ必要ありません。
循環参照機能を追加しようとしましたServiceStack.Text
が、開始するポイントが見つかりませんでした。神話がヒントになるかも?この機能は、非常に簡単に実行できる必要があります。
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
。
私の仮定は、の list プロパティ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
どのエンティティが新規/更新/削除されたかを判断するのに十分賢いです。また、変更されたエンティティのみを更新し、変更されたプロパティのみも更新します! Order が削除された場合は、すべての 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)
すべてを正しく保存できます。