2016-03-29 4 views
2

Вчера хороший человек помог мне построить PredicateBuilder для Linq to Entitieshere.
Кажется, что все в порядке, однако полный запрос порождает эту отвратительную вещь длиной 70 000 строк here (слишком длинная для вставки) и повышение SQL statement is nested too deeply.Почему этот запрос поднимает «заявление SQL слишком глубоко вложен»?

Вот контекст:
Пользователь ищет список животных, соответствующих его критериям, в частности, в отношении способностей.
В GUI для каждого типа способностей (например: «управляемость», «маневренность» и т. Д.) Пользователь может выбрать модификатор («>», «<» или «=») и значение.
Например, он может хотеть, чтобы отобразить «Все животные, которые имеют потенциальную способность> 3 в маневренности», или «Все животные, которые обладают способностью умение < 10 в maniability и способности потенциального = 2 в маневренности»

О базе:

Player с колоннами Id
Animal с колоннами Id
Ability с колоннами:

  • Id
  • AnimalId
  • TypeId (представляющий Enum, который может быть "Потенциал", "BirthPotentiel" или "навык")
  • AbilityId (представляющий Enum, который может быть "ловкость", или "Maniability")
  • Value

Таким образом, каждое животное имеет свойство AllAbilities которое является ICollection<Ability>.

Это функция поиска (все параметры были ранее введены или оставлены пустыми пользователем в графическом интерфейсе).

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; 
    } 

Объяснение:
В базе данных, Ability строки с TypeId == Potential или TypeId == Skill не может существовать, в то время как TypeId == BirthPotential всегда делают.

  • В случае TypeId == Potential не существует для текущего животного и ток AbilityId, я хочу сравнить значение пользователя с TypeId == BirthPotential значением строки (который всегда существует).
  • В случае, если TypeId == Skill не существует для текущего животного и текущего AbilityId, я хочу сравнить значение пользователя с 0.

Если у кого есть какие-либо предположения о том, почему этот запрос производит такой ужасный вывод и имеет улучшение, я был бы очень благодарен. Не стесняйтесь, если вам нужна дополнительная информация.

РЕШЕНИЕ:

Это, наконец, работает, благодаря juharr предложения (используя простой if вместо трехкомпонентной if не добавить пункт, если нет необходимости), в сочетании с Ivan Stoev раствором.
С критериями по возрасту, полу, видам, псевдо, минимкам, максимуму, характеру, навыкам и одной потенциальной способности, вот новый выход SQL: почти 70 000 строк до 60!
Result here

Большое спасибо!

+1

Является ли SQL Server работает на той же машине, что и приложение? Если это так, возможно, получить все отфильтрованные данные (без фильтров в циклах) в локальную коллекцию, а затем сделать фильтрацию циклов на этом параметре. – schlonzo

+1

Вместо того чтобы помещать if condtion в предикат, почему бы не условно применять их как 'if (speciesId! = -1) filter = filter.And (e => e.SpeciesId == speciesId);'. Это может помочь уменьшить сложность запроса. – juharr

+0

@schlonzo: Да, это так. Поэтому вы бы рекомендовали «ждать GetAll (filter)» дважды, один прямо перед циклами и один в конце? @juharr: Действительно, вы правы, я отредактирую свой код таким образом. Однако, если пользователь вводит все критерии, он все равно не сможет получить результаты: ( –

ответ

1

Выполняя динамическую фильтрацию, попробуйте сделать более статическую оценку за пределами выражений. Таким образом, вы получите более качественные запросы, поскольку в настоящее время EF не оптимизирует постоянные выражения, за исключением построения статических критериев списка IN (...).

Но основная проблема с вашим текущим кодом - это использование FirstOrDefault внутри ваших фильтров возможностей. В общем, старайтесь избегать использования любого типа конструкции запроса, который может привести к подзапросу SQL, потому что, как вы видели, по какой-то причине EF вложен все подзапросы, следовательно, вы получаете этот чудовище SQL и ошибку. Безопасная конструкция заключается в использовании Any, которая переводится в SQL EXISTS подзапрос без вложенности.

Так попробовать это и посмотреть, что вы получите:

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; 
} 
+1

Вы, вероятно, не можете себе представить, насколько я благодарен (снова)! Не только он отлично работает, но я также понял, что происходит на самом деле, и сможет использовать эту базу везде в моем проекте. Я отредактировал свой первый пост с новым выходом SQL, 70 000 строк до 60, невероятно! Еще раз спасибо, что нашли время, чтобы помочь мне, я надеялся, что смогу сделать больше, чем просто принять ответ;) –

+0

Добро пожаловать, рад, что помог :) Удачи вам в вашем проекте, приветствия! –