EntityFramework Code First:为什么懒惰加载多对多参考失败?

时间:2011-08-23 22:23:34

标签: entity-framework entity-framework-4.1 ef-code-first lazy-loading data-annotations

如果您查看下面的代码并尝试使用DataAnnotations,我的EF 4.1 Code First模型应该是相当自我解释的。

特许经营出售许多物品。 特许经营有很多商店可以出售这些物品。 商店可能会以商品的形式出售。

一个名为SoldOutItems的多对多表格允许商店销售多件商品,并且商品在很多商店售罄。

问题:

与多对多表关联的导航属性对我来说不会像普通导航属性那样延迟加载。当我在数据库第一种方法中使用EntityFramework与由下面的模型生成的完全相同的数据库时,可以这样做。

我认为我只是对[InverseProperty] DataAnnotations做了一些错误,但我在网上搜索的所有内容都表明,只要导航属性是虚拟的,它应该是'懒惰可加载'。

在最底层,我提供了在控制台应用中重现问题的代码。

特权

[Table("Franchises")]
public class Franchise
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Int64 Id { get; set; }
    [Required, MaxLength(50)]
    public string Name { get; set; }

    [InverseProperty("Franchise")]
    public virtual ICollection<Store> Stores { get; set; }

    [InverseProperty("Franchise")]
    public virtual ICollection<Item> Items { get; set; }
}

商店

[Table("Stores")]
public class Store
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Int64 Id { get; set; }
    public Int64 FranchiseId { get; set; }
    [Required, MaxLength(50)]
    public string Name { get; set; }

    [ForeignKey("FranchiseId")]
    public virtual Franchise Franchise { get; set; }

    public virtual ICollection<Item> UnavailableItems { get; set; }
}

产品

[Table("Items")]
public class Item
{
    [Key]
    public Int64 Id { get; set; }
    public Int64 FranchiseId { get; set; }
    [Required, MaxLength(50)]
    public string Name { get; set; }

    [ForeignKey("FranchiseId")]
    public virtual Franchise Franchise { get; set; }

    public virtual ICollection<Store> StoresUnavailableAt { get; set; }
}

SoldOutItems

[Table("SoldOutItems")]
public class SoldOutItem
{
    [Key, Column(Order = 0)]
    public Int64 StoreId { get; set; }
    [Key, Column(Order = 1)]
    public Int64 ItemId { get; set; }

    [InverseProperty("UnavailableItems")]
    [ForeignKey("StoreId")]
    public virtual Store Store { get; set; }

    [InverseProperty("StoresUnavailableAt")]
    [ForeignKey("ItemId")]
    public virtual Item Item { get; set; }
}

上下文

public class RetailContext : DbContext
{
    public DbSet<Franchise> Franchises { get; set; }
    public DbSet<Store> Stores { get; set; }
    public DbSet<Item> Items { get; set; }
    public DbSet<SoldOutItem> SoldOutItems { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<SoldOutItem>().HasRequired(s => s.Store).WithMany().WillCascadeOnDelete(false);
        base.OnModelCreating(modelBuilder);
    }
}

重现抛出的空异常的代码:

static void Main(string[] args)
{
    RetailContext ctx = new RetailContext();
    ctx.Configuration.LazyLoadingEnabled = true;

    Franchise franchise = ctx.Franchises.Where(f => f.Id == 0).FirstOrDefault();
    if (franchise == null)
    {
        franchise = new Franchise() { Id = 0, Name = "My Franchise" };
        ctx.Franchises.Add(franchise);
        ctx.SaveChanges();
    }

    Item item = ctx.Items.Where(i => i.Name == "Item 1").FirstOrDefault();
    if (item == null)
    {
        item = new Item() { Name = "Item 1" };
        franchise.Items.Add(item);
    }

    Store myStore = ctx.Stores.Where(s => s.Id == 0).FirstOrDefault();
    if (myStore == null)
    {
        myStore = new Store() { Id = 1, Name = "My Store" };
        myStore.UnavailableItems.Add(item); // Exception: UnavailableItems is null

    }
}

解决方案

kwon的回答确实解决了我遇到的直接问题。然而,对我来说更大的问题是,我似乎无法使用DataAnnotations得到我想要的东西。所以我最终删除了我的连接表(SoldOutItems),转而使用Fluent。遗憾的是,这会产生一个循环引用问题,因为我现在无法从多对多表中删除级联删除。

删除'SoldOutItems'表后的新代码:

public class RetailContext : DbContext
{
    public DbSet<Franchise> Franchises { get; set; }
    public DbSet<Store> Stores { get; set; }
    public DbSet<Item> Items { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Store>().HasMany(s => s.UnavailableItems).WithMany(i => i.StoresUnavailableAt).Map(mc => mc.ToTable("SoldOutItems").MapLeftKey("StoreId").MapRightKey("ItemId"));
        //Unfortunately I have to remove the cascading delete here since I can't figure out how to do it on the table created from the line above.
        modelBuilder.Entity<Franchise>().HasMany(f => f.Stores).WithRequired(s => s.Franchise).WillCascadeOnDelete(false);
        base.OnModelCreating(modelBuilder);
    }
}

1 个答案:

答案 0 :(得分:2)

简单,您需要按以下方式启动两个集合:

[Table("Stores")]
    public class Store
    {
        public Store()
        {
            UnavailableItems = new HashSet<Item>();
        }
        ...
     }


[Table("Items")]
    public class Item
    {
        public Item()
        {
            StoresUnavailableAt = new HashSet<Store>();
        }
        ...
    }