实体框架6,急切加载嵌套关系返回空集合

时间:2015-02-10 18:28:40

标签: c# .net entity-framework

我有一个看起来像这样的域模型(剥离了一些不重要的属性)

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有一对多的关系,后者与Ingredient之间存在一对多的关系。我正在使用代码优先方法,在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>个项目。每种食谱都有所不同,总共有4种成分。当我将它添加到存储库时,我可以在数据库中看到它具有正确的PK和FK。

但是,从数据库中检索时,返回的User对象有配方,但这些配方都没有配方。由于我设置我的getter的方式,当我尝试访问它们时,我收到一个空集合。

e.g。这样做:

/* 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有两个元素。然后,这会导致断言失败并导致集成测试失败。

有人可以告诉我如何在嵌套的一对多上正确加载?

2 个答案:

答案 0 :(得分:6)

这:

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

它会加载两个级别

答案 1 :(得分:1)

一些想法:

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