【问题标题】:Include property but exclude one of that property's properties包括属性但排除该属性的属性之一
【发布时间】:2016-12-13 10:29:48
【问题描述】:

假设我的一个控制器中有这样的方法:

[Route("api/Products")]
public IQueryable<Product> GetProducts() {
    return db.Products
             .Include(p => p.Category);
}

使用它,我可以从数据库中获取产品并包含其 Category 属性。

在我的 CategoryController 我有这个方法:

[Route("api/Categories")]
public IQueryable<Category> GetCategories() {
    return db.Categories
             .Include(c => c.Parent)
             .Include(c => c.Products)
             .Include(c => c.SubCategories);
}

当我向 CategoryController 发送一个 GET 请求时,它按预期工作,我得到了类别、它的父类别、它的产品和它的子类别。但是当我向 ProductController 发送 GET 请求时,我不想将所有产品都包含在所请求产品的类别中,我只需要有关该类别的基本信息。

那么,如何让 GetProducts() 返回数据库中的产品,包括每个产品的 Category 属性,但不包括该类别的 Products list 属性,仍然保留 id、title 等其他属性?

谢谢。

【问题讨论】:

  • 这看起来更像是 LINQ-to-SQL 或实体框架问题,而不是 ASP.NET 问题。请修改标签并为您实际使用的 ORM 添加标签。
  • @Heinzi 好电话,更新了标签。
  • 你能关闭延迟加载并使用急切加载吗?
  • @Michael 可能,但我宁愿不要。如果没有其他解决方案出现,我会记住这一点作为替代方案。编辑:等等,我很确定我已经在使用急切加载?
  • 请记住,当您的IQueryable&lt;Product&gt; 被序列化时,它将被迭代,并且所有属性都将被“触及”。如果您开启了延迟加载,您将为所有关系属性(外键)调用数据库。

标签: c# entity-framework asp.net-web-api2


【解决方案1】:

正如 cmets 中所说,第一步是禁用延迟加载。您可以通过从集合属性中删除 virtual 修饰符来做到这一点,这是永久的,或者通过在每个上下文实例中禁用它,这是临时的:

context.Configuration.ProxyCreationEnabled = false;

(禁用代理创建也会禁用延迟加载,但会保持生成的对象更轻量级)。

在断开连接的场景中,例如 Web API,人们通常更喜欢默认禁用延迟加载,因为这种序列化器延迟加载级联。

但是,您无法阻止 Entity Framework 执行关系修复。加载 Product 会将其附加到上下文中。 Include()-ing 其类别将 那些 附加到上下文中,并且 EF 使用附加的产品填充其 Products 集合,无论您是否喜欢。循环引用仍然是个问题。

您可以通过使用AsNoTracking 获取产品来在一定程度上减少这种影响(这可以防止实体被附加,即更改跟踪):

return db.Products.AsNoTracking()
         .Include(p => p.Category);

现在类别将仅将其Products 填充为它们所属类别的Product

顺便说一句,在断开连接的情况下,也首选使用AsNoTracking。无论如何,实体永远不会被同一个上下文实例保存,它会提高性能。

解决方案

  • 返回 DTO,而不是实体类型

通过使用 DTO 对象,您可以完全控制将被序列化的对象图。延迟加载不会让您感到惊讶。但是,是的,所需的 DTO 类的数量可能非常庞大。

  • 返回匿名类型。

这会引起一些人的注意,因为我们永远不应该从方法中返回匿名类型,对吧?好吧,他们将操作方法​​保留为 Json 字符串,就像命名类型一样,javascript 客户端不知道区别。你可能会说它只是让弱类型的 javascript 环境更近了一步。唯一的一点是,命名的 DTO 类型用作(某种)数据协定,并且匿名类型也可以很容易地更改并破坏客户端代码。

  • 调整序列化程序。

您可以告诉 Json.Net 序列化程序忽略引用循环。直接使用JsonConvert,看起来是这样的:

var products = db.Products.AsNoTracking().Include(p => p.Category);
var setting = new JsonSerializerSettings
{
    Formatting = Newtonsoft.Json.Formatting.Indented, // Just for humans
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var json = JsonConvert.SerializeObject(products, setting);

结合AsNoTracking(),这将序列化具有空Products数组的类别("Products": []),因为Product - Category - Product是一个引用循环。

在 Web API 中有几种方法可以配置内置的 Json.Net 序列化程序,您可能想要这样做per action method

就个人而言,我更喜欢使用 DTO。我喜欢控制(也可以控制跨线的属性),我不特别喜欢依靠序列化程序来解决我忽略的事情。

【讨论】:

  • 感谢您的出色回答。我将AsNoTracking() 方法与我已经在使用的ReferenceLoopHandling = ReferenceLoopHandling.Ignore 结合使用。如果我需要更多控制,我可能会切换到 DTO,但现在 AsNoTracking() 似乎工作得很好。我将暂缓提供赏金,因为我很确定是否会发布任何其他答案,但我怀疑你最终会得到它。再次感谢您。
【解决方案2】:

我认为这不是最佳做法,但它会起作用,您可以创建新对象并填充它。

[Route("api/Products")]
public IQueryable<Product> GetProducts() {
    return db.Products
             .Include(p => p.Category);
             .Select(x => new Product{
                              Name = x.Name,
                              Price = x.Price,
                              Category = new Category{
                                          Name = x.Category.Name}})
}

【讨论】:

  • 仅适用于 EF 核心。 EF6 经典不允许在 LINQ 查询中创建实体类。旁注:Include 在这里是多余的。它将被忽略。
  • 感谢您的启发性回答,我也学到了一些东西
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
  • 1970-01-01
  • 2011-10-15
  • 2022-01-17
  • 2020-09-28
  • 2014-06-12
  • 1970-01-01
相关资源
最近更新 更多