实体框架,代码优先,如何在多对多关系映射中向表添加主键

本文关键字:映射 添加 关系 代码 实体 框架 | 更新日期: 2023-09-27 18:07:51

我正在构建ASP。asp.net MVC 5应用程序。我使用实体框架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

实体框架,代码优先,如何在多对多关系映射中向表添加主键

让我展开@bubi的答案:

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

如果您需要这样的功能,例如,您需要以某种方式管理这些链接,例如将它们标记为"已删除"或设置"优先级",您需要:

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

对于你,它可能像:

<<p> 实体/strong>
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);
}
<<p> 上下文/strong>

这不是必需的,但很可能您需要将链接直接保存到上下文,因此让我们为它们定义一个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)]属性)),两个fk在productcategoreslink实体成为一个复合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;
    }
}

乌利希期刊指南:

在SO上有一个同样的问题和详细的答案:首先创建代码,多对多,在关联表

中添加字段

如果你需要一个像你发布的模型(一个"干净"的模型),你需要禁用自动迁移并自己创建表。EF将不处理Id,所以它必须是自动编号。

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