让我说我的一个控制器中有这样的方法:
[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列表属性,仍然保留其他属性,如id,title等?
谢谢。
答案 0 :(得分:9)
如评论中所述,第一步是禁用延迟加载。您可以通过从集合属性中删除virtual
修饰符来执行此操作,这是永久性的,或者通过每个上下文实例禁用它,这是临时的:
context.Configuration.ProxyCreationEnabled = false;
(禁用代理创建也会禁用延迟加载,但会使生成的对象保持更轻量级。)
在断开连接的场景中,例如Web AIP2(正如您最初标记问题的那样),人们通常更喜欢默认禁用延迟加载,因为此序列化器 - 延迟加载级联。
但是,您无法阻止Entity Framework执行关系修正 。加载Product
会将其附加到上下文中。 Include()
- 其类别会将那些附加到上下文中,并且EF会使用附加的产品填充其Products
个集合,无论您是否喜欢。
通过使用AsNoTracking
获取产品(可防止实体附加,即更改跟踪),您可以在某种程度上减少此影响:
return db.Products.AsNoTracking()
.Include(p => p.Category);
现在,类别只会将Products
填充为Product
的类别。
顺便说一下,在断开连接的场景中,首选使用AsNoTracking
也是首选。无论如何,这些实体都不会被相同的上下文实例保存,并且会提高性能。
通过使用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。我喜欢控制(也是通过电线的属性)我并不特别喜欢依靠序列化器为我解决我忽略的事情。