StudentHelper.cs
Ho una classe helper chiamata StudentHelper dove chiama la classe di deposito per ottenere tutti i dettagli degli studenti. Di seguito è riportato il codice per ottenere i dettagli dello studente.
model = await _repo.FilterAsync<StudentRbCasesLink>(s =>
containsTest(options, list, s, countyId) && s.student.Deleted = false && s.student.studentId !=0,
s=> s.student,
s => s.studentRbCase,
s=> s.studentRbCase.CurrentCounty,
s=> s.studentRbCase.Abawdtype);
dto => _mapper.Map<IEnumerable<Objects.DTO.StudentRbCases>>(model);
containsTest è un metodo che esegue tutto il filtraggio in base ai parametri di input. Questo metodo restituisce il valore booleano in base al filtro applicato e il valore booleano viene inviato alla classe repo.
private bool containsTest(string options, Dictionary<string,string> list,
StudentRbCasesLink s,int countyId){
if(options.contains("FirstName")){
firstName = list["FirstName"].ToLower().ToString();
predicate = firstName != "&county" ?
(s.student.FirstName.ToLower().contains(firstName)).Tostring() :
"false";
}
if(options.contains("LastName")){
lastName = ............................
}
........................
return convert.ToBoolean(predicate);
}
Di seguito è riportato il metodo FilterAsync effettivo che si trova nella classe repo. RepositoryClass.cs
public async Task<IQueryable<T>> FilterAsync<T>(Expression<Func<T,bool>> predicate, params Expression<Func<T,object>>[] includes) where T : class
{
return await Task.Run(()=> {
var query = _context.Set<T>().where(predicate).AsQueryable();
return includes.Aggregate(query, (current, includeProperty)
=> current.Include(includeProperty).asQueryable());
});
}
Lasciami descrivere chiaramente il problema. Sto facendo una ricerca in base alla funzionalità dei parametri qui. Non appena la classe studenthelper acquisisce tutti i parametri, viene colpito filterasync in studenthelper che a sua volta colpisce il metodo effettivo filterasync nella classe Repository. Quindi, quando vedo il profilo SQL per l'SQL convertito, sta mostrando la query SQL con tutti i join inclusi negli include in filterasync ma arrivando a dove condizione applica solo la condizione s.student! = 0 in SQL che sta rendendo la query molto lenta (non applicando tutte le condizioni / filtri in cui la condizione lo rende lento). Non sta applicando alcuna condizione menzionata nel metodo containsTest al momento della generazione di SQL, ma una volta che il cursore raggiunge la successiva linea Auto-mapper (utilizzata per convertire i modelli in dtos), il cursore sta colpendo containsTest Method e sta facendo tutti i filtri. Sotto i cofani, SQL sta recuperando tutti i record e mettendoli in memoria e applicando i filtri quando si colpisce Auto-mapper.
Ho visto altri post in cui le persone suggerivano di mettere il predicato Espressione> invece del predicato Func. Ma, il mio codice ha già Expression. Qualcuno può aiutarmi a scrivere il metodo containsTest, in modo tale che la condizione venga applicata mentre viene convertita in SQL Query. Nota che EF utilizzato è EntityFrameworkcore (1.1.2) Grazie per l'aiuto.
Ecco come ho risolto il problema: Innanzitutto, come suggerito sopra, ho rimosso il metodo containsTest e ho scritto tutto nello stesso metodo.
var predicate = PredicateBuilder.True<StudentRbcasesLink>();
if (options.Contains("FirstName"))
{
firstName = list["FirstName"].ToLower().ToString();
Expression<Func<StudentRBbasesLink, bool>> expressionFirstName = x =>
x.Student.FirstName.StartsWith(firstName);
if (firstName != "&county") {
predicate = predicate.And(x => x.Student.FirstName.StartsWith(firstName));
}
}
if (options.Contains("LastName"))
{
lastName = list["LastName"].ToLower().ToString();
Expression<Func<StudenRbcasesLink, bool>> expressionLastName = x =>
x.Student.LastName.StartsWith(lastName);
predicate = !string.IsNullOrEmpty(firstName) ? predicate.And(x =>
x.Participant.LastName.StartsWith(lastName)) : expressionLastName;
...........
}
...............................
La grande domanda è, come fare dinamicamente Logical And, Logical OR sulle espressioni.Below è il codice che lo ha reso possibile. ( PredicateBuilder è la classe che viene utilizzata per eseguire dianmicamente le operazioni logiche sui predicati di espressioni).
public static class PredicateBuilder
{
/// <summary>
/// Creates a predicate that evaluates to true.
/// </summary>
public static Expression<Func<T, bool>> True<T>() { return param => true; }
/// <summary>
/// Creates a predicate that evaluates to false.
/// </summary>
public static Expression<Func<T, bool>> False<T>() { return param => false; }
/// <summary>
/// Creates a predicate expression from the specified lambda expression.
/// </summary>
public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
/// <summary>
/// Combines the first predicate with the second using the logical "and".
/// </summary>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
/// <summary>
/// Combines the first predicate with the second using the logical "or".
/// </summary>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
/// <summary>
/// Negates the predicate.
/// </summary>
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
{
var negated = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
}
/// <summary>
/// Combines the first expression with the second using the specified merge function.
/// </summary>
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// zip parameters (map from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with the parameters in the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// create a merged lambda expression with parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
class ParameterRebinder : ExpressionVisitor
{
readonly Dictionary<ParameterExpression, ParameterExpression> map;
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
Il problema era che stavo facendo operazioni logiche sui booleani invece che sulle espressioni. E le operazioni logiche sulle espressioni possono essere raggiunte solo attraverso la precedente classe prediateBuilder.
Di seguito è riportato il codice per FilterAsync
model = await _repo.FilterAsync<StudentRbCasesLink>(
predicate
s => s.student.studentId !=0,
s => s.student,
s => s.studentRbCase,
s => s.studentRbCase.CurrentCounty,
s => s.studentRbCase.Abawdtype);
dto => _mapper.Map<IEnumerable<Objects.DTO.StudentRbCases>>(model);
Dopo aver modificato il codice, sono passato a SQL Server Profiler e ho verificato come si forma la query. Ha tutti i join necessari con le clausole where where dove la clausola cambia dinamicamente secondo la ricerca.
Dopodiché, la ricerca è iniziata in 3 secondi quando sono state date combinazioni (ovvero 2 o 3 periodi alla volta). Nota: in precedenza, la ricerca utilizzata richiedeva da 1 a 2 minuti. La creazione di alcuni indici e statistiche per le colonne necessarie lo ha ridotto a meno di 1 secondo .