【问题标题】:Is it possible to rewrite complex EF Core ".Include" calls to "Join" calls?是否可以将复杂的 EF Core ".Include" 调用重写为 "Join" 调用?
【发布时间】:2021-01-16 08:58:55
【问题描述】:

我有两个名为 Service 和 ServiceBranch 的 EF Core 实体模型:

public class Service
{
    public Guid ID { get; set; }
    public Guid? ParentServiceID { get; set; } // foreign key to the same table
    public string Name { get; set; }
    public DateTime? DateDeleted { get; set; }

    public virtual ICollection<Service> InverseParent { get; set; } // navigation property
    public virtual ICollection<ServiceBranch> ServiceBranches { get; set; } // navigation property
}

public class ServiceBranch
{
    public Guid ID { get; set; }
    public Guid ServiceID { get; set; } // foreign key
    public string Name { get; set; }
    public DateTime? DateDeleted { get; set; }

    public virtual Service Service { get; set; } // navigation property
}

为简单起见,假设父服务只能有一级子服务。许多服务分支都引用了父服务和子服务。

我想更新特定服务 ID 的 DateDeleted 字段,并在引用它的所有其他行中更新 DateDeleted(如果该字段还没有值)。

目前,我对所有必需实体的调用如下所示:

var serviceEntity = _context.Set<Service>().Where(x => x.ID == neededID && x.DateDeleted == null)
                .Include(x => x.ServiceBranches)
                .Include(x => x.InverseParent)
                    .ThenInclude(InverseParent => InverseParent.ServiceBranches)
                .FirstOrDefault();

我对主要实体使用 Where 子句,但遗憾的是我不能在 .Includes 中使用 Where 子句,因为我们还没有升级到 EF Core 5.0(它支持在 .Includes 中进行过滤)。

因此,我从数据库中带来了太多不需要的行(其中 DateDeleted 已经有一个值),然后通过将该字段与 null 进行比较,在 foreach 循环中过滤它们:

if (myentity.DateDeleted == null) myentity.DateDeleted = DateTime.Now;

我想重写我的查询,以便使用“join”而不是“.Include”,如下所示:

var serviceEntity = from service in _context.Set<Service>()
                where service.ID == neededID && service.DateDeleted == null
                join branches in _context.Set<ServiceBranch>() on service.ID equals branches.ServiceID
                where branches.DateDeleted == null // filter which I cannot use with .Include-s
                let // whatever, can't make it work
                select // etc

我不知道如何重写它。或者甚至可以通过连接来实现。

【问题讨论】:

  • 有无数关于过滤包含的问题,虽然它还不被支持。您应该能够在其中许多中找到解决方法。一个提示:不要加入。使用与 Include 相同的导航属性。

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


【解决方案1】:

在这种特殊情况下,您不需要连接,而是需要包含所需 Service 实体及其所有子实体的平面列表。一般来说,这需要递归查询(LINQ / EF Core 不支持),但对于单级,这是一个简单的过滤器问题,如

service.ID == neededID || service.ParentServiceID == neededID

然后您可以应用附加的DateDeleted 条件。最后,要使用DateDeleted 过滤器获取相关的ServiceBranch 实体,而不是Include,只需使用投影(Select)。

并且在处理返回的数据时,使用投影实体并忽略它们的导航属性。例如

var itemsToUpdate = _context.Set<Service>()
    .Where(service => (service.ID == neededID || service.ParentServiceID == neededID)
        && service.DateDeleted == null)
    .Select(service => new
    {
        Service = service,
        ServiceBrances = service.ServiceBranches
            .Where(branch => branch.DateDeleted == null),
    });

foreach (var item in itemsToUpdate)
{
    item.Service.DateDeleted = DateTime.Now;
    foreach (var branch in item.ServiceBrances)
        branch.DateDeleted =  DateTime.Now;
}

【讨论】:

    【解决方案2】:

    Include 将加载的对象直接放在您的实体框架模型上,并且该模型实际上应该直接表示数据库中的内容。如果您想要一个过滤的集合,我建议您提出一个不同的模型来表示您要表示的内容,然后使用 Select 投影到该模型。像这样的:

    var serviceEntity = _context.Set<Service>()
        .Where(x => x.ID == neededID && x.DateDeleted == null)
        .Select(x => new // you can make this a named type if necessary
            {
               Service = x,
               ActiveBranches = x.ServiceBranches.Where(b => b.DateDeleted == null),
               InverseParent,
               InverseParentServiceBranches = x.InverseParent.ServiceBranches
            })
        .FirstOrDefault();
    

    【讨论】:

    • 所以加入连接毕竟是错误的方向。谢谢
    猜你喜欢
    • 1970-01-01
    • 2020-11-29
    • 1970-01-01
    • 2020-11-28
    • 1970-01-01
    • 2015-11-03
    • 1970-01-01
    • 1970-01-01
    • 2010-12-11
    相关资源
    最近更新 更多