我的代码优先实体框架模型中有多对多的关系。想象一下,我们有两个表,“公司”和“文章”,它们之间有这种关系。我的简化代码模型如下所示:
public class Article
{
public int Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
使用流畅的映射,我创建了多对多关系:
modelBuilder.Entity<Company>().HasMany(c => c.Articles).WithMany(a => a.Companies);
这很好用,EF创建中间表。但是,根据我的逻辑,将每个实体与外键集合在一起会非常好。这意味着我想将以下属性添加到相应的模型类中:
public virtual ICollection<int> ArticlesId { get; set; } // to Company
public virtual ICollection<int> CompaniesId { get; set; } // to Article
我知道一个解决方案是在EF中创建中间表模型并在每次调用中手动选择适当的ID,但是EF映射可以提供更方便的方法来执行此类操作吗?提前感谢您提供任何提示。
答案 0 :(得分:9)
似乎很遗憾没有办法以我想要的方式映射ID。但是,以下是有关如何实现所需实体密钥检索的三种解决方法。
Ben Reich建议第一个解决方案 实现get-only属性,它只返回链接实体的ID。
public class Company
{
public virtual ICollection<Article> Articles { get; set; }
public IEnumerable<int> ArticlesIds
{
get { return Articles.Select(a => a.Id); }
}
}
它似乎很方便使用,但是它有一个缺点 - 整个实体将从数据库中读取,以便接收唯一的ID。以下是来自SQL事件探查器的此类调用的日志:
exec sp_executesql N'SELECT
[Extent2].[Id] AS [Id],
[Extent2].[Header] AS [Header],
[Extent2].[Description] AS [Description],
[Extent2].[Text] AS [Text],
[Extent2].[CreationDate] AS [CreationDate],
[Extent2].[AccountId] AS [AccountId],
[Extent2].[ImageSetId] AS [ImageSetId]
FROM [dbo].[CompanyArticles] AS [Extent1]
INNER JOIN [dbo].[Articles] AS [Extent2] ON [Extent1].[Article_Id] = [Extent2].[Id]
WHERE [Extent1].[Company_Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
第二解决方案。
使用相同的模型,在读取实体后单独读取ID。
var ids = db.Set<Article>().Where(a => a.Companies.Select(c => c.Id).Contains(f.Id)).ToList();
这种方法与前一种方法完全相同,将获取整个实体集。
exec sp_executesql N'SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Header] AS [Header],
[Extent1].[Description] AS [Description],
[Extent1].[Text] AS [Text],
[Extent1].[CreationDate] AS [CreationDate],
[Extent1].[AccountId] AS [AccountId],
[Extent1].[ImageSetId] AS [ImageSetId]
FROM [dbo].[Articles] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[ArticleCompanies] AS [Extent2]
WHERE ([Extent1].[Id] = [Extent2].[Article_Id]) AND ([Extent2].[Company_Id] = @p__linq__0)
)',N'@p__linq__0 int',@p__linq__0=1
第三次解决方案。从我的观点来看,这是最合适的 为中间表创建实体类。
public class ArticleCompany
{
public int CompanyId { get; set; }
public int ArticleId { get; set; }
public virtual Company Company { get; set; }
public virtual Article Article { get; set; }
}
将具有此实体的两个实体映射为1对m关系。不要忘记映射新实体本身。
modelBuilder.Entity<Article>().HasMany(a => a.ArticlesCompanies).WithRequired(ac => ac.Article).HasForeignKey(ac => ac.ArticleId);
modelBuilder.Entity<Company>().HasMany(c => c.ArticlesCompanies).WithRequired(ac => ac.Company).HasForeignKey(ac => ac.CompanyId);
modelBuilder.Entity<ArticleCompany>().ToTable("ArticlesCompanies");
modelBuilder.Entity<ArticleCompany>().HasKey(ac => new { ac.ArticleId, ac.CompanyId });
然后,在获取实体后,使用中间表来获取相关的ID:
var ids = db.Set<ArticleCompany>().Where(ca => ca.CompanyId == companyEntity.Id).Select(ca => ca.ArticleId);
对应的SQL日志(仅从数据库中提取ID):
exec sp_executesql N'SELECT
[Extent1].[ArticleId] AS [ArticleId]
FROM [dbo].[ArticlesCompanies] AS [Extent1]
WHERE [Extent1].[CompanyId] = @p__linq__0',N'@p__linq__0 int',@p__linq__0=1
答案 1 :(得分:1)
一些简单的吸气剂是否足够?
public IEnumerable<int> ArticlesId { get { return this.Articles.Select(a => a.Id); } }
public IEnumerable<int> CompaniesId { get { return this.Companies.Select(c => c.Id); } }
这将涵盖许多用例。当然,由于这不是虚拟关联属性,因此在实际的Linq-to-Entities查询中使用这些属性会失去一些灵活性。