EF:根据主人和细节的条件获取主人细节

EF: Get master-detail based on conditions on master as well as on detail

EF Core 3.1 等主从模型:

public class MyMaster
{
    public Guid Id { get; set; }
    public string Category { get; set; }
    public List<MyDetail> MyDetails { get; set; }
}

public class MyDetail
{
    public Guid Id { get; set; }
    public Guid MasterId { get; set; }
    public bool IsDeleted { get; set; }
}

我想 select 特定类别中的所有主记录以及未标记为已删除的详细信息。此外,如果特定母版的所有详细信息都标记为已删除,则不会返回该母版。基本上我想 运行 这么简单 select:

select * from MyMaster m
inner join MyDetail d on m.Id = d.MasterId and d.IsDeleted = 0
where m.Category = 'foo'

我尝试了这样的 LINQ 方法,但它returns 也删除了详细记录:

var result = await dbContext.MyMasters
    .Include(m => m.MyDetails)
    .Where(m => m.Category = 'foo')
    .Join(dbContext.MyDetails.Where(d => !d.IsDeleted), m => m.Id, d => d.MasterId, (m, d) => m)
    .ToListAsync();

LINQ 方法查询应该是什么样子?

兄弟,你的代码是真的,但我编写并测试了这些查询并显示了结果。它对我有用。

         var result = await _dBContext.MyMasters
            .Include(m => m.MyDetails)
            .Where(m => m.Category == "foo")
            .Join(_dBContext.MyDetails.Where(d => !d.IsDeleted),
                m => m.Id,
                d => d.MasterId,
                (m, d) => new {
                    MasterId = m.Id,
                    Category = m.Category,
                    DetailId = d.Id,
                    IsDeleted = d.IsDeleted,
                })
            .ToListAsync();


        var result2 = from m in _dBContext.MyMasters.Include(m => m.MyDetails)
                      join d in _dBContext.MyDetails on m.Id equals d.MasterId
                      where m.Category == "foo" && !d.IsDeleted
                      select new { 
                            MasterId = m.Id,
                            Category = m.Category,
                            DetailId = d.Id,  
                            IsDeleted = d.IsDeleted,
                      };

you try for select New type after Join two collection. IF you get Collection from MyDetails in masters, you have all items because it's not filtered, it's just all navigations item.

如果您只需要查询数据,可以通过Select轻松检索:

var result = await dbContext.MyMasters
    .Where(m => m.Category = 'foo')
    .Select(m => new MyMaster
    {
        Id = m.Id,
        Category = m.Category,
        MyDetails = m.MyDetails.Where(d => !d.IsDeleted).ToList()
    })
    .ToListAsync();

看来真正的问题是如何过滤相关实体。

已过滤包含

在 EF Core 5 及更高版本中,这是使用 filtered includes:

完成的
var result = await dbContext.MyMasters
    .Include(m => m.MyDetails.Where(d=>!d.IsDeleted))
    .Where(m => m.Category = 'foo')
    .ToListAsync();

全局查询过滤器

在 EF Core 2 和后期版本中,另一种选择是使用 global query filters 来确保删除的实体永远不会通过该 DbContext 加载,即使是意外加载也是如此:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<MyDetail>().HasQueryFilter(p => !p.IsDeleted);
    ...
}

这样,以下查询将只加载活动记录:

var result = await dbContext.MyMasters
    .Include(m => m.MyDetails)
    .Where(m => m.Category = 'foo')
    .ToListAsync();

全局查询过滤器会影响整个 DbContext,这非常好。 DbContext 不是数据库模型,实体也不是表。拥有针对同一数据库的实体和不同配置的不同 DbContext 非常好,可以处理特定的 scenarios/use 情况(如果你喜欢 DDD,则为有界上下文)。

在这种情况下,大多数应用程序将使用带有全局查询过滤器的 DbContext,而管理页面将使用允许访问所有记录的不同过滤器