首页 > 解决方案 > LINQ to Entities:对多对多关系执行连接(代码优先)

问题描述

我有以下模型:

[Table("Experiments")]
public class Experiment
{
    ...

    public virtual ICollection<ExperimentType> ExperimentTypes { get; set; }

    public Experiment()
    {
        ExperimentTypes = new List<ExperimentType>();
    }
}

[Table("ExperimentTypes")]
public class ExperimentType
{
    ...

    public virtual ICollection<Experiment> Experiments { get; set; }

    public ExperimentType()
    {
        Experiments = new List<Experiments>();
    }
}

DbSet 包含:

    public DbSet<Experiment> Experiments { get; set; }
    public DbSet<ExperimentType> ExperimentTypes{ get; set; }

这会在 SQL 上创建一个名为 ExperimentExperimentTypes 的表。

现在,我想执行 LINQ 连接,例如:

var query =
   from e in database.Experiments
   join eet in database.ExperimentExperimentTypes on eet.Experiment_Id equals eet.ExperimentType_Id ...

但显然 database.ExperimentExperimentTypes 在代码中无法识别。

为了告诉代码有这个表,我尝试了很多东西,我也尝试创建相应的c#类,但没有得到任何结果。

怎样才能做到这一点?

标签: c#linqef-code-firstlinq-to-entitiescode-first

解决方案


所以你有两个表:ExperimentExperimentType. 这两者之间存在多对多的关系:每Experiment一个都是零个或多个实验ExperimentTypes;everyExperimentType是 zero 或 more 的类型Experiments

这种多对多可以在您的类定义中看到。两边的virtual ICollection<...>表示多对多的关系。

在关系数据库中,这种多对多关系是使用联结表实现的。但是,在实体框架中,您很少看到联结表。它在您的数据库中,但是,您无法使用 DbContext 访问它

但是,如果我无法访问联结表,我将如何在 Experiments 和 ExperimentTypes 之间执行联结呢?

好吧,小熊维尼应该回到他的思考点。你不想加入表,你想要Experiments,每个都有它的ExperimentTypes.

那么为什么不直接使用ICollection<...>?

var experiments = myDbContext.Experiments
    .Where(experiment => ...)              // only if you don't want all experiments
    .Select(experiment => new
    {   // Select only the properties you actually plan to use
        Id = experiment.Id,
        Name = experiment.Name,
        ...

        // get all or some of its ExperimentTypes
        ExperimentTypes = experiment.ExperimentTypes
             .Where(experimentType => ...)  // only if you don't want all experiment types
             .Select(experimentType => new
             {
                  // again: select only the properties you plan to use
                  Id = experimentType.Id,
                  ...
             })
             .ToList(),
    });

实体框架知道多对多,它知道为此需要与联结表的三重连接,并将执行此三重连接。

在内部,这将是一个GroupJoin,你会得到Experiments,每个都有他们的ExperimentTypes. 您甚至可以获得还没有任何 ExperimentType 的 Experiments。

如果你真的想要inner join,你必须用他们的 ExperimentTypes 来展平 Experiments。这是使用具有 resultSelector 作为参数的 SelectMany 的重载来完成的

// flatten the Experiments with their experimentTypes
var flatInnerJoin = myDbContext.Experiments.SelectMany(experiment => experiment.ExperimentTypes,

    // from each experiment and one of its experimentTypes make one new object
    (experiment, experimentType) => new
    {
        ExperimentId = experiment.Id,
        ExperimentTypeId = experimentType.Id,
        ...
     });
 })

注意!这样您就不会得到没有 ExperimentTypes 的 Experiments,就像在标准内部连接中一样。


推荐阅读