Aus der Praxis: SQL Linq Expressions kombinieren
avatar

Bei der Umsetzung von Projekten bin ich nun des Öfteren auf die Anforderung gestoßen mehrere Linq Expressions mit einander logisch zu verknüpfen. Und jedes mal muss ich wieder die Suchmasche anwerfen und danach recherchieren. Daher habe ich die Lösung für dieses Problem nun nachfolgend noch mal zusammengefasst.

Die Anforderung:

Eine Liste von Suchbegriffen soll in unterschiedlichen Spalten einer Datenbanktabelle gesucht werden (logisches OR auf die Spalteninhalte). Jeder der Suchbegriffe soll mit einem logischen UND verknüpft werden, so dass die Ergebnismenge immer weiter verfeinert wird.

Man läuft also mit einer Schleife über die Liste der Suchbegriffe und baut die einzelnen Where-Klauseln die Suche in den Spalten zusammen. Aber wie bekommt man nun das UND um diese generierten Or-Anweisungen?

Die Lösung steckt in folgender Helper-Klasse:

public class ExpressionParameterReplacer : ExpressionVisitor
    {
        private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; }

        public ExpressionParameterReplacer
        (IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
        {
            ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();

            for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
            { ParameterReplacements.Add(fromParameters[i], toParameters[i]); }
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            ParameterExpression replacement;

            if (ParameterReplacements.TryGetValue(node, out replacement))
            { node = replacement; }

            return base.VisitParameter(node);
        }
    }

Vielen Dank an:  https://www.codeproject.com/Articles/895951/Combining-expressions-to-dynamically-append-criter

Dank dieser Klasse kann nun die Methode für das logische AND implementiert werden.

public static Expression<Func<MyEntity, Boolean>>
        AndAlso(Expression<Func<MyEntity, Boolean>> left, Expression<Func<MyEntity, Boolean>> right)
        {
            Expression<Func<MyEntity, Boolean>> combined = Expression.Lambda<Func<MyEntity, Boolean>>(
                Expression.AndAlso(
                    left.Body,
                    new ExpressionParameterReplacer(right.Parameters, left.Parameters).Visit(right.Body)
                    ), left.Parameters);

            return combined;
        }

Mit der Methode für das logische UND kann nun die komplette Suchlogik implementiert werden. Die Expressions für das Durchsuchen der Spalteninhalte werden in einer Liste gespeichert.

Diese wird im zweiten Schritt weiterverarbeitet und mit der zuvor genannten Methodik UND-verknüpft.

public IEnumerable<SearchFullTextResultViewModel> GetMyEntitysByFulltextSearch(string searchTerm)
        {
            var chars = new Char[] { '&', ' ', '+' };
            String[] parts = searchTerm.Split(chars, StringSplitOptions.RemoveEmptyEntries);

            var query = DatabaseContext.MyEntities.AsQueryable();
            //get all filter parts to search in the following 4 fields using OR
            var whereItemList = new List<Expression<Func<MyEntity, bool>>>();
            foreach (var part in parts)
            {
                var trimPart = part.Trim();
                Expression<Func<MyEntity, bool>> whereClause = (entity => 
				entity.Col1.Contains(trimPart) ||  
				entity.Col2.Contains(trimPart) || 
				entity.Col3.Contains(trimPart) ||
				entity.Col4.Contains(trimPart));
				
                whereItemList.Add(whereClause);
            }
            // get all 'Or statements' to create an AND of them
            for (int i = 0; i < whereItemList.Count; i+=2)
            {
                if (whereItemList.Count > 1 && (i + 1) < whereItemList.Count)
                {
                    query = query.Where(AndAlso(whereItemList[i], whereItemList[i + 1]));
                }
                else if (i < whereItemList.Count)
                {
                    query = query.Where(whereItemList[i]);
                }
            }

            return query.OrderBy(entity => entity. Col1)
                                       .Take(1000)
                                       .Select(entity => new SearchFullTextResultViewModel { Col1= entity. Col1, Col2= entity.Col2, Col3= entity.Col3, Col4= entity. Col4}).ToList();
        }

Ich hoffe der Artikel hat euch gefallen kann euch helfen falls ihr mal auf die gleiche Anforderung trefft.

Schreibe einen Kommentar