nHibernateでJSON.NETを使用している人はいますか?子コレクションを含むクラスを読み込もうとすると、エラーが発生することに気付きました。
9 に答える
同じ問題に直面していたので、@ Liedmanのコードを使用しようとしましたがGetSerializableMembers()
、プロキシされた参照に対して呼び出されることはありませんでした。オーバーライドする別のメソッドを見つけました:
public class NHibernateContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (typeof(NHibernate.Proxy.INHibernateProxy).IsAssignableFrom(objectType))
return base.CreateContract(objectType.BaseType);
else
return base.CreateContract(objectType);
}
}
これとまったく同じ問題がありましたが、Handcraftsman の回答からインスピレーションを得て解決されました。
この問題は、JSON.NET が NHibernate のプロキシ クラスをシリアル化する方法について混乱しているために発生します。解決策: 基本クラスのようにプロキシ インスタンスをシリアル化します。
Handcraftsman のコードの簡略版は次のようになります。
public class NHibernateContractResolver : DefaultContractResolver {
protected override List<MemberInfo> GetSerializableMembers(Type objectType) {
if (typeof(INHibernateProxy).IsAssignableFrom(objectType)) {
return base.GetSerializableMembers(objectType.BaseType);
} else {
return base.GetSerializableMembers(objectType);
}
}
}
IMHO、このコードには、カスタム属性などに関する JSON.NET のデフォルトの動作に依存しているという利点があります (そして、コードはずっと短くなります!)。
こんな感じで使われています
var serializer = new JsonSerializer{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new NHibernateContractResolver()
};
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new Newtonsoft.Json.JsonTextWriter(stringWriter);
serializer.Serialize(jsonWriter, objectToSerialize);
string serializedObject = stringWriter.ToString();
注:このコードは、NHibernate 2.1 用に作成され、使用されています。一部のコメンターが指摘しているように、NHibernate の新しいバージョンではそのままでは機能しないため、いくつかの調整を行う必要があります。新しいバージョンの NHibernate でコードを更新する必要がある場合は、コードを更新しようとします。
Json.NET で NHibernate を使用していますが、シリアル化されたオブジェクトで不可解な "__interceptors" プロパティを取得していることに気付きました。Google 検索により、Lee Henson によるこの優れたソリューションが見つかりました。これは、次のように Json.NET 3.5 Release 5 で動作するように調整しました。
public class NHibernateContractResolver : DefaultContractResolver
{
private static readonly MemberInfo[] NHibernateProxyInterfaceMembers = typeof(INHibernateProxy).GetMembers();
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
var members = base.GetSerializableMembers(objectType);
members.RemoveAll(memberInfo =>
(IsMemberPartOfNHibernateProxyInterface(memberInfo)) ||
(IsMemberDynamicProxyMixin(memberInfo)) ||
(IsMemberMarkedWithIgnoreAttribute(memberInfo, objectType)) ||
(IsMemberInheritedFromProxySuperclass(memberInfo, objectType)));
var actualMemberInfos = new List<MemberInfo>();
foreach (var memberInfo in members)
{
var infos = memberInfo.DeclaringType.BaseType.GetMember(memberInfo.Name);
actualMemberInfos.Add(infos.Length == 0 ? memberInfo : infos[0]);
}
return actualMemberInfos;
}
private static bool IsMemberDynamicProxyMixin(MemberInfo memberInfo)
{
return memberInfo.Name == "__interceptors";
}
private static bool IsMemberInheritedFromProxySuperclass(MemberInfo memberInfo, Type objectType)
{
return memberInfo.DeclaringType.Assembly == typeof(INHibernateProxy).Assembly;
}
private static bool IsMemberMarkedWithIgnoreAttribute(MemberInfo memberInfo, Type objectType)
{
var infos = typeof(INHibernateProxy).IsAssignableFrom(objectType)
? objectType.BaseType.GetMember(memberInfo.Name)
: objectType.GetMember(memberInfo.Name);
return infos[0].GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Length > 0;
}
private static bool IsMemberPartOfNHibernateProxyInterface(MemberInfo memberInfo)
{
return Array.Exists(NHibernateProxyInterfaceMembers, mi => memberInfo.Name == mi.Name);
}
}
これを使用するには、JsonSerializer の ContractResolver プロパティにインスタンスを配置するだけです。jishi が指摘した循環依存の問題は、ReferenceLoopHandling プロパティを ReferenceLoopHandling.Ignore に設定することで解決できます。Json.Net を使用してオブジェクトをシリアル化するために使用できる拡張メソッドを次に示します。
public static void SerializeToJsonFile<T>(this T itemToSerialize, string filePath)
{
using (StreamWriter streamWriter = new StreamWriter(filePath))
{
using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter))
{
jsonWriter.Formatting = Formatting.Indented;
JsonSerializer serializer = new JsonSerializer
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new NHibernateContractResolver(),
};
serializer.Serialize(jsonWriter, itemToSerialize);
}
}
}
循環依存エラーが発生していますか?シリアル化からオブジェクトをどのように無視しますか?
遅延読み込みはプロキシオブジェクトを生成するため、クラスメンバーが持っている属性はすべて失われます。プロキシオブジェクトに[JsonIgnore]属性がなくなったため、NewtonsoftJSONシリアライザーで同じ問題が発生しました。
シリアル化できるように、ほとんどのオブジェクトを熱心にロードする必要があるでしょう。
ICriteria ic = _session.CreateCriteria(typeof(Person));
ic.Add(Restrictions.Eq("Id", id));
if (fetchEager)
{
ic.SetFetchMode("Person", FetchMode.Eager);
}
これを行う良い方法は、データ プロバイダー メソッドのコンストラクター (bool isFetchEager) に bool を追加することです。
私の意見では、これは設計上の問題だと思います。NH はすべての下にデータベースへの接続を作成し、中間にプロキシがあるため、それらを直接シリアル化することはアプリケーションの透過性にとって良くありません (そして、Json.NET はそれらをまったく好まないことがわかります)。
エンティティ自体をシリアル化する必要はありませんが、それらを「ビュー」オブジェクトまたは POCO または DTO オブジェクト (名前は何でも) に変換してからシリアル化する必要があります。
違いは、NH エンティティにはプロキシ、遅延属性などがある場合があることです。ビュー オブジェクトは、デフォルトでシリアライズ可能なプリミティブのみを持つ単純なオブジェクトです。
FK の管理方法 私の個人的なルールは次のとおりです。
エンティティ レベル: Person クラスと Gender クラスが関連付けられている
ビュー レベル: GenderId および GenderName プロパティを持つ Person ビュー。
これは、ビュー オブジェクトに変換するときに、プロパティをプリミティブに展開する必要があることを意味します。このようにして、json オブジェクトもよりシンプルで扱いやすくなります。
変更を DB にプッシュする必要がある場合、私の場合は AutoMapper を使用し、新しい Guid を Gender オブジェクトに変換できる ValueResolver クラスを実行します。
更新: NH から直接 (AliasToBean) ビューを取得する方法については、http ://blog.andrewawhitaker.com/blog/2014/06/19/queryover-series-part-4-transforming/ を確認してください。これはDB側のブーストになります。
NHibernate プロキシ クラスを含むオブジェクトをシリアル化すると、データベース全体をダウンロードすることになる可能性があります。これは、プロパティにアクセスすると、NHibernate がデータベースへの要求をトリガーするためです。NHibernate の作業単位を実装しました: NHibernateの最も厄介な問題の 2 つ (フェッチ使用時のプロキシ クラスとデカルト積) を修正するNHUnitです。
これをどのように使用しますか?
var customer = await _dbContext.Customers.Get(customerId) //returns a wrapper to configure the query
.Include(c => c.Addresses.Single().Country, //include Addresses and Country
c => c.PhoneNumbers.Single().PhoneNumberType) //include all PhoneNumbers with PhoneNumberType
.Unproxy() //instructs the framework to strip all the proxy classes when the Value is returned
.Deferred() //instructs the framework to delay execution (future)
.ValueAsync(token); //this is where all deferred queries get executed
上記のコードは、基本的にクエリを構成しています。複数の子オブジェクトを持つ ID で顧客を返します。このオブジェクトは、他のクエリ (先物) で実行する必要があり、返された結果から NHibernate プロキシを削除する必要があります。ValueAsync
が呼び出されると
、クエリが実行されます。NHUnitは、メイン クエリと結合するか、新しい将来のクエリを作成するか、バッチ フェッチを利用するかを決定します。
Github には、 NHUnit パッケージの使用方法を示す簡単なサンプル プロジェクトがあります。他の人がこのプロジェクトに興味を持っている場合は、より良いものにするためにもっと時間を費やします.