Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc

LINQ - jak na dynamickou podmínku

LINQ asi není potřeba nijak detailně představovat. Asi každý .NET vývojář se již setkal s LINQem, nejčastěji ve formě dotazu, kde byla jeho podoba již programově dána a měnit se mohl snad jen nějaký parameter dotazu:

public void Linq1() 
{
    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
 
    var lowNums =  from n in numbers
                   where n < 5
                   select n;
 
    foreach (var x in lowNums) 
    {
        Console.WriteLine(x);
    }
}

 

Poznámka: tento příklad byl převzat ze stránky 101  LINQ Samples - výborný zdroj pro studium.

Jak to ale řešit, pokud chceme podmínku sestavit dynamicky, například provozujeme realitní server a chceme jej rozšířit o službu, která na základě zaslaných podmínek v textové podobě vrátí seznam vyhovujících nemovitosti?

 

Databáze nemovitostí

Řešení se pokusím vysvětli v tomto článku. Příklad bude maximálně zjednodušen, proto nebudeme dělat žádné rozhraní webové služby, ale spokojíme se s výstupem na konzolu. Též náše realitní databáze bude maximálně zjednodušena a u každého objektu si budeme držet jen tři údaje:

  • název místa, kde nemovitost leží
  • typ nemovitosti (výběr z číselníku)
  • počet ložnic

Class diagram pak vypádá takto:

ClassDiagram1

 

Naším vstupem pak bude řetězec ve tvaru: Název vlastnosti:přípustné hodnoty|Název vlastnosti: přípustné hodnoty,

Při předání řetězce "Location:Humpolec, Polna|PropertyType:Castle,Flat,Chateu,TerraceHouse|Bedrooms:1-20" budeme očekávat seznam všech bytů, zámků a hradů s dvěmi až dvaceti ložnicemi nacházejícími se v lokalitě Polná a Humpolec. Při předání řetězce "Bedrooms:5-10|PropertyType:Castle" pak zase seznam všech hradů s pěti až deseti ložnicemi.

Potřebovali bychom tedy nějak dynamicky sestavit omezující podmínku výběru, neboli filtr, neboli predikát - tedy pokud bych použil příklad ze začátku článku, potřebujeme dynamicky sestavit část "where n < 5".

To vyřešíme tak, že si definujeme metody pro sestavení každého filtru (predikátu), který bychom mohli potřebovat. Náš případ je zjednodušený - máme tři vlastnosti podle kterých chceme vyhledávat, budeme tedy definovat tři metody pro:

  • získání predikátu omezujícího na základě lokality
  • získání predikátu omezujícího na základě typu nemovitosti
  • získání predikátu omezujícího na základě rozsahu počtu ložnic

 

Příprava - převedení řetězce na hodnotu z číselníku

Požadovaný  typ nemovitosti je předáván ve formě řetězce, kde jsou jednotlivé typy oddělené čárkou. Tento řetězec musíme převést na kolekci hodnot našeho enumerátoru. K tomu nám poslouží statická funkce:

public static IEnumerable<PropertyType> GetPropertyTypesFromString(string types)
{
   List<PropertyType> propertyTypes = new List<PropertyType>();
   PropertyType propertyType;
 
   var rawPropertyTypes = types.Split(",".ToCharArray());
    
   foreach (string rawPropertyType in rawPropertyTypes)
   {
      propertyType = (PropertyType) Enum.Parse(typeof(PropertyType), rawPropertyType);
      propertyTypes.Add(propertyType);
   }
 
   return propertyTypes;
}

 

 

Metody pro získání predikátů

Bude se jednat o statické metody. Pro získání predikátu omezujího na základě lokality nemovitosti použijeme tuto statickou metodu:

public static Expression<Func<IRealProperty, bool>> IsInLocation(string locations)
{
     var predicate = PredicateBuilder.False<IRealProperty>();
     var rawLocations = locations.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
    
     foreach (string location in rawLocations)
     {
        string tempLocation = location.Trim();
        predicate = predicate.Or(p => p.Location.Contains(tempLocation));
     }
    
     return predicate;
}

Pokud by snad někoho zarážel příkaz string tempLocation = location.Trim(); - je tam jednoduše proto, že LINQ věci se vykonávají v okamžiku, kdy jsou použity, ne kdy jsou definovány. Pokud bychom tento řádek vynechaly a další řádek použili ve tvaru predicate = predicate.Or(p => p.Location.Contains(location)); byla by použita hodnota proměnné location v okamžiku volání, tedy v tomto případě poslední název lokace. Díky příkazu string tempLocation = location.Trim();  je tedy pro každou podmínku použita vlastní proměnná držící jedinečnou hodnotu lokace v okamžiku použití.

