实体框架,代码优先,如何在多对多关系映射中向表添加主键
本文关键字:映射 添加 关系 代码 实体 框架 | 更新日期: 2023-09-27 18:07:51
我正在构建ASP。asp.net MVC 5应用程序。我使用实体框架6.1,代码第一的方法来生成一个数据库。Product和Category之间存在多对多关系。
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添加额外的表),并允许您向一个类别添加多个产品,向一个产品添加多个类别。但是它不允许您以实体的形式访问链接表的行。
如果您需要这样的功能,例如,您需要以某种方式管理这些链接,例如将它们标记为"已删除"或设置"优先级",您需要:
- 创建新实体(ProductCategoryLink)
- 将其作为另一个DbSet添加到您的上下文
- 相应更新产品和品类实体中的关系。
对于你,它可能像:
<<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表必须在模型中有一个类