为什么此查询引发 "SQL statement is nested too deeply"?

Why is this query raising "SQL statement is nested too deeply"?

昨天,一个好人帮我为 Linq to Entities .
构建了一个 PredicateBuilder 似乎工作正常,但是完整的查询生成了这个可怕的 70 000 行长的东西 here(太长而无法粘贴),并引发 SQL statement is nested too deeply.

这里是上下文:
用户正在寻找符合他标准的动物列表,特别是关于能力。
在 GUI 中,对于每种能力类型(例如:"maniability"、"agility" 等),用户可以 select 修饰符(“>”、“<”或“=”)和一个值。
比如他可能想显示"All animals that have an ability potential > 3 in agility",或者"All animals that have an ability skill < 10 in maniability and ability potential = 2 in agility"

关于数据库:

PlayerId
AnimalId
Ability 列:

因此,每只动物都有一个 AllAbilities 属性 即 ICollection<Ability>.

这是搜索功能(所有参数之前都已由用户在 GUI 中输入或留空)。

public async Task<List<Animal>> Search
    (
        Player player,
        int speciesId,
        int breedId,
        int coatId,
        int genderId,
        int minAge,
        int maxAge,
        int priceModifier, // int representing an Enum Criteria.ModifierE: ">", "<" or "="
        int priceValue,
        string ownerPseudo,
        bool isSearchingOwn,
        int minHeight,
        int maxHeight,
        int minWeight,
        int maxWeight,
        List<int> character, // representing list of Enum Flags
        List<int> abilitySkillModifiers, // representing list of Enum ModifierE: ">", "<" or "="
        List<int> abilitySkillValues,
        List<int> abilityPotentialModifiers, // representing list of Enum ModifierE: ">", "<" or "="
        List<int> abilityPotentialValues
    )
    {
        // You can see "PredicateUtils" class following the first link of this post
        var filter = PredicateUtils.Null<Animal>();

        filter = filter.And(e => speciesId != -1 ? e.SpeciesId == speciesId : true);
        filter = filter.And(e => breedId != -1 ? e.BreedId == breedId : true);
        filter = filter.And(e => coatId != -1 ? e.CoatId == coatId : true);
        filter = filter.And(e => genderId != -1 ? e.GenderId == genderId : true);
        filter = filter.And(e => minAge != -1 ? e.age >= minAge : true);
        filter = filter.And(e => maxAge != -1 ? e.age <= maxAge : true);

        string pseudo = isSearchingOwn ? player.Pseudo : ownerPseudo;
        filter = filter.And(e => !string.IsNullOrEmpty(ownerPseudo) ? e.Owner.Pseudo.Equals(pseudo, StringComparison.InvariantCultureIgnoreCase) : true);
        filter = filter.And(e => minHeight > 0 ? e.FinalHeight >= minHeight : true);
        filter = filter.And(e => maxHeight > 0 ? e.FinalHeight <= maxHeight : true);
        filter = filter.And(e => minWeight > 0 ? e.FinalWeight >= minWeight : true);
        filter = filter.And(e => maxWeight > 0 ? e.FinalWeight <= maxWeight : true);
        filter = filter.And(e => character.All(c => (e.character & c) == c));

        for (int i = 0; i < abilitySkillValues.Count; i++)
        {
            filter = filter.And(
                AbilitySkillFilter
                (
                    (Criteria.ModifierE)abilitySkillModifiers[i], // ">", "<", or "="
                    i,
                    abilitySkillValues[i] // value entered by the user for the current ability
                )
            );
        }

        for (int i = 0; i < abilityPotentialValues.Count; i++)
        {
            filter = filter.And(
                AbilityPotentialFilter
                (
                    (Criteria.ModifierE)abilityPotentialModifiers[i], // ">", "<", or "="
                    i,
                    abilityPotentialValues[i] // value entered by the user for the current ability
                )
            );
        }
        return await GetAll(filter);
    }

能力过滤函数:

