【问题标题】:Keep list of foreign keys in many-to-many Entity Framework relationship在多对多实体框架关系中保留外键列表
【发布时间】:2013-09-10 12:15:44
【问题描述】:

我的代码优先实体框架模型中存在多对多关系。想象一下,我们有两个表,“Company”和“Article”,它们之间有这样的关系。我的简化代码模型如下所示:

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 映射可以提供更方便的方式来执行此类操作?提前感谢您的任何提示。

【问题讨论】:

  • 这是不可能的,因为它没有意义,EF 只是代表您的数据库设计中的内容,并且在多对多关系中,您需要 3 个表,其中两个“主”表没有任何外键相互引用。
  • Articles 的物理集合也不存在,但 EF 会自动管理此类集合。

标签: c# entity-framework entity-framework-5


【解决方案1】:

不幸的是,似乎没有办法以我想要的方式映射 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 Profiler 的此类调用的日志:

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; }
}

将两个实体与该实体映射为一对一关系。不要忘记映射新实体本身。

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

【讨论】:

  • 第三个解决方案确实很有吸引力。应该记住,所有关系的更改,如CompanyArticle 添加都应该通过新实体完成,这比通常的“列表语法”多对多方便。还有另一个有争议的第四解决方案:使用原始 SQL 查询进行索引提取:_ctx.Database.SqlQuery&lt;int&gt;("SELECT Article_Id FROM ArticlesCompanies WHERE Company_Id = '" + c.Id + "'");
【解决方案2】:

一些简单的吸气剂就足够了吗?

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 查询中使用这些属性的一些灵活性。

【讨论】:

  • 这似乎是一个很好的解决方案,但是当我们调用这个 getter 并访问例如文章集合,将文章作为数据库中的整个实体读取(每个字段都将被填充)并返回适当的 ID,还是仅执行读取 ID 的查询?我猜在第一种情况下,它可能会影响性能。
  • @Eadel 说得好。通过将过多数据读入内存,这可能会对性能产生轻微影响。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-28
  • 2011-12-17
  • 1970-01-01
相关资源
最近更新 更多