在当前项目中,我首先使用WebAPI和EntityFramework模型。经过一番调查,看来我必须使用LazyLoading来加载相关实体。

我在许多博客中读到,在服务中使用LazyLoading可能会导致性能和序列化问题-因此,我希望尽可能避免这种情况。

关于如何在不创建POCO对象的情况下实现此目标的任何建议?

最佳答案

您问题的简单答案是-使用.Include()

例如:假设您有一个Customer对象,该对象具有关联的ReferredBy对象,该对象引用了另一个客户。在您的应用程序中,您想返回一个Customer列表以及引用每个引用列表的关联Customer

您的WebAPI方法可能类似于:

[HttpGet]
public IQueryable<Customer> Customers() {
    return db.Customers.OrderBy(c => c.LastName).Top(10);
}

当序列化程序意识到这一点时,您很容易遇到各种错误,您可以阅读有关in this article的更多信息。从本质上讲,它来自两件事:
  • ProxyCreation / LazyLoading-用EF来表示“仅在我需要时才按需加载我的对象图的关联对象”,以及
  • 循环的序列化-这意味着-A指的是B,而B指的是A-因此,每次我序列化一个序列时,我都会再次序列化另一个作为其子级。这将创建一个无限循环。

  • 我不会涉及所有细节或其他问题-我已经给您提供了一篇深入探讨它的文章。相反,这是我在应用程序中解决问题的方法:
  • 使用JSON.net作为序列化器。您可以参考this link以获取有关如何在Visual Studio中将其设置为项目的默认序列化程序的说明(假设尚未设置)
  • 在配置中加载的Global.asax.cs文件之一中,使用以下设置:
    config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
        Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    

    这告诉JSON.net不要序列化引用循环。
  • MergeOption中的DataContext及其每个Collection属性设置为MergeOption.NoTracking。在我的应用程序中,我通过编辑创建.ttDataContext文件来做到这一点:

    首先,找到创建构造函数的行,然后更改为:
    MergeOption _defaultMergeOption = MergeOption.AppendOnly;
    
    public <#=code.Escape(container)#>() : this("name=<#=container.Name#>") { }
    public <#=code.Escape(container)#>(String connectionString) : base(connectionString) {
    <# if (!loader.IsLazyLoadingEnabled(container)) { #>
        this.Configuration.LazyLoadingEnabled = false;
    <# } #>
        this.Configuration.ProxyCreationEnabled = false;
        _defaultMergeOption = MergeOption.NoTracking;
    }
    

    查找以以下内容开头的行:
    <#  foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>()) { #>
    

    现在,编辑以下几行内容:
    <#  foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>()) { #>
    <#= Accessibility.ForReadOnlyProperty(entitySet)#> ObjectQuery<<#=typeMapper.GetTypeName(entitySet.ElementType)#>> <#=code.Escape(entitySet)#> {
        get {
            var set = (((IObjectContextAdapter)this).ObjectContext).CreateObjectSet<<#=typeMapper.GetTypeName(entitySet.ElementType)#>>();
            set.MergeOption = _defaultMergeOption;
    
            return set;
        }
    }
    <#  }
    

    这样做是为您的DataContext提供了一个构造函数,该构造函数可以将所有集合默认设置为MergeOption.NoTracking,并自动禁用ProxyCreationLazyLoading。当您创建要从中提取的DataContext实例时,现在可以简单地说:
    var db = new MyDataContext("ConnectionStringGoesHere");
    

  • 鉴于以上所述,您的新WebAPI方法变得非常简单:
    [HttpGet]
    public IQueryable<Customer> Customers() {
        return db.Customers.Include("ReferredBy")
                 .OrderBy(c => c.LastName).Top(10);
    }
    
    .Include()将加​​载子记录作为初始SQL语句的一部分(总共命中数据库一次),而Serializer将忽略反向引用,从而使您可以生成类似于以下内容的JSON:
    [{
        Id: 1,
        FirstName: 'Frank',
        LastName: 'Abba',
        ReferredBy: {
            Id: 4,
            FirstName: 'Bob',
            LastName: 'Jones',
            ReferredBy: null
        }
     }, {
        Id: 4,
        FirstName: 'Bob',
        LastName: 'Jones',
        ReferredBy: null
     }
    }]
    

    08-06 19:30