web-dev-qa-db-ja.com

エンティティフレームワークの自己参照ループが検出されました

奇妙なエラーがあります。 .NET 4.5 Web API、Entity Framework、およびMS SQL Serverを試しています。私はすでにデータベースを作成し、正しいプライマリおよび外部キーと関係を設定しました。

.edmxモデルを作成し、2つのテーブル(EmployeeとDepartment)をインポートしました。部門には多くの従業員がいる場合があり、この関係が存在します。足場オプションを使用してEmployeeControllerと呼ばれる新しいコントローラーを作成し、Entity Frameworkを使用して読み取り/書き込みアクションを持つAPIコントローラーを作成しました。ウィザードで、モデルとして従業員を選択し、データコンテキストの正しいエンティティを選択しました。

作成されるメソッドは次のようになります。

public IEnumerable<Employee> GetEmployees()
{
    var employees = db.Employees.Include(e => e.Department);
    return employees.AsEnumerable();
}

/ api/Employee経由でAPIを呼び出すと、次のエラーが表示されます。

「ObjectContent`1」タイプは、コンテンツタイプ「application/json」の応答本文のシリアル化に失敗しました。 ... System.InvalidOperationException "、" StackTrace ":null、" InnerException ":{" Message ":"エラーが発生しました。 "、" ExceptionMessage ":"タイプ 'System.Data.Entity.DynamicProxiesで検出された自己参照ループ.Employee_5D80AD978BC68A1D8BD675852F94E8B550F4CB150ADB8649E8998B7F95422552 '。パス '[0] .Department.Employees'。 "、" ExceptionType ":" Newtonsoft.Json.JsonSerializationException "、" StackTrace ":" ...

なぜ自己参照[0] .Department.Employeesですか?それはあまり意味がありません。データベースで循環参照を使用している場合、これが起こると予想されますが、これは非常に単純な例です。何が間違っているのでしょうか?

98
Lydon

Json.netに基づくデフォルトのJsonフォーマッターの正解は、ReferenceLoopHandlingIgnoreに設定することです。

これをGlobal.asaxのApplication_Startに追加するだけです:

HttpConfiguration config = GlobalConfiguration.Configuration;

config.Formatters.JsonFormatter
            .SerializerSettings
            .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

これが正しい方法です。オブジェクトを指す参照は無視されます。

他の応答は、データを除外するか、ファサードオブジェクトを作成することによって返されるリストを変更することに焦点を当てており、場合によってはオプションではありません。

JsonIgnore属性を使用して参照を制限するのは時間がかかり、問題になる別のポイントからツリーをシリアル化する場合に時間がかかることがあります。

148

これは、EFオブジェクトコレクションを直接シリアル化しようとしているために発生します。部門には従業員への関連付けがあり、従業員は部門に関連付けられているため、JSONシリアライザーはd.Employee.Departments.Employee.Departmentsなどを読み取りながら無限にループします。

シリアル化の直前にこれを修正するには、必要な小道具で匿名型を作成します

例(擬似)コード:

departments.select(dep => new { 
    dep.Id, 
    Employee = new { 
        dep.Employee.Id, dep.Employee.Name 
    }
});
51
Nicolás Straub

同じ問題が発生したため、シリアル化したくないナビゲーションプロパティに[JsonIgnore]属性を適用するだけでよいことがわかりました。親エンティティと子エンティティの両方を引き続きシリアル化しますが、自己参照ループを回避します。

32
B-Lat

私は質問がかなり古いことを知っていますが、それはまだ人気があり、ASP.net Coreの解決策を見つけることができません。

ASP.net Coreの場合、Startup.csファイルに新しいJsonOutputFormatterを追加する必要があります。

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddMvc(options =>
        {
            options.OutputFormatters.Clear();
            options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings()
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            }, ArrayPool<char>.Shared));
        });

        //...
    }

JSONシリアライザーは、実装後、ループ参照を単に無視します。つまり、相互に参照しているオブジェクトを無限にロードする代わりにnullを返します。

上記のソリューションを使用しない場合:

var employees = db.Employees.ToList();

Employeesとそれらに関連するDepartmentsをロードします。

ReferenceLoopHandlingIgnoreに設定すると、クエリに含めない限り、Departmentsはnullに設定されます。

var employees = db.Employees.Include(e => e.Department);

また、すべての OutputFormatters をクリアすることに注意してください。これが不要な場合は、この行を削除してみてください。

options.OutputFormatters.Clear();

しかし、それを削除すると、何らかの理由でself referencing loop例外が再び発生します。

16
Piotrek

メッセージエラーは、自己参照ループがあることを意味します。

生成するjsonは次の例のようになります(1人の従業員のリストを使用)。

[
employee1 : {
    name: "name",
    department : {
        name: "departmentName",
        employees : [
            employee1 : {
                name: "name",
                department : {
                    name: "departmentName",
                    employees : [
                        employee1 : {
                            name: "name",
                            department : {
                                and again and again....
                            }
                    ]
                }
            }
        ]
    }
}

]

何かを要求するときに、リンクされたすべてのエンティティを取得したくないことをdbコンテキストに伝える必要があります。 DbContextのオプションはConfiguration.LazyLoadingEnabledです

私が見つけた最良の方法は、シリアル化のためのコンテキストを作成することです:

public class SerializerContext : LabEntities 
{
    public SerializerContext()
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
}
8
Thomas B. Lze

主な問題は、他のエンティティモデルとの関係(外部キー関係)を持つエンティティモデルをシリアル化することです。この関係により、jsonまたはxmlへのシリアル化中に自己参照により例外がスローされます。多くのオプションがあります。 Automapper または Valueinjector を使用してカスタムモデルにマッピングされたエンティティモデルデータの値またはデータ(オブジェクトマッピング)からエンティティモデルをシリアル化せずにリクエストを返すと、その他の問題。または、エンティティモデルをシリアル化して、最初にエンティティモデルのプロキシを無効にすることができます

public class LabEntities : DbContext
{
   public LabEntities()
   {
      Configuration.ProxyCreationEnabled = false;
   }

オブジェクト参照をXMLで保持するには、2つのオプションがあります。より簡単なオプションは、[DataContract(IsReference = true)]をモデルクラスに追加することです。 IsReferenceパラメーターは、オブジェクト参照を有効にします。 DataContractはシリアル化をオプトインするので、プロパティにDataMember属性を追加する必要があることに注意してください。

[DataContract(IsReference=true)]
public partial class Employee
{
   [DataMember]
   string dfsd{get;set;}
   [DataMember]
   string dfsd{get;set;}
   //exclude  the relation without giving datamember tag
   List<Department> Departments{get;set;}
}

Global.asaxのJSON形式

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

xml形式

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

コンテキストモデルの部分クラス定義のコンストラクターにConfiguration.ProxyCreationEnabled = false;行を追加します。

    public partial class YourDbContextModelName : DbContext
{
    public YourDbContextModelName()
        : base("name=YourDbContextConn_StringName")
    {
        Configuration.ProxyCreationEnabled = false;//this is line to be added
    }

    public virtual DbSet<Employee> Employees{ get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    }
}
7
newstockie

使用したいモデルが1つしかなかったので、次のコードになりました。

var JsonImageModel = Newtonsoft.Json.JsonConvert.SerializeObject(Images, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
3
sindrem

ここでは、各コントローラー/アクションに明示的なサンプルを追加することも検討します。

http://blogs.msdn.com/b/yaohuang1/archive/2012/10/13/asp-net-web-api-help-page-part-2-providing-custom-samples-on- the-help-page.aspx

すなわち、config.SetActualResponseType(typeof(SomeType)、 "Values"、 "Get");

0
sobelito

Blazor(ASP.NET Core Hosted)テンプレートでこの設定を変更しようとしている場合は、AddNewtonsoftJsonプロジェクトのStartup.csServer呼び出しに以下を渡す必要があります。

services.AddMvc().AddNewtonsoftJson(options => 
    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);

.netコアサイトで同じ問題が発生しました。受け入れられた答えは私にはうまくいきませんでしたが、ReferenceLoopHandling.IgnoreとPreserveReferencesHandling.Objectsの組み合わせがそれを修正することがわかりました。

//serialize item
var serializedItem = JsonConvert.SerializeObject(data, Formatting.Indented, 
new JsonSerializerSettings
{
     PreserveReferencesHandling = PreserveReferencesHandling.Objects,
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
0
D. Dahlberg

例としての自己参照

================================================== ===========

public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public int ManagerId { get; set; }
    public virtual Employee Manager { get; set; }

    public virtual ICollection<Employee> Employees { get; set; }

    public Employee()
    {
        Employees = new HashSet<Employee>();
    }
}

================================================== ===========

        HasMany(e => e.Employees)
            .WithRequired(e => e.Manager)
            .HasForeignKey(e => e.ManagerId)
            .WillCascadeOnDelete(false);
0