【问题标题】:Entity Framework DB first issues loading related entities (ASP.NET web api)Entity Framework DB 首先加载相关实体 (ASP.NET web api)
【发布时间】:2015-06-01 17:28:05
【问题描述】:

我一直在寻找这个相对简单的任务的答案,但没有成功。所以我想我会在这里问我的问题。我有一个简单的数据库,其中包含两个表,书籍和作者。

我的模型由 ADO.NET 实体数据模型生成。这是自动生成的 Books 模型:

public partial class Book
{
    public int BookID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public int ISBN { get; set; }
    public int AuthorID { get; set; }

    public virtual Author Author { get; set; }
}

这是自动生成的作者模型:

public partial class Author
{
    public Author()
    {
        this.Books = new HashSet<Book>();
    }

    public int AuthorID { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Book> Books { get; set; }
}

这是控制器的一部分,用于获取 JSON 格式的所有书籍列表的方法。

    // api/books
    public IQueryable<Book> GetBooks()
    {
        // return db.Books.Include(x => x.Authors); Don't work
        return db.Books;
    }

这是我调用端点的 JS:

$.getJSON("api/books")
.done(function (data) {
    console.log(data);
})
.fail(function (xhr) { console.log(xhr.responseText) });

没什么特别的,只是尝试发出 GET 请求并接收所有书籍及其相关作者的列表。

这是错误信息的一部分:

{"Message":"An error has occurred.","ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.","ExceptionType":"System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Self referencing loop detected for property 'Author' with type 'System.Data.Entity.DynamicProxies.Author_5968F94A1BBB745A30D62CD59D0AC5F96A198B3F16F0EA2C2F61575F70034886'. Path '[0].Books[0]'.","ExceptionType":"Newtonsoft.Json.JsonSerializationException","StackTrace":"

我尝试在 JSON 中保留对象引用,但这会破坏响应。这是唯一的选择吗?

【问题讨论】:

    标签: c# asp.net json entity-framework


    【解决方案1】:

    如果您检查内部异常,它会说:

    Self referencing loop detected for property 'Author'
    

    这告诉您 Author 类引用回父类(即 Books,反之亦然)。

    在您的 Web api 配置 (App_Start/WebApiConfig.cs) 中,添加以下内容:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Prevent "Self referencing loop detected" error occurring for recursive objects
            var serializerSettings = new JsonSerializerSettings()
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };
            config.Formatters.JsonFormatter.SerializerSettings = serializerSettings;
        }
    }
    

    这告诉 JSON.NET 忽略引用父对象的嵌套对象

    【讨论】:

    • 非常感谢,成功了 :)
    • 花了一段时间试图弄清楚如何在 MVC 6 中做到这一点,所以这里是:在 Startup.ConfigureServices() 函数中,执行以下操作:services.AddMvc().AddJsonOptions(jsonOptions =&gt; { jsonOptions.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; } 这很简单,但我做不到发现它记录在任何地方,所以我想我会把它附加到这个答案上。
    【解决方案2】:

    adaam 的回答是100% 正确,但我想我会提出一些建议,但它太长了,无法放入评论中,所以在这里。

    直接序列化实体框架对象通常不是一个好主意;最好使用简单的 DTO 样式的对象将数据传回给客户端。

    当然这只是建议和 YMMV :)

    使用 DTO 的好处包括

    • 与控制器中的 Entity Framework 对象正确解耦(您可以创建存储库抽象并在控制器中使用它,这意味着您的控制器不依赖于 Entity Framework,因此更易于测试)

    • 更简单的序列化控制 - 使用Entity Framework,当Entity Framework 代理被直接序列化时,您将难以控制通过线路发送给客户端的公共属性;通常在 DB First 中,这些属性的声明位于自动生成的文件中,每次 edmx 更改时都会重写这些文件;因此,必须在不希望通过网络发送的属性(例如[IgnoreDataMember] 等)上维护非序列化属性会变得很痛苦

    • 如果您打算通过POSTPUT 等接受模型,那么您很快就会发现将“向内”直接有效地序列化到Entity Framework 是一种痛苦(如果不是不可能的话)代理,所以无论如何你都必须编写映射代码;通过使用DTO 方法,您接受必须预先映射。

    • 不会发生循环引用,因此您永远不必担心它,更重要的是,您无需支付忽略它的代价(虽然很小,但序列化程序必须完成一些工作避免这些引用)

    • 您可以轻松执行额外的转换,例如展平,以更好地适应客户和/或隐藏您不希望通过网络发送的细节。

    示例

    public class BookDTO
    {
        public int BookID {get;set;}
        public string Title {get;set;}
        public string Description {get;set;}
        public int ISBN {get;set;}
        public string AuthorName{get;set;}
    }
    
    public HttpResponseMessage GetBooks()
    {
      //ideally you'd be using a repository abstraction instead of db directly
      //but I want to keep this simple.
       var books = db.Books.Select(
          book=>new BookDTO(){
             BookID=book.BookID,
             Title=book.Title,
             Description=book.Description,
             ISBN=book.ISBN,
             AuthorName=book.Author.Name //<-flattening
       });
       return Request.CreateResponse(HttpStatusCode.OK, books);
    }
    
    • 这会生成一系列漂亮的扁平对象供客户使用,而无需公开例如AuthorID,这可能是您不特别希望客户知道的内部概念。

    • 一旦掌握了窍门,您就可以考虑使用Automapper 之类的东西,这将大大减少维护负担,并允许您在查询中执行内联投影。

    【讨论】:

      【解决方案3】:

      最好在返回数据时创建自己的自定义模型,但您只想使用实体框架类,然后忽略 refrenceLoopHandeling,您需要从实体模型中禁用 ProxyCreation。

      按照以下步骤操作:

      第1步:像adaam描述的把他的放在WebApiConfig register函数中:

             // Prevent "Self referencing loop detected" error occurring for recursive objects
          var serializerSettings = new JsonSerializerSettings()
          {
              ReferenceLoopHandling = ReferenceLoopHandling.Ignore
          };
          config.Formatters.JsonFormatter.SerializerSettings = serializerSettings;
      

      第 2 步:在我的最新 EF 案例中,从 EF Contaxt 禁用代理最重要。

      Goto: [FILE].edmx file then your [FILE].Context.cs
      

      并在您的构造函数中添加以下行..

      Configuration.ProxyCreationEnabled = false;
      

      现在你不会再有相关的课程结果了..

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-10-10
        • 1970-01-01
        • 1970-01-01
        • 2020-11-16
        • 1970-01-01
        • 1970-01-01
        • 2011-05-15
        • 2023-03-29
        相关资源
        最近更新 更多