Yesterday, a nice person helped me to build a PredicateBuilder
for Linq to Entities
here.
Seems to work fine, however the complete query generates this hideous 70 000 lines long thing here (too long to be pasted), and raising SQL statement is nested too deeply
.
Here is the context :
The user is looking for a list of animals matching his criteria, notably regarding abilities.
In the GUI, for each ability type (ex: "maniability", "agility" etc.), the user can select a modifier (">", "<", or "=") and a value.
For example, he may want to display "All animals that have an ability potential > 3 in agility", or "All animals that have an ability skill < 10 in maniability and ability potential = 2 in agility"
About the database:
Player
with columns Id
Animal
with columns Id
Ability
with columns :
Id
AnimalId
TypeId
(representing Enum which can be "Potential", "BirthPotentiel" or "Skill")AbilityId
(representing Enum which can be "Agility", or "Maniability")Value
Thus, each animal has an AllAbilities
property which is an ICollection<Ability>
.
Here is the search function (all parameters have previously been entered, or left blank, by the user in the 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);
}
And the abilities filter functions :
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;
}
Explanation :
In database, Ability
rows with TypeId == Potential
or TypeId == Skill
may not exist, while TypeId == BirthPotential
always do.
- In case
TypeId == Potential
does not exist for the current animal and the currentAbilityId
, I want to compare user value withTypeId == BirthPotential
row value (which always exists). - In case
TypeId == Skill
does not exist for the current animal and the currentAbilityId
, I want to compare user value with 0.
If anyone has any suggestion about why this query is producing such an horrible ouput and has an improvement, I would be really grateful. Don't hesitate if you need more information.
SOLUTION:
It finally works, thanks to juharr
proposal (using simple if
instead of ternary if
to not add the clause if not necessary), combined to Ivan Stoev
solution.
With criteria on age, gender, species, pseudo, minheight, maxheight, character, one skill ability and one potential ability, here is the new SQL output: almost 70 000 lines to 60 !
Result here
Thanks a lot !