为什么此查询引发 "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"
关于数据库:
Player
列 Id
Animal
列 Id
Ability
列:
Id
AnimalId
TypeId
(代表枚举,可以是"Potential"、"BirthPotentiel"或"Skill")
AbilityId
(代表Enum,可以是"Agility",或者"Maniability")
Value
因此,每只动物都有一个 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 == Potential
或 TypeId == Skill
的 Ability
行可能不存在,而 TypeId == BirthPotential
始终存在。
- 如果当前动物和当前
AbilityId
不存在 TypeId == Potential
,我想将用户值与 TypeId == BirthPotential
行值(始终存在)进行比较。
- 如果
TypeId == Skill
对于当前动物和当前 AbilityId
不存在,我想将用户值与 0 进行比较。
如果有人对为什么此查询产生如此可怕的输出有任何改进建议,我将不胜感激。
如果您需要更多信息,请不要犹豫。
解决方案:
它终于成功了,多亏了 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;
}
昨天,一个好人帮我为 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"
关于数据库:
Player
列 Id
Animal
列 Id
Ability
列:
Id
AnimalId
TypeId
(代表枚举,可以是"Potential"、"BirthPotentiel"或"Skill")AbilityId
(代表Enum,可以是"Agility",或者"Maniability")Value
因此,每只动物都有一个 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 == Potential
或 TypeId == Skill
的 Ability
行可能不存在,而 TypeId == BirthPotential
始终存在。
- 如果当前动物和当前
AbilityId
不存在TypeId == Potential
,我想将用户值与TypeId == BirthPotential
行值(始终存在)进行比较。 - 如果
TypeId == Skill
对于当前动物和当前AbilityId
不存在,我想将用户值与 0 进行比较。
如果有人对为什么此查询产生如此可怕的输出有任何改进建议,我将不胜感激。 如果您需要更多信息,请不要犹豫。
解决方案:
它终于成功了,多亏了 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;
}