【问题标题】:How to filter nested entities of the same type?如何过滤相同类型的嵌套实体?
【发布时间】:2020-11-04 12:28:12
【问题描述】:

以下是我的实际情况的简化版。假设我有这个Person 实体:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    // etc.

    // Some navigation properties
    public virtual ICollection<Thing> Things { get; set; }
    // etc.
}

我还写了一个扩展方法来根据一个或多个属性进行过滤:

public static IQueryable<Person> Filter(this IQueryable<Person> query,
                                        string name = null, int? thingId = null, 
                                        Foo etc = null)
{
    if (!string.IsNullOrEmpty(name))
        query = query.Where(p => p.Name.ToLower().Contains(name.ToLower()));

    if (thingId.HasValue)
        query = query.Where(p => p.Things.Count > 0 && 
                                 p.Things.Any(t => t.Id == thingId.Value));
    // etc.

    return query;
}

..我可以这样使用:

var query = context.People.Filter(name, thingId);
var filteredPeople = query.Include(p => p.Things).Include(__).OrderBy(__).ToList();

我想让Person 成为一个嵌套实体(即每个人都有一个人的集合)。所以,我添加了以下属性:

public virtual ICollection<Person> Children { get; set; }
[ForeignKey("Parent")]
public int? ParentId { get; set; }
public virtual Person Parent { get; set; }

现在我正在努力实现过滤逻辑。我需要的是:

  • 如果父 Person 匹配过滤器,则将包含它如果它的后代之一匹配。
  • 只有满足上述条件的子 Person 才会被包括在内。

对于第二个问题,我可能会尝试this answer 中的解决方案,但我需要先解决第一个问题。我试图创建一个递归表达式,如下所示:

private static IQueryable<Person> FilterByName(this IQueryable<Person> query, string name)
{
    if (string.IsNullOrEmpty(name)) return query;

    Expression<Func<Person, bool>> selector = (p) => 
        p.Name.ToLower().Contains(name.ToLower()) 
        || p.Children.AsQueryable().FilterByName(name).Any();

    return query.Where(selector);
}

..但我得到一个例外,说它“无法翻译成商店表达式”

我能想到的唯一其他解决方案是递归迭代子树并尝试手动构建列表,因为它需要太多查询,所以效率低下。

如何有效过滤Person 及其后代的集合?

【问题讨论】:

  • 不要尝试对 EF 进行此操作。这是一个递归 CTE 查询,任何当前的 EF 版本都不支持。所以最好通过 Dapper 运行 SQL。
  • altho Svyatoslav 的评论可能不是你想听到的那种正确的评论。或者至少我不知道这样做......相反我在代码中执行recursively iterate the children's tree......所以你需要你的数据库来理解父子和链接......我这样做的方式是添加一个parentId到 Person 表,然后在代码中您可以构建一个树模型....然后应用您的逻辑,但您将无法直接从 EF 生成查询来为您构建它....或者其他任何东西,您需要更多的非关系数据库......如果是这样的话,即使我不确定。
  • @Seabizkit 谢谢。我现在可能不得不在代码中加载它。我只是想让这个问题得到更多关注,以防有一种我想不出的方法,因此,赏金。
  • 我首先将 p.Children.AsQueryable().FilterByName(name) 替换为 FilterByName 的内容,以查看 EF 将生成哪个查询(如果有的话)(没有单独方法的过滤也是如此,到位)。

标签: c# entity-framework linq entity-framework-6


【解决方案1】:

我们需要创建一个实现以下功能的查询:

  1. 如果父 Person 匹配过滤器或其后代之一匹配,则将包含它。

  2. 仅当满足上述条件时才包含子 Person。

如果我们颠倒我们的思维方式,并意识到没有父或子实体,只有人,我们可以重写我们的标准,说如下:

  1. 如果某个人与过滤器匹配,我们希望包含该人及其所有祖先。

这极大地简化了我们需要执行的查询并给出相同的结果。

现在,正如一些 cmets 所指出的,就分层查询而言,Entity Framework 并没有内置很多内容。这并不意味着我们不能使用 EF,但我们确实需要编写和执行原始 SQL 查询。

如果您想将实体保存到数据库,EF 仍会处理对象映射和更改跟踪。

这无法通过 Linq 进一步扩展,例如 .Where().Include(),因为它是一个不可堆肥的查询。

public static IEnumerable<Person> FilterPeople(this DbSet<Person> people, string name)
{
    return people.FromSqlRaw(
        "WITH child AS (" +
        "    SELECT * FROM People" +
        "    WHERE Name LIKE {0}" +
        "    UNION ALL" +
        "    SELECT parent.* FROM People parent" +
        "    INNER JOIN child ON parent.Id = child.ParentId" +
        ") SELECT * FROM child",
        $"%{name}%")
        .AsEnumerable();
}

这转化为参数化查询:

exec sp_executesql N'WITH child AS (SELECT * FROM People WHERE Name LIKE @p0 UNION ALL SELECT parent.* FROM People parent INNER JOIN child ON parent.Id = child.ParentId) SELECT * FROM child',N'@p0 nvarchar(4000)',
@p0=N'%Dick%'

【讨论】:

  • 这看起来很有希望;我要试一试。不过有两点:1)我认为我将不得不使用.SqlQuery() 而不是.FromSqlRaw(),因为我使用的是EF6。 2) 你知道这种类型的查询在 MS-Access 中是否可行吗? (因为这是我目前正在使用的;我没有在问题中提到它,因为我认为它不相关)
  • 我对 Access 不是很熟悉,但快速搜索得到了 stackoverflow.com/questions/763016/…。该问题的答案已有 10 年历史,所以我不知道是否有任何变化,但是他们建议在查询中执行任意数量的左连接。如果您知道层次结构的最大深度或者您对某个深度感到满意,则此方法有效。例如,使用 3 个左连接将产生孩子、父母、祖父母和曾祖父母。
  • 也许你应该用access 标记这个问题,以引起更了解访问的人的注意。如果是这样,我希望我的回答可以作为通向最后一个的垫脚石!
  • 不需要,因为a)我会以一种使当前答案无效的方式改变问题,b)这似乎不太希望在Access中工作使用纯 SQL。我真的很感谢你的帮助。这对未来的项目很有用,而且我实际上计划很快将这个项目迁移到 MySQL 或 MSSQL。我实际上用 SQL Server 对此进行了测试,虽然它包含与过滤器不匹配的子项(第二个要求),但它工作得很好,但我可能会想出一种方法来处理这个问题。再次感谢您的帮助!
猜你喜欢
  • 2014-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-30
  • 2011-10-28
  • 2018-12-31
  • 2016-02-15
  • 2021-03-16
相关资源
最近更新 更多