V příkladu jsou použiti metody třídy PredicateBuilder - to je pomocná třída pro sestavování predikátů. Pokud bych to popsal polopaticky, tak předchozí funkce sestaví podmínku (v případě, že vyhledávácí řetězec obsahoval "Location:Humpolec, Polna|PropertyType:Castle,Flat,Chateu,TerraceHouse|Bedrooms:1-20" :

podmínka = False Or Location='Humpolec' Or Location='Polna'

 

Třídu PredicateBuilder jsem si vypůjčil ze stránky Dynamically Composing Expression Predicates - lze ji v různých obdobách nalézt i jinde, ale myslím, že tohle byl původní zdroj-

 

Podobně by vypadaly i metody pro omezení dle typu nemovitosti:
 
public static Expression<Func<IRealProperty, bool>> IsOfType(string types)                                  
{
   var predicate = PredicateBuilder.False<IRealProperty>();
   var propertyTypes = GetPropertyTypesFromString(types);
 
   foreach (PropertyType propertyType in propertyTypes)
   {
      PropertyType tempType = propertyType;
      predicate = predicate.Or(p => p.Type ==tempType);
   }
 
   return predicate;
}

 

a i metoda omezující dle poču ložnic:

public static Expression<Func<IRealProperty, bool>> HasBedroomCount(int min, int max)
{
    var predicate = PredicateBuilder.False<IRealProperty>();
    
    predicate = predicate.Or(p => p.BedroomCount >=min && p.BedroomCount<=max);
    
    return predicate;
}

 

Zpracování vyhledávací podmínky

Po definici jednotlivých metod již stačí jednoduše zpracovat předávaný řetězec a jednotlivé metody zkombinovat. Pro jednoduchost nejsou přidány žádné kontroly formální správnosti předávaného řetězce - spoléháme se, že tvar bude dodržen:

string searchConditions = "Location:Humpolec, Polna|PropertyType:Castle,Flat,Chateu,TerraceHouse|Bedrooms:1-20";
var conditions = searchConditions.Split("|".ToCharArray());
 
var predicate = PredicateBuilder.True<IRealProperty>();
 
foreach (string condition in conditions)
{
   var keyValuePair = condition.Split(":".ToCharArray());
 
   switch (keyValuePair[0])
   {
      case "PropertyType":
         predicate = predicate.And(RealProperty.IsOfType(keyValuePair[1]));
         break;
 
      case "Bedrooms":
         var bedroomRange = keyValuePair[1].Split("-".ToCharArray());
         int min= int.Parse(bedroomRange[0]);
         int max = int.Parse(bedroomRange[1]);
         predicate = predicate.And(RealProperty.HasBedroomCount(min, max));
         break;
 
      case "Location":
         predicate = predicate.And(RealProperty.IsInLocation(keyValuePair[1]));
         break;
   }
}

 

Provedení dotazu

Nyní stačí získaný předpis jen zkompilovat, použít v LINQ dotazu a výsledek vypsat:

Func<IRealProperty, bool> SatisfiesSearchConditions = predicate.Compile();
 
var query = from property in propertis
            where SatisfiesSearchConditions(property)
            select property;
 
foreach(RealProperty property in query)
{
   Console.WriteLine(property.ToString());
}

 

A je to.

 

Závěr

Snažil jsem ukázat, jak lze dynamicky konstruovat omezovací podmínky LINQ dotazu. V některém volném pokračování ukáži jak lze podobně provést i seřazení výsledků a i reálné použítí. Ačkoliv byl tento článek založen na zjednodušeném příkladě, myslím že není problém přenést principy zde ukázané jinam.

Zdrojový kód příkladu je jako obvykle přiložen v zip balíčku.

Na závěr tedy ještě přehledně dva odkazy zmíněné v článku, kde lze nalézt další informace a studijní materiál:

Zveřejněno Sunday, December 14, 2008 10:49 AM by mstr
Vedeno pod: , ,

Attachment(s): LinqSearch.zip

Upozornění na nové komentáře

Pokud chčeš dostávat upozornění emailem na změny u toho příspěvku,tak se zaregistruj zde.zde

Odebírat komentáře k tomuto příspěvku pomocí RSS

Komentář

# re: LINQ - jak na dynamickou podmínku

Velmi pekne reseni je, vcetne rozpitvani napsal Tomas Petricek na http://tomasp.net/blog/dynamic-linq-queries.aspx.

Sunday, December 14, 2008 12:23 PM by jirka

# re: LINQ - jak na dynamickou podmínku

to jirka: dík za odkaz, odkazují na něj i na stránkách Dynamically Composing Expre... zmíněných v článku. Já se snažil jen o rychlé vysvětlení principu rychlého dynamického seskládání And a Or LINQ dotazu.

Sunday, December 14, 2008 12:45 PM by mstr

Vytvoření nového komentáře

(povinný) 
povinný 
(povinný) 
Opiš čísla, která vidíš na obrázku: