Đã phát hiện vòng lặp tự tham chiếu - Lấy lại dữ liệu từ WebApi về trình duyệt


80

Tôi đang sử dụng Entity Framework và gặp sự cố khi tải dữ liệu cha và con vào trình duyệt. Đây là các lớp học của tôi:

 public class Question
 {
    public int QuestionId { get; set; }
    public string Title { get; set; }
    public virtual ICollection<Answer> Answers { get; set; }
}

public class Answer
{
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public virtual Question Question { get; set; }
}

Tôi đang sử dụng mã sau để trả về dữ liệu câu hỏi và câu trả lời:

    public IList<Question> GetQuestions(int subTopicId, int questionStatusId)
    {
        var questions = _questionsRepository.GetAll()
            .Where(a => a.SubTopicId == subTopicId &&
                   (questionStatusId == 99 ||
                    a.QuestionStatusId == questionStatusId))
            .Include(a => a.Answers)
            .ToList();
        return questions; 
    }

Về phía C #, điều này có vẻ hoạt động tuy nhiên tôi nhận thấy rằng các đối tượng trả lời có tham chiếu trở lại câu hỏi. Khi tôi sử dụng WebAPI để tải dữ liệu vào trình duyệt, tôi nhận được thông báo sau:

Loại 'ObjectContent`1' không thể tuần tự hóa phần thân phản hồi cho loại nội dung 'application / json; charset = utf-8 '.

Đã phát hiện vòng lặp tự tham chiếu cho thuộc tính 'câu hỏi' với loại 'Models.Core.Question'.

Điều này có phải vì Câu hỏi có Câu trả lời và các Câu trả lời có tham chiếu trở lại Câu hỏi không? Tất cả những nơi tôi đã xem đều gợi ý rằng có tham chiếu đến cha mẹ của đứa trẻ nên tôi không chắc phải làm gì. Ai đó có thể cho tôi một số lời khuyên về điều này.


6
Sử dụng dto cho api web của bạn, tránh trở Entity trực tiếp trong reaponse của bạn
Cuongle

Dto là gì? Toàn bộ ứng dụng của chúng tôi sử dụng EF, chúng tôi đang sử dụng AngularJS trên máy khách và chúng tôi không gặp vấn đề gì ngoài trường hợp này.

1
Ý tôi là bạn nên xác định Dto của mình cho Api Web của bạn, Dto tương tự như với ViewModel trong MVC. Dto giống như một trình bao bọc trong mô hình EF của bạn để cung cấp dữ liệu cho khách hàng của bạn (anglejs).
cuongle


Bạn có thể xem câu trả lời của tôi về ngoại lệ “Đã phát hiện vòng lặp tự tham chiếu” với trang JSON.Net .
Murat Yıldız

Câu trả lời:


73

Điều này có phải vì Câu hỏi có Câu trả lời và các Câu trả lời có tham chiếu trở lại Câu hỏi không?

Đúng. Nó không thể được nối tiếp.

CHỈNH SỬA: Xem câu trả lời của Tallmaris và bình luận của OttO vì nó đơn giản hơn và có thể được đặt trên toàn cầu.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌​ferenceLoopHandling = ReferenceLoopHandling.Ignore;

Câu trả lời cũ:

Chiếu đối tượng EF lên đối tượng Questiontrung gian hoặc DataTransferObject của riêng bạn. Dto này sau đó có thể được tuần tự hóa thành công.

public class QuestionDto
{
    public QuestionDto()
    {
        this.Answers = new List<Answer>();
    } 
    public int QuestionId { get; set; }
    ...
    ...
    public string Title { get; set; }
    public List<Answer> Answers { get; set; }
}

Cái gì đó như:

public IList<QuestionDto> GetQuestions(int subTopicId, int questionStatusId)
{
    var questions = _questionsRepository.GetAll()
        .Where(a => a.SubTopicId == subTopicId &&
               (questionStatusId == 99 ||
                a.QuestionStatusId == questionStatusId))
        .Include(a => a.Answers)
        .ToList();

    var dto = questions.Select(x => new QuestionDto { Title = x.Title ... } );

    return dto; 
}

