仅包含实体框架

时间:2017-02-12 15:42:24

标签: c# entity-framework

我正在为购物车做一个大数据库调用。它包含许多关系,均使用.Include()方法指定。

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

所以我有一个ShoppingCart,购物车有ShoppingCartProducts的集合,而且该集合的关系可以追溯到ShoppingCartProduct

所以我想要再包含产品,但不包括购物车,所以我这样做:

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

稍后我执行一个执行查询的.FirstOrDefault()。通过此调试,每个ShoppingCart中都包含ShoppingCartProducts

这听起来有点小,但在整个应用程序中实际上都是这样。新架构将实体对象转换为具有不同静态方法和扩展的模型。最终导致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&#39; bug&#39;我知道所有已知的关系到处插入。

以下是我们的解决方案无法运作的示例。对于这种情况,代码首先会为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
        });
    }

当你研究它时,你会发现它可以从ShoppingCartShoppingCartProduct再到ShoppingCartCoupon回到ShoppingCart

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

2 个答案:

答案 0 :(得分:1)

正如评论中所述,实体框架的默认行为,我认为它无法改变。相反,您可以更改代码以防止堆栈溢出异常。如何很好地完成这项工作非常依赖于您的代码库,但我会提供一个草图。在上面的草图中,我使用了其他实体名称(因为我总是检查我的代码是否至少在编译之前进行了编译):

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方法并且此线程局部变量不为null - 这意味着此方法已被递归调用。样品:

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方法中实现此功能,则无需传递任何参数或更改代码的其他部分。

答案 1 :(得分:0)

此解决方案检查源对象类型是否不等于我们调用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,
        });
    }
}

然而,这可能无法解决整个问题。如果你有另一个实体,让我们在AdditionalCosts中说ShoppingCart,它也有ShoppingCartProduct的引用,它仍会'旋转'。 如果某人有解决方案,那就太棒了!

ShoppingCart - &gt; ConvertToModel(shoppingCart) - &gt; SelectAdditionalCostsModel - &gt; ShoppingCartProduct - &gt; ConvertToModel(shoppingCartProduct) - &gt; ShoppingCart - &gt; ConvertToModel(shoppingCart)。等等..