只包含内容实体框架

问题描述:

我正在做一个购物车的大数据库调用。它包含许多关系,所有关系都使用.Include()方法指定。只包含内容实体框架

现在我只想要EF包含我指定的内容。当我包含一个集合时,它会自动加载集合的关系。

所以我有一个ShoppingCart,购物车有一个集合,如果ShoppingCartProducts和那个有关系回到ShoppingCartProduct

所以我想包括产品,但不购物车了,所以我做的:

IQueryable<ShoppingCart> query = DbContext.ShoppingCarts 
         .Include(p => p.ShoppingCartProducts) 
         .Include(p => p.ShoppingCartProducts.Select(x => x.Product)) 

后来我执行.FirstOrDefault()其执行查询。通过这个调试,它还包括每个ShoppingCartProducts中的ShoppingCart

这听起来有点小,但实际上在整个应用程序中都是这样。新体系结构将实体对象转换为具有不同静态方法和扩展的模型。最终导致*Exception,因为它递归地包含它的关系。

那么我如何只有包括我所包含的内容?

我已将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'错误',我知道所有已知的关系到处都是。

以下是我们的解决方案无法正常工作的示例。对于这种情况,代码将首先调用ConvertToModel,然后再调用ShoppingCart。但当然它可能是反之亦然。

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 
     }); 
    } 

当你学习它时,你会发现它可以从ShoppingCartShoppingCartProductShoppingCartCoupon回到ShoppingCart

我目前的解决方法是找出聚合根,并选择哪一个需要哪一个。但我宁愿有一个优雅的解决方案来解决这个问题。最好的做法是防止EF加载这些已知关系,或者以某种方式确定某个属性是否以这种方式加载(反射?)。

+0

EF在您的情况下不会从数据库加载相关实体。对于每个ShoppingCartProduct,相关的ShoppingCart都是已知的 - 这就是您正在查询的购物车。这个ShoppingCart已经加载到上下文中,这就是EF填充ShoppingCartProduct的ShoppingCart属性的原因 - 它已经被加载并且已知并且无需加载任何东西。我不知道有什么办法来禁用这种(相当合理的)行为。 – Evk

+0

好点,这有助于理解'问题'。更新了答案。 – CularBytes

+0

我认为你将不得不修改你的扩展方法,通过跟踪你已经访问过的实体并忽略它们(不转换为模型)来防止堆栈溢出。或者不要忽略,但要像EF那样做 - 用你已经转换成模型的实体代替。 – Evk

正如评论所述,这是实体框架的默认行为,我不认为它可以改变。相反,您可以更改您的代码以防止出现堆栈溢出异常。如何做到这一点非常依赖于您的代码库,但我会提供一个草图。在草图上面我用其他实体的名字(因为我经常检查,如果我的代码样本这里张贴之前至少编译):

public static partial class Ex { 
    public static CodeModel ConvertToModel(Code entity) { 
     if (entity == null) return null; 
     CodeModel model = new CodeModel(); 
     var map = new Dictionary<object, object>(); 
     map.Add(entity, model); 
     model.Errors = entity.Errors?.SelectShoppingCartProductModel(map); 
     return model; 
    }   

    public static ErrorModel[] SelectShoppingCartProductModel(this IEnumerable<Error> source, Dictionary<object, object> map = null) { 
     bool includeRelations = source.GetType() != typeof(DbQuery<Error>); //so it doesn't call other extensions when we are a db query (linq to sql) 
     return source.Select(x => new ErrorModel { 
      Code = includeRelations ? (map?.ContainsKey(x.Code) ?? false ? (CodeModel) map[x.Code] : ConvertToModel(x.Code)) : null, 
      // other such entities might be here, check the map 
     }).ToArray(); 
    } 
} 

另一种选择是存储在线程局部变量的当前模型。如果你调用一些ConvertToModel方法,并且这个线程局部变量不为空 - 这意味着这个方法已被递归调用。示例:

public static partial class Ex { 
    private static readonly ThreadLocal<CodeModel> _code = new ThreadLocal<CodeModel>(); 
    public static CodeModel ConvertToModel(Code entity) { 
     if (entity == null) return null; 
     if (_code.Value != null) 
      return _code.Value; 

     CodeModel model = new CodeModel(); 
     _code.Value = model; 
     model.Errors = entity.Errors?.SelectShoppingCartProductModel(); 
     // other setters here 
     _code.Value = null; 
     return model; 
    } 

    public static ErrorModel[] SelectShoppingCartProductModel(this IEnumerable<Error> source) { 
     bool includeRelations = source.GetType() != typeof(DbQuery<Error>); //so it doesn't call other extensions when we are a db query (linq to sql) 
     return source.Select(x => new ErrorModel { 
      Code = includeRelations ? ConvertToModel(x.Code) : null, 
     }).ToArray(); 
    } 
} 

如果实施该在所有ConvertToModel方法 - 没有必要通过任何参数或更改代码的其他部分。

+0

dayum,很好,但我希望有一个更优雅的解决方案,避免发送额外的参数。我现在发送对象的类型并使用它来查看它是否相同。我不确定,但两种解决方案(你的和我的)似乎只解决1关系的问题? – CularBytes

+0

那么您可以将该地图传递给执行转化的所有方法。您也可以将所有转换方法合并到一个非静态类中,并将该地图存储在该类的专用字段中。这样不需要额外的参数。 – Evk

+0

@CularBytes更新了一个答案。 – Evk

该解决方案检查源对象类型是否与我们调用ConvertToModel的对象类型不相等。

public static ShoppingCartModel ConvertToModel(ShoppingCart entity) 
{ 
    if (entity == null) return null; 
    ShoppingCartModel model = new ShoppingCartModel 
    { 
     ... 
     Products = entity.ShoppingCartProducts?.SelectShoppingCartProductModel(typeof(ShoppingCart)), 
    }; 
    return model; 
} 

SelectShoppingCartProductModel扩展:

public static partial class Ex 
{ 
    public static IEnumerable<ShoppingCartProductModel> SelectShoppingCartProductModel(this IEnumerable<ShoppingCartProduct> source, Type objSource = null) 
    { 
     bool includeRelations = source.GetType() != typeof(DbQuery<ShoppingCartProduct>);//so it doesn't call other extensions when we are a db query (linq to sql) 
     return source.Select(x => new ShoppingCartProductModel 
     { 
      .... 
      ShoppingCart = includeRelations && objSource != typeof(ShoppingCart) ? ShoppingCartRepository.ConvertToModel(x.ShoppingCart) : null, 
     }); 
    } 
} 

然而,这可能不解决整个问题。如果你有另外一个实体,比如说AdditionalCostsShoppingCart之内,那也可以参考ShoppingCartProduct,它仍然会“旋转”。 如果有人有这个解决方案,这将是伟大的!

ShoppingCart - >ConvertToModel(shoppingCart) - >SelectAdditionalCostsModel - >ShoppingCartProduct - >ConvertToModel(shoppingCartProduct) - >ShoppingCart - >ConvertToModel(shoppingCart)。依此类推。