3
Tôi muốn nói thêm, đối với tôi, thiết lập ReferenceLoopHandling.Ignore không hoạt động, đặt nó toàn cầu hoặc khi khởi động API hoàn toàn không hoạt động. Tôi đã cố gắng làm cho nó hoạt động bằng cách trang trí thuộc tính điều hướng của lớp con với [JsonIgnore]. Tôi vẫn nhận được ParentId nhưng điều hướng Parent bị bỏ qua trong khi nối tiếp.
Claiton Lovato,

Xin chào, Bỏ qua tuần tự hóa sẽ phá vỡ sự phụ thuộc vòng tròn Câu hỏi> Câu trả lời> Câu hỏi. Phương pháp DTO có bảo tồn nó không?
Bartosz

Tôi gặp sự cố này trong một dự án ASP.NET MVC cũ. GlobalConfiguration.Configuration không có Formatters. Bạn có thể đề nghị những gì có thể được thực hiện cho điều đó?
Ren

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌ ferenceLoopHandling = ReferenceLoopHandling.Ignore; -> Đặt dòng mã này ở đâu ???
anhtv13 13/05

56

Bạn cũng có thể thử điều này trong Application_Start():

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;

Nó sẽ khắc phục sự cố của bạn mà không phải trải qua nhiều vòng.


CHỈNH SỬA: Theo bình luận của OttO bên dưới, sử dụng: ReferenceLoopHandling.Ignorethay thế.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

78
Tôi biết đây là một chủ đề cũ nhưng đối với những người tình cờ gặp nó trong tương lai, hãy thử: GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
OttO

@OttO, đề xuất của bạn hiệu quả với tôi. Cảm ơn rất nhiều.
J86,

2
Mã đi vào vòng lặp vô hạn và hiển thị ngoại lệ tràn ngăn xếp sau khi thêm dòng này.
Nhà phát triển Microsoft

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; hoạt động tốt hơn
Dragos Durlut

@Demodave, bạn phải tạo JsonSerializer của mình bằng Create()phương thức tĩnh chấp nhận cài đặt làm tham số. Documents: newtonsoft.com/json/help/html/...
Tallmaris

21

Nếu sử dụng OWIN, hãy nhớ rằng, không có GlobalSettings nào khác dành cho bạn! Bạn phải sửa đổi cài đặt tương tự này trong đối tượng HttpConfiguration được chuyển đến hàm IAppBuilder UseWebApi (hoặc bất kỳ nền tảng dịch vụ nào bạn đang sử dụng)

Sẽ trông giống như thế này.

    public void Configuration(IAppBuilder app)
    {      
       //auth config, service registration, etc      
       var config = new HttpConfiguration();
       config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
       //other config settings, dependency injection/resolver settings, etc
       app.UseWebApi(config);
}

1
Bạn đã cứu ngày của tôi. Tôi đã tự hỏi tại sao câu trả lời ở trên không hoạt động. Có, sử dụng cài đặt OWIN trong Global.asax sẽ không hoạt động.
Sithu

21

Trong ASP.NET Core, cách khắc phục như sau:

services
.AddMvc()
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

5

Nếu sử dụng DNX / MVC 6 / ASP.NET vNext blah blah, thậm chí còn HttpConfigurationthiếu. Bạn phải định cấu hình bộ định dạng bằng cách sử dụng các mã sau trong Startup.cstệp của mình .

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().Configure<MvcOptions>(option => 
        {
            //Clear all existing output formatters
            option.OutputFormatters.Clear();
            var jsonOutputFormatter = new JsonOutputFormatter();
            //Set ReferenceLoopHandling
            jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            //Insert above jsonOutputFormatter as the first formatter, you can insert other formatters.
            option.OutputFormatters.Insert(0, jsonOutputFormatter);
        });
    }

1
Trong asp-net rc-1-final, tôi tin rằng đó là "services.Configure <MvcOptions>" ngay bây giờ
Michał W.

JsonOutputFormatter nằm trong không gian tên Microsoft.AspNet.Mvc.Formatters
Sam

