让我扩展@bubi 的答案:
默认情况下,当您定义多对多关系(使用属性或 FluentAPI)时,EF 会创建它(向数据库添加额外的表)并允许您将多个产品添加到一个类别并将多个类别添加到一个产品。但它不允许您访问链接表行作为实体。
如果您需要这样的功能,例如您如何管理这些链接,例如将它们标记为“已删除”或设置“优先级”,您需要:
- 创建新实体 (ProductCategoryLink)
- 将其作为另一个 DbSet 添加到您的上下文中
- 相应地更新 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