簡體   English   中英

實體框架6,急切加載嵌套關系返回空集合

[英]Entity Framework 6, eager loading nested relationship returns empty collection

我有一個看起來像這樣的域模型(剝離了一些不重要的屬性)

public class User
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual int UserId { get; set; }

    private ICollection<Recipe> _recipes;
    public virtual ICollection<Recipe> Recipes
    {
        get { return _recipes ?? new List<Recipe>(); } 
        set { _recipes = value; }
    } 
}

public class Recipe
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int RecipeId { get; set; }

    private ICollection<Ingredient> _ingredients;
    public virtual ICollection<Ingredient> Ingredients
    {
        get { return _ingredients ?? new List<Ingredient>(); } 
        set { _ingredients = value; }
    }
    public User User { get; set; }
}

public class Ingredient
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int IngredientId { get; set; }

    public Recipe Recipe { get; set; }
}

簡而言之, UserRecipe一對多的關系,而RecipeIngredient之間存在一對多的關系。 我正在使用代碼第一種方法,在protected override void OnModelCreating(DbModelBuilder modelBuilder)使用此映射protected override void OnModelCreating(DbModelBuilder modelBuilder)

modelBuilder.Entity<User>()
    .HasMany(u => u.Recipes)
    .WithRequired(u => u.User)
    .WillCascadeOnDelete(true);

modelBuilder.Entity<Recipe>()
    .HasMany(u => u.Ingredients)
    .WithRequired(u => u.Recipe)
    .WillCascadeOnDelete(true);

我正在使用存儲庫,它具有此代碼將數據存儲到MySQL數據庫中:

public void Add(User entity)
{
    using (var context = new UserContext())
    {
        context.Users.Add(entity);
        context.SaveChanges();
    }
}

和取:

public User GetById(int id)
{
    using (var context = new UserContext())
    {
        var user = context.Users
            .Include(u => u.Recipes)
            .Include(u => u.Recipes.Select(x => x.Ingredients))
            .FirstOrDefault(u => u.UserId == id);

        return user;
    }
}

我有集成測試,它創建一個User對象, List<Recipe>有兩個配方,每個配方都有List<Ingredient>有2個項目。 每種食譜都有所不同,總共有4種成分。 當我將它添加到存儲庫時,我可以在數據庫中看到它具有正確的PK和FK。

但是,從數據庫中檢索時,返回的User對象具有配方,但這些配方都沒有配方。 由於我設置我的getter的方式,當我嘗試訪問它們時,我收到一個空集合。

例如這樣做:

/* create recipes collection and seed it */

User user = new User {Recipes = recipes /* plus some omitted properites*/};
_repository.Add(user);

var returnedUser = _repository.GetById(user.UserId);

var userIngredients = user.Recipes.First(u => u.RecipeName == "Test").Ingredients.ToList();
var returnedIngredients = returnedUser.Recipes.First(u => u.RecipeName == "Test").Ingredients.ToList();

returnedIngredients為空,而userIngredients有兩個元素。 然后這會失敗斷言並導致集成測試失敗。

有人能告訴我如何在嵌套的一對多上正確地進行加載嗎?

這個:

get { return _recipes ?? new List<Recipe>(); } 

應該讀:

get { return _recipes ?? (_recipes = new List<Recipe>()); } 

成分相同。

否則,每次都會返回一個新的空列表(因為它沒有存儲在任何地方)。 這僅在整個列表設置在某個點(從數據庫讀取並覆蓋)時才有效,但如果您是按代碼創建實體則不會,因為它們將被添加到未存儲在支持字段中的列表中。

你看到Recipes已滿,因為你在這里設置它: User user = new User {Recipes = recipes /* plus some omitted properites*/}; ,因此它存儲在支持字段中。

但是當你添加它們時,成分不是:

var recipe = new Recipe();
recipe.Ingredients.Add(myIngredient); // <-- this will be stored in a 
                                      //     new List<Ingredient>, 
                                      //     but not on the 
                                      //     _ingredients backing field
                                      //     since the get accesor doesn't 
                                      //     set it
recipe.Ingredients.Add(myIngredient); // <-- this will be stored in a 
                                      //     new List<Ingredient>, not in 
                                      //     the expected created one

至於急切的加載,你不需要這個:

 var user = context.Users
        .Include(u => u.Recipes)
        .Include(u => u.Recipes.Select(x => x.Ingredients))
        .FirstOrDefault(u => u.UserId == id);

只需:

 var user = context.Users
        .Include(u => u.Recipes.Select(x => x.Ingredients))
        .FirstOrDefault(u => u.UserId == id);

它會加載兩個級別

幾點想法:

public class Ingredient
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int IngredientId { get; set; }

    public virtual Recipe Recipe { get; set; } // <-- make this virtual
}

...和...

public class Recipe
{
    public Recipe()
    {
        // set up a default collection during construction
        Ingredients = new List<Ingredient>();
        // could also be Ingredients = new Collection<Ingredient>();
    }

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int RecipeId { get; set; }

    // private ICollection<Ingredient> _ingredients; <-- don't back
    public virtual ICollection<Ingredient> Ingredients
    {
        get; // <-- should never return null because of constructor
        protected set; // and because externals cannot set to null
    }

    public virtual User User { get; set; } // <-- make this virtual too
}

也可能值得向依賴實體添加一些外鍵屬性(例如,成分中的RecipeId)並更新modelBuilder定義。 添加fk屬性之后會發生以下情況:

modelBuilder.Entity<Recipe>()
    .HasMany(u => u.Ingredients)
    .WithRequired(u => u.Recipe)
    .HasForeignKey(u => u.RecipeId) // <-- change is here
    .WillCascadeOnDelete(true);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM