【发布时间】:2017-02-12 15:42:24
【问题描述】:
我正在为购物车进行大型数据库调用。它包括许多关系,都使用.Include() 方法指定。
现在我只希望 EF 包含我指定的内容。当我包含一个集合时,它会自动加载集合的关系。
所以我有一个ShoppingCart,购物车有一个集合,如果ShoppingCartProducts 并且该集合与ShoppingCart 和Product 有关系。
所以我想再次包含产品,而不是购物车,所以我这样做了:
IQueryable<ShoppingCart> query = DbContext.ShoppingCarts
.Include(p => p.ShoppingCartProducts)
.Include(p => p.ShoppingCartProducts.Select(x => x.Product))
稍后我执行.FirstOrDefault() 来执行查询。通过这个调试,它还在每个ShoppingCartProducts中包含了ShoppingCart。
这听起来有点小,但实际上在整个应用程序中都是这样。新架构将实体对象转换为具有不同静态方法和扩展的模型。最终导致 StackoverflowException,因为它递归地包含了它的关系。
那么我如何仅包含我已包含的内容?
我已将 LazyLoadingEnabled 设置为 false,并将 ProxyCreationEnabled 设置为 false。而且我的收藏/回复没有标有virtual。
检查了这些答案:
DBContext lazyloadingenabled set to true still loads related entities by default 关于集合的包含确实如此,但是一旦包含集合,该集合将加载所有其他关系(我猜)
Entity Framework with Proxy Creation and Lazy Loading disabled is still loading child objects 几乎相同的问题,但不是一个好的答案,只是一个解释
EF 6 Lazy Loading Disabled but Child Record Loads Anyway 使用分离并没有帮助。
编辑:
正如 Evk 所提到的,这与 EF 自动填充已知关系的空白有关。现在的问题实际上是如何关闭它。
编辑 2:
因此,在 Evk 的回答和我自己的解决方法之后,我们了解到这些解决方案并不能解决全局问题。让我试着解释一下:
这些扩展和ConvertToModel 方法在每个存储库中实现,并在与它有关系时相互调用。这个概念实际上很棒:如果你有关系,只需转换为模型,如果你没有,不要做任何事情。然而,由于这个 EF“错误”,我了解到所有已知的关系都插入到任何地方。
以下是我们的解决方案不起作用的示例。对于这种情况,代码将首先为ShoppingCart 调用ConvertToModel,然后是其余的。但当然也可以反过来。
ShoppingCartRepository
public static ShoppingCartModel ConvertToModel(ShoppingCart entity)
{
if (entity == null) return null;
ShoppingCartModel model = new ShoppingCartModel
{
Coupons = entity.ShoppingCardCoupons?.SelectShoppingCouponModel(typeof(ShoppingCart)),
Products = entity.ShoppingCartProducts?.SelectShoppingCartProductModel(typeof(ShoppingCart)),
};
return model;
}
ShoppingCartProductRepository
public static IEnumerable<ShoppingCartProductModel> SelectShoppingCartProductModel(this IEnumerable<ShoppingCartProduct> source, Type objSource = null)
{
bool includeRelations = source.GetType() != typeof(DbQuery<ShoppingCartProduct>);
return source.Select(x => new ShoppingCartProductModel
{
ShoppingCart = includeRelations && objSource != typeof(ShoppingCart) ? ShoppingCartRepository.ConvertToModel(x.ShoppingCart) : null,
ShoppingCartCoupons = includeRelations && objSource != typeof(ShoppingCartCoupon) ? x.ShoppingCartCoupons?.SelectShoppingCouponModel(typeof(ShoppingCartProduct)) : null,
});
}
ShoppingCartCouponRepository
public static IEnumerable<ShoppingCartCouponModel> SelectShoppingCouponModel(this IEnumerable<ShoppingCartCoupon> source, Type objSource = null)
{
bool includeRelations = source.GetType() != typeof(DbQuery<ShoppingCartCoupon>);
return source.Select(x => new ShoppingCartCouponModel
{
ShoppingCart = includeRelations && objSource != typeof(ShoppingCart) ? ShoppingCartRepository.ConvertToModel(x.ShoppingCart) : null,
ShoppingCartProduct = includeRelations && objSource != typeof(ShoppingCartProductModel) ? ShoppingCartProductRepository.ConvertToModel(x.ShoppingCartProduct) : null
});
}
当你研究它时,你会发现它可以从ShoppingCart 到ShoppingCartProduct 到ShoppingCartCoupon 再到ShoppingCart。
我目前的解决方法是找出聚合根并选择哪一个需要哪一个。但我宁愿有一个优雅的解决方案来解决这个问题。最好的办法是阻止 EF 加载那些已知的关系,或者以某种方式确定属性是否以这种方式加载(反射?)。
【问题讨论】:
-
在您的情况下,EF 不会从数据库中加载相关实体。对于每个 ShoppingCartProduct,相关的 ShoppingCart 是已知的 - 这就是您要查询的购物车。此 ShoppingCart 已经加载到上下文中,这就是 EF 填充 ShoppingCartProduct 的 ShoppingCart 属性的原因 - 它已经加载并且已知并且无需加载任何成本。不过,我不知道如何以某种方式禁用这种(相当合理的)行为。
-
好点,这有助于理解“问题”。更新了答案。
-
我认为您将不得不修改您的扩展方法,以通过跟踪您已经访问过的实体并忽略它们(不转换为模型)来防止堆栈溢出。或者不忽略,而是像 EF 一样做 - 替换您已经转换为模型的实体。
-
@Evk,你有什么好的建议吗?
-
您介意使用 AutoMapper 吗?除了解决您的问题之外,它还可以使您的预测更有效率。
标签: c# entity-framework