EF核心。如何提取(左)联接表作为集合?

时间:2020-04-09 20:27:37

标签: c# .net entity-framework linq entity-framework-core

我需要帮助将联接表提取为上下文linq语法表达式中的一个集合。

public class Computer
{
   public int Id { get; set; }
   public string Name { get; set; }
}

public class Component
{
   public int Id { get; set; }
   public string Name { get; set; }

   public int ComputerId { get; set; } //FK
   public Computer Computer { get; set; } //HasOne(Computer).WithMany().HasFK(ComputerId)
}

计算机没有对组件的反向引用

需要通过Linq语法选择具有相关组件的计算机。

(from computer in db.Computers
join component in db.Components on computer.Id equals component.ComputerId into components //|  JOINED
from component in components.DefaultIfEmpty()                                              //|  TABLE
select new ComputerFullData
{
   Computer = computer,
   Components = components // <-- collection
})
........other code

此linq不起作用并生成NullRefException NullRefException

但是,如果计算机具有 ICollection backref 属性,那么我可以通过调用Include(x = > x.Components),实际上就像我的示例一样,它是revert-JOIN。在这种情况下,我可能无法访问组件集合。

如何通过Linq实现相同的目标?

1 个答案:

答案 0 :(得分:2)

这在EF Core中对我来说很漂亮:

public class Computer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Component
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int ComputerId { get; set; }
    public Computer Computer { get; set; }
}

public class Db : DbContext
{
    public static readonly ILoggerFactory MyLoggerFactory
        = LoggerFactory.Create(builder => builder.AddConsole());

    public DbSet<Component> Components { get; set; }

    public DbSet<Computer> Computers { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseLoggerFactory(MyLoggerFactory)
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb; Database=demo;Integrated Security=True");
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var db = new Db())
        {
            db.Database.EnsureCreated();

            db.Components.Add(
                new Component
                {
                    Computer = new Computer
                    {
                        Name = "Fred"
                    },
                    Name = "Fred component"
                });

            db.SaveChanges();
        }

        using (var db = new Db())
        {
            var computersWithComponents =
                (from c in db.Computers
                 select new
                 {
                     Computer = c,
                     Components = (from cp in db.Components
                                   where cp.ComputerId == c.Id
                                   select cp).ToList()
                 }).ToList();
        }
    }
}

这很像您在SQL中编写的内容。我很高兴发现在这样的子查询中引用上下文(db)对于EF查询提供程序是可接受的,并且生成的SQL与我可能手工编写的内容类似:

SELECT [c].[Id], [c].[Name], [c0].[Id], [c0].[ComputerId], [c0].[Name]
       FROM [Computers] AS [c]
       LEFT JOIN [Components] AS [c0] ON [c].[Id] = [c0].[ComputerId]
       ORDER BY [c].[Id], [c0].[Id]

出于兴趣-我知道这不是您问题的直接答案-为什么您拒绝在模型上拥有Components的导航属性?我经常在EF中发现,做一些有效的工作会有所帮助,而不是过度努力以获取我想要的模型。 (如果我对所需对象的形状有非常特定的要求,则可能会有两个类层次结构-EF特有的EF数据库模型,以及我的首选模型,其中包含在两者之间进行转换的方法。)无论如何,在在这种情况下,获取您想要的东西并不难,因此在这里可能会显得过大。