【问题标题】:Return list of EF entities based on FK in related table using LINQ in WPF App在 WPF App 中使用 LINQ 在相关表中基于 FK 返回 EF 实体列表
【发布时间】:2018-05-26 07:02:27
【问题描述】:

我想返回与单个设备关联的组件列表。我有 3 个表,组件、设备、设备组件。

一个设备可以有多个组件,如图所示。

我在数据服务上有一个方法,它应该返回一个组件列表。

public async Task<IEnumerable<Component>> GetComponentsByEquipmentIdAsync(int equipmentId)
        {
            using (var ctx = _contextCreator())
            {
                //query components code
            }
        }

我做了一个 SQL 查询来完成这个:

select Component.ComponentId, Component.ComponentName
from Equipment inner join EquipmentComponent on Equipment.EquipmentId = EquipmentComponent.EquipmentID
inner join Component on EquipmentComponent.ComponentID = Component.ComponentId
where Equipment.EquipmentId = equipmentId

但我不确定如何将此 SQL 查询转换为 LINQ,然后返回组件列表。是否可以仅使用扩展方法来实现?目前我正在从数据库中返回单个组件而不使用 LINQ:

return await ctx.Components.AsNoTracking().SingleAsync(f => f.ComponentId == componentId);

【问题讨论】:

  • 如果 EF 设置正确,您的 Component 上应该有一个 EquipmentComponents 属性,在您的 EquipmentComponent 类上应该有一个 Equipments。您应该能够执行类似equipment.SelectMany(c =&gt; c.EquipmentComponents.Select(e =&gt; e.Components)) 的操作来获取设备组件的平面列表

标签: c# sql entity-framework linq


【解决方案1】:

有两种类型的 LINQ 函数:使用延迟执行的函数和不使用延迟执行的函数。

使用延迟执行的函数(Where、Select、GroupBy、Join、...)仅更改 IQueryable 的表达式。只有在调用 ToList()、Any()、FirstOrDefault() 等非延迟函数后,才会真正执行查询。

异步版本仅对非延迟函数有用。毕竟,你的线程可以做一些其他事情,而不是等待来自数据库的结果。

幸运的是,DbSet 有可等待的异步扩展函数。你可以使用它们。

但是,如果您设置了many-to-many relationship according to entity-framework default conventions,您的查询将非常容易。

class Equipment
{
    public int Id {get; set;}
    ...
    // every equipment has zero or more Components (many-to-many)
    public virtual ICollection<Component> Components {get; set;}
}
class Component
{
    public int Id {get; set;}
    public string Name {get; set;}
    // Every Component belongs to zero or more Equipments (many-to-many)
    public virtual ICollection<Equipment> Equipments {get; set;}
}
class MyDbcontext : DbContext
{
    public DbSet<Equipment> Equipments {get; set;}
    public DbSet<Component> Components {get; set;}
}

这就是 Entity Framework 需要知道的所有内容,才能理解您想要设计多对多关系。您不必提及 EquipmentComponent 表。实体框架会理解它的需要并为你创建它。

返回您的查询
您想要属于 ID 等于设备 ID 的设备的所有组件的所有 ComponentIds 和 ComponentNames

正确设置多对多后,您可以使用 ICollections 获取数据:

var result = dbContext.Equipments                     // from all equipment
    .Where(equipment => equipment.Id == equipmentId)  // take only those with equipmentId
    .SelectMany(equipment => equipment.Components     // from their components
        .Select(component => new
        {
            Id = component.Id,                        // select the Id
            Name = component.Name,                    // and the Name
        }));

这样的结果仍然是一个 IQueryable。尚未与数据库进行通信。要执行异步查询,您必须使用非延迟函数

 public async Task<IEnumerable<Component>> 
     GetComponentsByEquipmentIdAsync(int equipmentId)
{
    using (var dbContext = new MyDbContext())
    {
        var result = ... // see above
        return await result.ToListAsync();
    }
 }

如果您确实更喜欢偏离标准实体框架的多对多约定,并且您不想使用 ICollections,而是设计自己的联接,那么您必须自己做。

使用 linq 方法语法的内连接没有问题,但是如果你连接三个表,内连接看起来很可怕。 See How to perform multiple tables using lambda expressions

因此,我将使用查询语法来创建 IQueryable,然后使用异步函数获取结果:

public async Task<IEnumerable<Component>> GetComponentsByEquipmentIdAsync (int equipmentId)
{
    using (var dbContext = new MyDbContext())
    {
         var result = from equipment in dbContext.Equipments
         .Where(equipment => equipment.Id == equipmentId)
         join equipmentmodel in dbContext.EquipmentModel
            on equipment.Id equals equipmentModel.EquipmentId
         join model in dbContext.Models
            on equipmentmodel.ModelId equals model.Id
         select new
         {
             Id = component.Id,
             Name = component.Name
         };
    return await result.ToListAsync(); // or whatever non-deferred you want
}

【讨论】:

  • 抱歉回复晚了。谢谢,不得不在你的 IQueryable 中改变一些东西,但它现在对我有用。
【解决方案2】:

在一定程度上取决于您的实体以及您如何建立 M:N 关系...

如果您将 EF 配置为了解 M:N 关系,那么您可以/应该在任一侧都有一个导航属性,您可以使用该属性从关系一侧的一个元素获取到相关元素另一边,反之亦然...

在代码优先的方法中,它可能如下所示:

modelBuilder.Entity<EntityA>().HasMany(e => e.correspondingBs).WithMany(e => e.correspondingAs).Map(e => e.ToTable("AtoBTable").MapLeftKey("aId").MapRightKey("BId"));

在这种情况下,您可以使用 KinSlayerUY 在对您的问题的评论中提供的选择(EF 处理 M:N 部分并将映射表处理隐藏在引擎盖下的某处)

另一种选择是将映射表视为实体本身(如果关系本身具有其他属性,也许您想要这样做)

在这种情况下,您最终会得到如下结果:

ctx.Set<EntityA>().Where(x=>x.id==entityAid).SelectMany(x=>x.correspondingAtoBs.Select(y=>y.correspondingB))

(请注意代码中的拼写错误,我现在手头没有 IDE)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多