JSONおよびEntityを使用した循環参照の問題を回避する方法


13

データモデル/データベースのプレゼンテーションレイヤーとエンティティフレームワークにJSONを使用したMVCを活用するWebサイトの作成を実験しています。私の問題は、ModelオブジェクトをJSONにシリアル化することに関係しています。

私は自分のデータベースを作成するためにコードファーストの方法を使用しています。コードファーストの方法を実行するとき、1対多の関係(親/子)では、子が親への参照を持つ必要があります。(サンプルコードはタイプミスかもしれませんが、写真を取得します)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

JsonResultを介して「親」オブジェクトを返す場合、「子」にはクラス親のプロパティがあるため、循環参照エラーがスローされます。

ScriptIgnore属性を試しましたが、子オブジェクトを見ることができません。ある時点で、親子ビューに情報を表示する必要があります。

循環参照を持たない親と子の両方の基本クラスを作成しようとしました。残念ながら、baseParentとbaseChildを送信しようとすると、これらは派生クラスとしてJSONパーサーによって読み取られます(この概念が私を逃れていると確信しています)。

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

私が思いついた1つの解決策は、「ビュー」モデルを作成することです。親クラスへの参照を含まないシンプルなバージョンのデータベースモデルを作成します。これらのビューモデルにはそれぞれ、データベースバージョンを返すメソッドと、データベースモデルをパラメーターとして取得するコンストラクターがあります(viewmodel.name = databasemodel.name)。この方法は機能しますが、強制されているようです。

注:ここで投稿しているのは、これがより議論に値すると思うからです。別のデザインパターンを活用してこの問題を克服することも、モデルで別の属性を使用するのと同じくらい簡単にすることもできます。私の検索では、この問題を克服する良い方法を見ていません。

私の最終目標は、サーバーと通信してデータを表示するためにJSONを大幅に活用する素晴らしいMVCアプリケーションを持つことです。レイヤー全体で一貫したモデルを維持しながら(または、私が思いつく限り)。

回答:


6

私はあなたの質問に2つの異なる主題を見ます:

  • JSONにシリアル化するときに循環参照を管理する方法は?
  • ビューでモデルエンティティとしてEFエンティティを使用することはどれくらい安全ですか?

循環参照については、簡単な解決策はないと言ってすみません。まず、JSONは循環参照を表すために使用できないため、次のコード:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

結果: TypeError: Converting circular structure to JSON

唯一の選択肢は、コンポジットのみを保持する->コンポジションのコンポーネント部分であり、「戻るナビゲーション」コンポーネントを破棄する->コンポジットです。

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

ここでは、jQueryを使用して、クライアント側でこのナビゲーションプロパティを再構成することを妨げるものはありません。

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

ただし、JSON.stringifyは循環参照をシリアル化できないため、サーバーに送信する前に再度破棄する必要があります。

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

現在、ビューモデルエンティティとしてEFエンティティを使用する問題があります。

最初のEFは、クラスの動的プロキシを使用して変更検出や遅延読み込みなどの動作を実装する可能性が高いため、EFエンティティをシリアル化する場合はそれらを無効にする必要があります。

さらに、UIでEFエンティティを使用すると、すべてのデフォルトバインダーがリクエストのすべてのフィールドを、ユーザーに設定させたくないものを含むエンティティフィールドにマッピングするため、危険にさらされる可能性があります。

したがって、MVCアプリを適切に設計する場合は、専用のビューモデルを使用して、内部ビジネスモデルの「内臓」がクライアントに公開されないようにすることをお勧めします。したがって、特定のビューモデルをお勧めします。


循環参照とEFの問題の両方を回避できる、オブジェクト指向の手法を使った派手な方法はありますか。
ダンスキャン14年

循環参照とEFの問題の両方を回避できるオブジェクト指向の手法を使用した素晴らしい方法はありますか?BaseObjectと同様に、entityObjectとviewObjectによって継承されます。したがって、entityObjectは循環参照を持ちますが、viewObjectは循環参照を持ちません。entityObject(viewObject.name = entityObject.name)からviewObjectを作成することでこれを回避しましたが、これは時間の無駄のようです。この問題を回避するにはどうすればよいですか?
ダンスキャン14年

彼らはあなたとても。あなたの説明は非常に明確で理解しやすいものでした。
ニック

2

オブジェクトをシリアル化しようとするより簡単な代替方法は、親/子オブジェクトのシリアル化を無効にすることです。代わりに、関連する親/子オブジェクトを必要なときに取得するために、個別の呼び出しを行うことができます。これはアプリケーションにとって理想的ではないかもしれませんが、オプションです。

これを行うには、DataContractSerializerをセットアップし、データモデルクラスのコンストラクターでDataContractSerializer.PreserveObjectReferencesプロパティを「false」に設定します。これは、HTTP応答のシリアル化時にオブジェクト参照を保持しないことを指定します。

例:

JSON形式:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

XML形式:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

つまり、子オブジェクトが参照されているアイテムを取得した場合、子オブジェクトはシリアル化されません。

DataContractsSerializerクラスも参照してください。


1

循環参照を処理するJSONシリアライザー

カスタムジャクソンの例を次に示します JSONSerializer最初の出現をシリアル化し、reference後続のすべての出現で* を最初の出現に保存することによって循環参照を処理するです。

Jacksonでオブジェクトをシリアル化する際の循環参照の処理

上記の記事からの関連部分スニペット:

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

0

私が思いついた1つの解決策は、「ビュー」モデルを作成することです。親クラスへの参照を含まないシンプルなバージョンのデータベースモデルを作成します。これらのビューモデルにはそれぞれ、データベースバージョンを返すメソッドと、データベースモデルをパラメーターとして取得するコンストラクターがあります(viewmodel.name = databasemodel.name)。この方法は機能しますが、強制されているようです。

最低限のデータを送信することが唯一の正しい答えです。データベースからデータを送信する場合、通常、すべての関連付けを持つすべての列を送信することは意味がありません。消費者は、データベースの関連付けや構造、つまりデータベースを扱う必要はありません。これにより帯域幅が節約されるだけでなく、保守、読み取り、消費もはるかに簡単になります。データをクエリしてから、eqを送信するために実際に必要なものをモデル化します。最低限。


今ではすべてを2回変換する必要があるため、ビッグデータについて話しているときは、より多くの処理時間が必要です。
デビッドファンダグテレン

-2

.Include(x => x.TableName ) リレーションシップを返さない(プリンシパルテーブルから依存テーブルへ)、または1行のデータのみを返す、ここで修正:

/programming/43127957/include-not-working-in-net-core-returns-one-parent

また、Startup.csで、これが一番上にあることを確認してください。

using Microsoft.EntityFrameworkCore; 
using Newtonsoft.Json; 
using Project_Name_Here.Models;

息子ワット?えーと。
アメルス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.