web-dev-qa-db-ja.com

タイプ 'SubSonic.Schema .DatabaseColumn'のオブジェクトのシリアル化中に循環参照が検出されました。

単純なJSONリターンを実行しようとしていますが、以下の問題があります。

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

この質問のタイトルに示されている例外を除き、HTTP 500を受け取ります。私も試しました

var data = Event.All().ToList()

それは同じ問題をもたらしました。

これはバグですか、それとも実装ですか?

166
Jon

JSONシリアライザーでサポートされていないオブジェクト階層に循環参照があるようです。すべての列が必要ですか?ビューで必要なプロパティのみを選択できます。

return Json(new 
{  
    PropertyINeed1 = data.PropertyINeed1,
    PropertyINeed2 = data.PropertyINeed2
});

これにより、JSONオブジェクトが軽くなり、理解しやすくなります。多くのプロパティがある場合、 AutoMapper を使用して 自動 DTOオブジェクトとViewオブジェクトをマッピングできます。

170
Darin Dimitrov

私は同じ問題を抱えていてusing Newtonsoft.Json;で解決しました

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});

return Content(list, "application/json");
93
ddfnfal

これは実際には、複雑なオブジェクトが結果のjsonオブジェクトを失敗させるために起こります。また、オブジェクトがマップされると子がマップされ、親がマップされて循環参照が発生するため、失敗します。 Jsonはシリアル化に無限の時間を要するため、例外の問題を防ぎます。

Entity Frameworkマッピングでも同じ動作が発生し、解決策は不要なプロパティをすべて破棄することです。

最終回答を明示すると、コード全体は次のようになります。

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           new {
                Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
               }
           , JsonRequestBehavior.AllowGet
           );
}

Resultプロパティ内のオブジェクトが必要ない場合は、次のようにすることもできます。

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
           , JsonRequestBehavior.AllowGet
           );
}
54
ClayKaboom

まとめると、これには4つの解決策があります。

解決策1:DBContextのProxyCreationをオフにして、最後に復元します。

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        bool proxyCreation = db.Configuration.ProxyCreationEnabled;
        try
        {
            //set ProxyCreation to false
            db.Configuration.ProxyCreationEnabled = false;

            var data = db.Products.ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
        finally
        {
            //restore ProxyCreation to its original state
            db.Configuration.ProxyCreationEnabled = proxyCreation;
        }
    }

解決策2:ReferenceLoopHandlingを設定してJsonConvertを使用し、シリアライザーの設定を無視します。

    //using using Newtonsoft.Json;

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.ToList();

            JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
            var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);

            return Json(result, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

次の2つのソリューションは同じですが、モデルは強い型付けがされているため、使用する方が適切です。

解決策3:必要なプロパティのみを含むモデルを返します。

    private DBEntities db = new DBEntities();//dbcontext

    public class ProductModel
    {
        public int Product_ID { get; set;}

        public string Product_Name { get; set;}

        public double Product_Price { get; set;}
    }

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new ProductModel
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

解決策4:必要なプロパティのみを含む新しい動的オブジェクトを返します。

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }
13
Amro

JSONは、xmlやその他のさまざまな形式と同様に、ツリーベースのシリアル化形式です。オブジェクトに循環参照がある場合、「ツリー」は次のようになります。

root B => child A => parent B => child A => parent B => ...

特定のパスに沿ってナビゲーションを無効にする方法がしばしばあります。たとえば、XmlSerializerを使用すると、親プロパティをXmlIgnoreとしてマークできます。問題のjsonシリアライザーでこれが可能かどうかも、DatabaseColumnに適切なマーカーがあるかどうかもわかりません(すべてのシリアル化APIを参照する必要があるため、very可能性は低いです)

7
Marc Gravell

[JsonIgnore]をモデルのvirtualsプロパティに追加します。

その理由は、EntityFrameworkエンティティの生成に使用される新しいDbContext T4テンプレートです。変更の追跡を実行できるようにするため、このテンプレートではプロキシパターンを使用して、Nice POCOをラップします。これにより、JavaScriptSerializerでシリアル化するときに問題が発生します。

したがって、2つのソリューションは次のとおりです。

  1. クライアントで必要なプロパティをシリアル化して返すか、
  2. コンテキストの構成でプロキシを設定することにより、プロキシの自動生成をオフにすることができます

    context.Configuration.ProxyCreationEnabled = false;

以下の記事で非常によく説明されています。

http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/

4
nilesh

テーブルオブジェクトを直接変換しないでください。他のテーブル間にリレーションが設定されている場合、このエラーがスローされる可能性があります。代わりに、モデルクラスを作成し、クラスオブジェクトに値を割り当ててからシリアル化できます。

4
Unais.N.I

Newtonsoft.Jsonの使用:Global.asax Application_Startメソッドで次の行を追加します。

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
4
kravits88

提供された答えは良いのですが、「アーキテクチャ」の観点を追加することで改善できると思います。

調査

MVC's Controller.Json関数がジョブを実行していますが、この場合、関連するエラーを提供するのは非常に貧弱です。 Newtonsoft.Json.JsonConvert.SerializeObjectを使用することにより、エラーは循環参照をトリガーしているプロパティを正確に特定します。これは、より複雑なオブジェクト階層をシリアル化するときに特に役立ちます。

適切なアーキテクチャ

ORMのナビゲーションプロパティはシリアル化に関してperditionへの道であるため、データモデル(EFモデルなど)をシリアル化しようとしないでください。データフローは次のようになります。

Database -> data models -> service models -> JSON string 

サービスモデルは、自動マッパーを使用してデータモデルから取得できます(例: Automapper )。これは循環参照の欠如を保証するものではありませんが、適切な設計でそれを行う必要があります。サービスモデルには、サービスコンシューマが必要とするもの(つまりプロパティ)を正確に含める必要があります。

これらのまれなケースでは、クライアントが異なるレベルの同じオブジェクトタイプを含む階層を要求すると、サービスは親->子関係の線形構造を作成できます(参照ではなく識別子のみを使用)。

現代のアプリケーションは、複雑なデータ構造を一度にロードすることを避ける傾向があり、サービスモデルはスリムでなければなりません。例えば。:

  1. イベントへのアクセス-ヘッダーデータ(識別子、名前、日付など)のみが読み込まれます->ヘッダーデータのみを含むサービスモデル(JSON)
  2. 管理された出席者リスト-ポップアップにアクセスし、リストを遅延ロードします->出席者のリストのみを含むサービスモデル(JSON)
2
Alexei

MVC5ビューでKnockoutを使用しているため、修正を使用しています。

アクションで

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

関数

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    {
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        {
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        }
        return Entity_;  
    }
1
A.Kosecik

循環参照の原因となるプロパティに気付くことができます。その後、次のようなことができます:

private Object DeCircular(Object object)
{
   // Set properties that cause the circular reference to null

   return object
}
0
Bassil