Laden...

Umsetzung eines dynamischen Filters + LINQ to Entities

Erstellt von Tris vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.485 Views
T
Tris Themenstarter:in
18 Beiträge seit 2009
vor 13 Jahren
Umsetzung eines dynamischen Filters + LINQ to Entities

Hallo,

zur Vorgeschichte:
Ich verwende als DB den MS SQL Server 2008, als Abfragesprache LINQ to Entities mit .NET 4.

Nach einer Loginmaske wird automatisch eine Filter-GUI angezeigt, wo man die Möglichkeit hat, benutzerdefinierte Filter zu erstellen. So kann man seine eigenen Filter, bestehend aus einem FilterField (der Feldname, in dem gesucht werden soll), einem FilterOperator (der Vergleichsoperator) und einer TextBox als Suchbegriff, erstellen.

Um eine gefilterte Abfrage / ObjectQuery zu erstellen, habe ich derzeit so ein riesiges Codegerüst, dass ich mich frage, ob es vielleicht noch eine andere, einfachere Möglichkeit gibt, die ich bisher übersehen habe / nicht kannte.

So schaut's aus:


            // "BaseQuery"
            var fQuery = from a in objContext.auftraege
                         .Include("personen")
                         select vg;


            foreach (var filterItem in currentFilterList)
            {
                switch (filterItem.FilterField.Description)
                {
                    #region Nachname
                    case "Nachname":
                        string strNachname = filterItem.TextValue;

                        switch (filterItem.FilterOperator.Description)
                        {
                            case "enthält":
                                fQuery = fQuery.Where(q => q.personen.Count(p => p.Nachname.Contains(strNachname)) > 0);
                                break;

                            case "beginnt mit":
                                fQuery = fQuery.Where(q => q.personen.Count(p => p.Nachname.StartsWith(strNachname)) > 0);
                                break;

                            case "endet auf":
                                fQuery = fQuery.Where(q => q.personen.Count(p => p.Nachname.EndsWith(strNachname)) > 0);
                                break;
                        }
                        break;
                    #endregion

                    #region Eingangsdatum
                    case "Eingangsdatum":
                        DateTime dtEingangsdatum = filterItem.DateValue;

                        switch (filterItem.FilterOperator.Description)
                        {
                            case "liegt vor / ist gleich":
                                fQuery = fQuery.Where(q => q.int_Eingangsdatum.Date <= dtEingangsdatum.Date);
                                break;

                            case "liegt nach / ist gleich":
                                fQuery = fQuery.Where(q => q.int_Eingangsdatum.Date >= dtEingangsdatum.Date);
                                break;
                        }
                        break;
                    #endregion

                    #region OrdNummer
                    case "OrdNummer":
                        string strOrdNummer= filterItem.TextValue;

                        switch (filterItem.FilterOperator.Description)
                        {
                            case "enthält":
                                fQuery = fQuery.Where(q => q.int_OrdNummer.Contains(strOrdNummer));
                                break;

                            case "beginnt mit":
                                fQuery = fQuery.Where(q => q.int_OrdNummer.StartsWith(strOrdNummer));
                                break;

                            case "endet auf":
                                fQuery = fQuery.Where(q => q.int_OrdNummer.EndsWith(strOrdNummer));
                                break;
                        }
                        break;
                    #endregion
                }

            }

... und das ist nur auszugsweise.

Da es insgesamt 14 Feldnamen gibt, die ihrerseits nach 3 Filteroperatoren gefiltert werden können, sieht der ganze Code echt unübersichtlich aus.

Vielleicht hat von euch jemand einen Tipp / Link mit Codebeispielen, wie ich solche Abfrage-Queries besser generieren kann.

Gruß,
Tris

P.S.
Ich dachte auch schon darüber nach, einfach alle Datensätze ab einem bestimmten Jahr abzurufen und dann per "Filter - Predicate" vom DataGrid rauszufiltern. Das scheint mir aber irgendwie keine gute Idee, zumal das über die Zeit ja nicht unbedingt weniger Datensätze in der DB werden.

5.742 Beiträge seit 2007
vor 13 Jahren

Hallo Tris,

verwende statt dieser switch Konstrukte lieber Klassen, z.B. ähnlich den folgenden:


public interface Filter
{
   bool Include(string predicate, string value);
}

public sealed class StartsWithFilter
 : IFilter
{
  public bool Include(string predicate, string value)
  {
    return value.StartsWith(predicate);
  }
}

//... und andere

Das hilft schon einmal, ein paar Redundanzen bei den Filtern zu beseitigen.

Evtl. müsstest du die Signatur noch etwas umgestalten, um direkt IQueryable verwenden zu können - ich weiß nicht, inwiefern das EF mit benutzerdefinierten Klassen zurechtkommt.

Zum Auswählen eines passenden Filters kannst du dir vorerst mit einem Dictionary<string, Filter> behelfen.
Alternativ kannst du auch hier Klassen verwenden.

Evtl. macht es auch mehr Sinn, die Filterung komplett clientseitig durchzuführen, um die Wartezeiten beim Abfragen zu reduzieren.
Aber das kommt wirklich auf die Menge der Daten an.

W
955 Beiträge seit 2010
vor 13 Jahren

Warum löst Du es nicht mit eSQL?

T
Tris Themenstarter:in
18 Beiträge seit 2009
vor 13 Jahren

Danke für die Antworten. Der Ansatz mit den Klassen klingt interessant, damit werde ich die nächsten Tage mal etwas "herumspielen". 😃

@witte
Ich vermute mal, dass bei Abfrageergebnissen per eSQL diese Änderungsverfolgung, welche ich im Moment durch den Objektkontext/-StateManager habe, fehlt? Das habe ich bisher eigentlich sehr gemocht (und funktionierte alles ohne mein "Zutun" g) und würde nur ungern darauf verzichten.

W
955 Beiträge seit 2010
vor 13 Jahren

Wie kommst Du darauf dass die Änderungsverfolgung bei eSQL fehlt? Das ist doch Job des ObjectContextes. Es ist doch egal woraus der Ausdrucksbaum gebaut wird. Es gibt sicherlich einige Unterschiede aber der Context sollte doch in der Lage sein modifizierte Entities zu erkennen egal womit sie materialisiert wurden.
Ich habe eSQL erwähnt weil ich mir vorstelle dass man bei diesem Problem einen String einfacher zusammenbasteln kann.

BTW, zur Erhöhung der Übersichtlichkeit wäre hier vllt das Erbauermuster angebracht.

S
902 Beiträge seit 2007
vor 13 Jahren

Hallo,

ich habe ein ähnliches problem bei uns auch mit eigenen Filterklassen gelöst, welche eine FilterExpression für IQueryable-Objekte zurückgeben.

Dazu habe ich mir ExpressionTrees geschnappt.

Beispiel eines Filter für Nachrichten, welche einen Autor besitzen müssen


public class NewsAutorNOTNullFilter:GenericFilterBase<News>
    {

        public override Expression<Func<News, bool>> FilterExpression
        {
            get 
            {
                Expression<Func<News, bool>> exp = (n) => n.news_autor != null;
                return exp;
            }
        }

        public override string FriendlyName
        {
            get { return "Autor muss gesetzt sein"; }
        }
    }

diesen Filter kann ich direkt in das .Where() eingeben.

mfg
serial