EntityFramework Code First:为什么延迟加载多对多引用会失败?

发布于 2024-11-30 23:29:45 字数 4921 浏览 3 评论 0原文

如果您查看下面的代码并尽可能尝试使用 DataAnnotations,那么我的 EF 4.1 Code First 模型应该是相当不言自明的。

特许经营店出售许多商品。 特许经营拥有许多可以销售商品的商店。 商店中的商品可能会售空。

名为 SoldOutItems 的多对多表允许商店拥有许多已售完的商品,并且允许一个商品在许多商店中售罄。

问题:

The navigation properties associated with the many to many table won't lazy load for me like a normal navigation property would. It was possible to do when I used EntityFramework in a Database first approach with the same exact database that generated by the model below.

我认为我只是在 [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);
    }
}

My EF 4.1 Code First model should be fairly self-explanitory if you look at the code below and am trying to use DataAnnotations whenever I can.

A franchise sells many items.
A franchise has many stores that can sell the items.
A store may be sold out of an item.

A many to many table called SoldOutItems allows for a store to have many sold out items, and an item to be sold out at many stores.

Problem:

The navigation properties associated with the many to many table won't lazy load for me like a normal navigation property would. It was possible to do when I used EntityFramework in a Database first approach with the same exact database that generated by the model below.

I assume I'm just doing something wrong with the [InverseProperty] DataAnnotations, but everything I search for online just states that as long as the navigation property is virtual, it should be 'lazy loadable'.

At the very bottom, I've provided code to reproduce the issue in a console app.

Franchises

[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; }
}

Stores

[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; }
}

Items

[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; }
}

Context

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

Code to reproduce null exception that gets thrown:

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

    }
}

Solution

kwon's answer does indeed fix the immediate problem I was having. However, a much bigger problem for me is that it seems I cannot get what I want simply using DataAnnotations. So I ended up removing my Junction Table (SoldOutItems) in favor of using Fluent. This unfortunately creates a cyclical reference problem since I cannot remove the cascading deletes from my many-to-many table now.

New code after removing my 'SoldOutItems' table:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

薄荷梦 2024-12-07 23:29:45

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

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


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

Simple,, you need to initiate both collections as follow :

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


[Table("Items")]
    public class Item
    {
        public Item()
        {
            StoresUnavailableAt = new HashSet<Store>();
        }
        ...
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文