programing tip

엔티티 프레임 워크 자체 참조 루프가 감지되었습니다.

itbloger 2020. 8. 20. 07:56
반응형

엔티티 프레임 워크 자체 참조 루프가 감지되었습니다.


이상한 오류가 있습니다. .NET 4.5 Web API, Entity Framework 및 MS SQL Server를 실험하고 있습니다. 이미 데이터베이스를 만들고 올바른 기본 및 외래 키와 관계를 설정했습니다.

.edmx 모델을 만들고 Employee와 Department의 두 테이블을 가져 왔습니다. 부서에는 많은 직원이있을 수 있으며 이러한 관계가 존재합니다. Entity Framework를 사용하는 읽기 / 쓰기 작업이있는 API 컨트롤러를 만들기 위해 스캐 폴딩 옵션을 사용하여 EmployeeController라는 새 컨트롤러를 만들었습니다. 마법사에서 Employee를 모델로 선택하고 데이터 컨텍스트에 대한 올바른 엔터티를 선택했습니다.

생성되는 메서드는 다음과 같습니다.

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 인 이유는 무엇입니까? 그다지 말이되지 않습니다. 내 데이터베이스에 순환 참조가 있으면 이런 일이 발생할 것으로 예상하지만 이것은 매우 간단한 예입니다. 무엇이 잘못 될 수 있습니까?


Json.net을 기반으로하는 기본 Json 포매터에 대한 정답은로 설정 ReferenceLoopHandling하는 것 Ignore입니다.

다음 Application_Start을 Global.asax에 추가하면됩니다 .

HttpConfiguration config = GlobalConfiguration.Configuration;

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

이것이 올바른 방법입니다. 개체를 다시 가리키는 참조를 무시합니다.

다른 응답은 데이터를 제외하거나 파사드 객체를 만들어 반환되는 목록을 변경하는 데 중점을 두 었으며 때로는 옵션이 아닙니다.

JsonIgnore속성을 사용하여 참조를 제한하는 것은 시간이 많이 걸릴 수 있으며 문제가되는 다른 지점에서 시작하는 트리를 직렬화하려는 경우에 가능합니다.


이는 EF 개체 컬렉션을 직접 직렬화하려고하기 때문에 발생합니다. 부서에는 직원과 직원과 부서가 연관되어 있으므로 JSON 직렬 변환기는 d.Employee.Departments.Employee.Departments 등을 무한 반복해서 읽습니다.

직렬화 직전에이 문제를 해결하려면 원하는 소품으로 익명 유형을 만듭니다.

예제 (의사) 코드 :

departments.select(dep => new { 
    dep.Id, 
    Employee = new { 
        dep.Employee.Id, dep.Employee.Name 
    }
});

나는 같은 문제가 있었고 [JsonIgnore]직렬화하지 않으려는 탐색 속성에 속성을 적용 할 수 있음을 발견했습니다 . 부모 및 자식 엔터티를 모두 직렬화하지만 자체 참조 루프를 피할뿐입니다.


질문이 꽤 오래되었다는 것을 알고 있지만 여전히 인기가 있으며 ASP.net Core에 대한 솔루션을 볼 수 없습니다.

의 I 경우 ASP.net 코어 , 당신은 새로운 추가 할 필요가 JsonOutputFormatter에서 Startup.cs파일 :

    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();

Would load Employees and related to them Departments.

After setting ReferenceLoopHandling to Ignore, Departments will be set to null unless you include it in your query:

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

Also, keep in mind that it will clear all OutputFormatters, if you don't want that you can try removing this line:

options.OutputFormatters.Clear();

But removing it causes again self referencing loop exception in my case for some reason.


The main problem is that serializing an entity model which has relation with other entity model(Foreign key relationship). This relation causes self referencing this will throw exception while serialization to json or xml. There are lots of options. Without serializing entity models by using custom models.Values or data from entity model data mapped to custom models(object mapping) using Automapper or Valueinjector then return request and it will serialize without any other issues. Or you can serialize entity model so first disable proxies in entity model

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

To preserve object references in XML, you have two options. The simpler option is to add [DataContract(IsReference=true)] to your model class. The IsReference parameter enables oibject references. Remember that DataContract makes serialization opt-in, so you will also need to add DataMember attributes to the properties:

[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;}
}

In Json format in global.asax

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

in xml format

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

The message error means that you have a self referencing loop.

The json you produce is like this example (with a list of one employee) :

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

]

You have to tell the db context that you don't want to get all linked entities when you request something. The option for DbContext is Configuration.LazyLoadingEnabled

The best way I found is to create a context for serialization :

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

Add a line Configuration.ProxyCreationEnabled = false; in constructor of your context model partial class definition.

    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)
    {
    }
}

I only had one model i wanted to use, so i ended up with the following code:

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

I just had the same issue on my .net core site. The accepted answer didn't work for me but i found that a combination of ReferenceLoopHandling.Ignore and PreserveReferencesHandling.Objects fixed it.

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

I might also look into adding explicit samples for each controller/action, as well covered here:

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

i.e. config.SetActualResponseType(typeof(SomeType), "Values", "Get");


self-referencing as example

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

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);

If you are trying to change this setting in the Blazor (ASP.NET Core Hosted) template, you need to pass the following to the AddNewtonsoftJson call in Startup.cs in the Server project:

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

참고URL : https://stackoverflow.com/questions/19467673/entity-framework-self-referencing-loop-detected

반응형