2
Đối với .NET Core 1.0 RTM: new JsonOutputFormatter (serializerSettings, ArrayPool <char> .Shared);
aherrick

5

ASP.NET Core Web-API (.NET Core 2.0):

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.Configure<MvcJsonOptions>(config =>
    {
        config.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    });
}

2

Sử dụng cái này:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore

không hiệu quả với tôi. Thay vào đó, tôi đã tạo một phiên bản mới, đơn giản hóa của lớp mô hình của mình chỉ để kiểm tra và điều đó trở lại tốt. Bài viết này đi sâu vào một số vấn đề tôi gặp phải trong mô hình hoạt động tốt cho EF, nhưng không thể tuần tự hóa:

http://www.asp.net/web-api/overview/data/using-web-api-with-entity-framework/part-4


1

ReferenceLoopHandling.Ignore không phù hợp với tôi. Cách duy nhất tôi có thể làm là xóa thông qua mã các liên kết trở lại trang gốc mà tôi không muốn và giữ lại những liên kết tôi đã làm.

parent.Child.Parent = null;

1

Đối với Ứng dụng Web Asp.Net mới sử dụng .Net Framework 4.5:

Web Api: Goto App_Start -> WebApiConfig.cs:

Sẽ giống như sau:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

        // ReferenceLoopHandling.Ignore will solve the Self referencing loop detected error
        config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

        //Will serve json as default instead of XML
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

1

Là một phần của ASP.NET Core 3.0, theo mặc định, nhóm đã loại bỏ Json.NET. Bạn có thể đọc thêm về điều đó nói chung trong [Bao gồm Json.Net đến netcore 3.x] [1] https://github.com/aspnet/Anosystemments/issues/325

Lỗi có thể do bạn sử dụng lazyloading: services.AddDbContext (options => options.UseLazyLoadingProxies () ... hoặc db.Configuration.LazyLoadingEnabled = true;

sửa chữa: thêm vào startup.cs

 services.AddControllers().AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        });

0

Do tải chậm, bạn đang gặp lỗi này. Do đó, đề xuất của tôi là xóa khóa ảo khỏi thuộc tính. Nếu bạn đang làm việc với API thì tải chậm sẽ không tốt cho sức khỏe API của bạn.

Không cần thêm dòng bổ sung trong tệp cấu hình của bạn.

public class Question
 {
    public int QuestionId { get; set; }
    public string Title { get; set; }
    public ICollection<Answer> Answers { get; set; }
}

public class Answer
{
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public Question Question { get; set; }
}

0

Tôi nhận thấy lỗi này đã được gây ra khi tôi tạo edmx (tệp XML xác định mô hình khái niệm) của cơ sở dữ liệu hiện có và nó có thuộc tính Điều hướng cho cả bảng mẹ và bảng con. Tôi đã xóa tất cả các liên kết điều hướng đến các đối tượng chính vì tôi chỉ muốn điều hướng đến trẻ em và vấn đề đã được giải quyết.


0

Entities db = new Entities ()

db.Configuration.ProxyCreationEnabled = false;

db.Configuration.LazyLoadingEnabled = false;


0

Bạn có thể tạo động một bộ sưu tập con mới để dễ dàng giải quyết vấn đề này.

public IList<Question> GetQuestions(int subTopicId, int questionStatusId)
    {
        var questions = _questionsRepository.GetAll()
            .Where(a => a.SubTopicId == subTopicId &&
                   (questionStatusId == 99 ||
                    a.QuestionStatusId == questionStatusId))
            .Include(a => a.Answers).Select(b=> new { 
               b.QuestionId,
               b.Title
               Answers = b.Answers.Select(c=> new {
                   c.AnswerId,
                   c.Text,
                   c.QuestionId }))
            .ToList();
        return questions; 
    }

0

Không có cấu hình nào trong các câu trả lời ở trên phù hợp với tôi trong ASP.NET Core 2.2.

Tôi đã thêm các JsonIgnorethuộc tính trên thuộc tính điều hướng ảo của mình.

public class Question
{
    public int QuestionId { get; set; }
    public string Title { get; set; }
    [JsonIgnore]
    public virtual ICollection<Answer> Answers { get; set; }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.