【问题标题】:Entity Framework, code-first, how to add primary key to table out of many-to-many relationship mapping?实体框架,代码优先,如何在多对多关系映射中向表添加主键?
【发布时间】:2015-08-14 09:24:48
【问题描述】:

我正在构建 ASP.NET MVC 5 应用程序。我正在使用 Entity Framework 6.1,代码优先的方法来生成数据库。我在 ProductCategory 之间存在多对多关系。

public class Product
{
    public long Id { get; set; }        
    public string Name { get; set; }
    public string Description { get; set; }

    // navigation
    public virtual ICollection<Category> Categories { get; set; }
}

public class Category
{
    public long Id { get; set; }        
    public string Name { get; set; }

    // navigation
    public virtual ICollection<Product> Products { get; set; }
}

Dbcontext 类中,我重写了 OnModelCreating 方法来为多对多关系创建表,如下所示:

modelBuilder.Entity<Product>().HasMany<Category>(s => s.Categories).WithMany(c => c.Products)
.Map(cs =>
{
    cs.MapLeftKey("ProductId");
    cs.MapRightKey("CategoryId");
    cs.ToTable("ProductCategories");
});

表作为连接两个外键出现。 如何向此联结表添加 Id(作为主键)?

ProductCategories

 - Id // add id as primary key
 - ProductId    
 - CategoryId

【问题讨论】:

  • 您确定要另一列作为 PK 吗?这意味着您可以拥有多个产品类别关联。
  • 只需明确地为您的连接表建模,您就不需要流利的了。然后,如果您想要独特的组合,则可以设置索引。看到这个stackoverflow.com/questions/26787976/…

标签: c# asp.net-mvc entity-framework ef-code-first many-to-many


【解决方案1】:

让我扩展@bubi 的答案:

默认情况下,当您定义多对多关系(使用属性或 FluentAPI)时,EF 会创建它(向数据库添加额外的表)并允许您将多个产品添加到一个类别并将多个类别添加到一个产品。但它不允许您访问链接表行作为实体。

如果您需要这样的功能,例如您如何管理这些链接,例如将它们标记为“已删除”或设置“优先级”,您需要:

  1. 创建新实体 (ProductCategoryLink)
  2. 将其作为另一个 DbSet 添加到您的上下文中
  3. 相应地更新 Product 和 Category 实体中的关系。

对你来说可能是这样的:

实体

public class Product
{
    [Key]
    public long ProductId { get; set; }        
    public string Name { get; set; }
    public string Description { get; set; }

    [InverseProperty("Product")]
    public ICollection<ProductCategoryLink> CategoriesLinks { get; set; }
}

public class Category
{
    [Key]
    public long CategoryId { get; set; }        
    public string Name { get; set; }

    [InverseProperty("Category")]
    public ICollection<ProductCategoryLink> ProductsLinks { get; set; }
}

public class ProductCategoryLink
{
    [Key]
    [Column(Order=0)]
    [ForeignKey("Product")]
    public long ProductId { get; set; }        

    public Product Product { get; set; }

    [Key]
    [Column(Order=1)]
    [ForeignKey("Category")]
    public long CategoryId { get; set; }       

    public Category Category { get; set; }
}

我使用属性方式来定义关系,因为我更喜欢这种方法。但是您可以轻松地将其替换为具有两个一对多关系的 FluentAPI:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{

   // Product to Links one-to-many 
   modelBuilder.Entity<ProductCategoryLink>()
                    .HasRequired<Product>(pcl => pcl.Product)
                    .WithMany(s => s.CategoriesLinks)
                    .HasForeignKey(s => s.ProductId);


   // Categories to Links one-to-many 
   modelBuilder.Entity<ProductCategoryLink>()
                    .HasRequired<Category>(pcl => pcl.Category)
                    .WithMany(s => s.ProductsLinks)
                    .HasForeignKey(s => s.CategoryId);
}

上下文

这不是必需的,但很可能您需要将链接直接保存到上下文中,所以让我们为它们定义一个 DbSet。

public class MyContext : DbContext 
{
    public DbSet<Product> Products { get; set; }

    public DbSet<Category > Categories{ get; set; }

    public DbSet<ProductCategoryLink> ProductCategoriesLinks { get; set; }    
}

两种实现方式

我使用属性来定义关系的另一个原因是它表明(都标有 [Key] 属性(还要注意 [Column(Order=X)] 属性]))ProductCategoriesLink 实体中的两个 FK 成为复合PK,因此您无需定义另一个属性,如“ProductCategoryLinkId”并将其标记为特殊的 PK 字段。

您总是可以找到所需的链接实体,您只需要两个 PK:

using(var context = new MyContext)
{
    var link = context.ProductCategoriesLinks.FirstOrDefault(pcl => pcl.ProductId == 1 
                                                                 && pcl.CategoryId == 2);
}

此外,这种方法限制了保存具有相同产品和类别的多个链接的机会,因为它们是复杂的键。如果您更喜欢 Id 与 FK 分离的方式,则需要手动添加 UNIQUE 约束。

无论您选择哪种方式,您都可以根据需要操作链接并在需要时向其中添加其他属性。

注 1

由于我们将多对多链接定义为单独的实体,因此产品和类别之间不再有直接关系。所以你需要更新你的代码:

现在您需要定义一个 ProductCategoryLink 实体并根据您的逻辑上下文使用三种方式之一保存它,而不是将 Product 直接添加到 Category 或 Category 直接添加到 Product:

public void AddProductToCategory(Product product, Company company)
{
    using (var context = new MyContext())
    {
         // create link
         var link = new ProductCategoryLink{
             ProductId = product.ProductId, // you can leave one link
             Product = product,             // from these two
             CategoryId = category.CategoryId, // and the same here
             Category = category
             };

         // save it
         // 1) Add to table directly - most general way, because you could 
         // have only Ids of product and category, but not the instances
         context.ProductCategoriesList.Add(link);

         // 2) Add link to Product - you'll need a product instance
         product.CategoriesLinks.Add(link);

         // 3) Add link to Category - you'll need a category instance
         category.ProductLinks.Add(link);

         context.SaveChanges();
    }
}

注2

还请记住,如果您需要查询第二个链接实体,则您的属性现在导航到 ProductCategoryLinks(不是针对类别的产品,也不是针对产品的类别)。Include() 它:

public IEnumerable<Product> GetCategoryProducts(long categoryId)
{
    using (var context = new MyContext())
    {
         var products = context.Categories
                               .Include(c => c.ProductsCategoriesLinks.Select(pcl => pcl.Product))
                               .FirstOrDefault(c => c.CategoryId == categoryId);

         return products;
    }
}

UPD:

关于 SO 的详细答案有一个相同的问题: Create code first, many to many, with additional fields in association table

【讨论】:

    【解决方案2】:

    如果您需要像您发布的模型(“干净”模型),您需要禁用自动迁移并自行创建表。 EF 不会处理 Id,所以它必须是自动编号的。

    如果你需要在你的应用中处理和查看 Id,你的模型是不同的,Junction 表必须在模型中有一个类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-14
      • 1970-01-01
      • 2011-11-20
      相关资源
      最近更新 更多