2017-02-12 135 views
1

我正在做一個購物車的大數據庫調用。它包含許多關係,所有關係都使用.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

這聽起來有點小,但實際上在整個應用程序中都是這樣。新體系結構將實體對象轉換爲具有不同靜態方法和擴展的模型。最終導致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'錯誤',我知道所有已知的關係到處都是。

以下是我們的解決方案無法正常工作的示例。對於這種情況,代碼將首先調用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

回答

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方法,並且這個線程局部變量不爲空 - 這意味着這個方法已被遞歸調用。示例:

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

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

然而,這可能不解決整個問題。如果你有另外一個實體,比如說AdditionalCostsShoppingCart之內,那也可以參考ShoppingCartProduct,它仍然會「旋轉」。 如果有人有這個解決方案,這將是偉大的!

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