static Expression<Func<Animal, bool>> AbilitySkillFilter(Criteria.ModifierE modifier, int abilityId, int userValue)
    {
        if (modifier == Criteria.ModifierE.More) // User chose ">"
            return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId)
                ? e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId).Value >= userValue
                : value <= 0;
        else if (modifier == Criteria.ModifierE.Equal) // User chose "<"
            return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId)
                ? e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId).Value == userValue
                : value == 0;
        else if (modifier == Criteria.ModifierE.Less) // User chose "<"
            return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId)
                ? e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId).Value <= userValue
                : value >= 0;
        else
            return null;
    }

    static Expression<Func<Animal, bool>> AbilityPotentialFilter(Criteria.ModifierE modifier, int abilityId, int userValue)
    {
        if (modifier == Criteria.ModifierE.More)
            return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId)
                ? e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId).Value >= userValue
                : e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.BirthPotential && a.AbilityId == abilityId).Value >= userValue;
        else if (modifier == Criteria.ModifierE.Equal)
            return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId)
                ? e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId).Value == userValue
                : e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.BirthPotential && a.AbilityId == abilityId).Value == userValue;
        else if (modifier == Criteria.ModifierE.Less)
            return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId)
                ? e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId).Value <= userValue
                : e.AllAbilities.FirstOrDefault(a => a.TypeId == (int)Ability.TypeE.BirthPotential && a.AbilityId == abilityId).Value <= userValue;
        else
            return null;
    }

解释:
在数据库中,具有 TypeId == PotentialTypeId == SkillAbility 行可能不存在,而 TypeId == BirthPotential 始终存在。

如果有人对为什么此查询产生如此可怕的输出有任何改进建议,我将不胜感激。 如果您需要更多信息,请不要犹豫。

解决方案:

它终于成功了,多亏了 juharr 提议(使用简单的 if 而不是三元的 if 如果不需要则不添加子句),结合 Ivan Stoev解决方案。
以年龄、性别、物种、伪装、最小身高、最大身高、性格、一项技能能力和一项潜在能力为标准,这是新的 SQL 输出:将近 70 000 行到 60 !
Result here

非常感谢!

在进行动态过滤时,尽量在表达式之外进行更多的静态计算。这样您将获得更好的查询,因为目前 EF 不会优化常量表达式,除了构建静态 IN (...) 列表条件。

但是您当前代码的主要问题是 FirstOrDefault 在您的能力过滤器中的用法。通常尽量避免使用任何可能导致 SQL 子查询的查询构造类型,因为如您所见,出于某种原因 EF 嵌套了所有子查询,因此您会得到那个怪异的 SQL 和错误。安全的构造是使用 Any,它被翻译成 SQL EXISTS 子查询 w/o 嵌套。

所以试试这个,看看你会得到什么:

static Expression<Func<Animal, bool>> AbilitySkillFilter(Criteria.ModifierE modifier, int abilityId, int userValue)
{
    Expression<Func<GameAnimal, bool>> filter = null;
    bool includeMissing = false;
    if (modifier == Criteria.ModifierE.More) // User chose ">"
    {
        filter = e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId && a.Value >= userValue);
        includeMissing = userValue <= 0;
    }
    else if (modifier == Criteria.ModifierE.Equal) // User chose "="
    {
        filter = e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId && a.Value == userValue);
        includeMissing = userValue == 0;
    }
    else if (modifier == Criteria.ModifierE.Less) // User chose "<"
    {
        filter = e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId && a.Value >= userValue);
        includeMissing = userValue >= 0;
    }
    if (filter != null && includeMissing)
        filter = filter.Or(e => !e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Skill && a.AbilityId == abilityId));
    return filter;
}

static Expression<Func<Animal, bool>> AbilityPotentialFilter(Criteria.ModifierE modifier, int abilityId, int userValue)
{
    if (modifier == Criteria.ModifierE.More)
        return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId)
            ? e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId && a.Value >= userValue)
            : e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.BirthPotential && a.AbilityId == abilityId && a.Value >= userValue);
    else if (modifier == Criteria.ModifierE.Equal)
        return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId)
            ? e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId && a.Value == userValue)
            : e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.BirthPotential && a.AbilityId == abilityId && a.Value == userValue);
    else if (modifier == Criteria.ModifierE.Less)
        return e => e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId)
            ? e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.Potential && a.AbilityId == abilityId && a.Value <= userValue)
            : e.AllAbilities.Any(a => a.TypeId == (int)Ability.TypeE.BirthPotential && a.AbilityId == abilityId && a.Value <= userValue);
    else
        return null;
}