2009-12-08 7 views
5

Mam opcjonalną część zapytania, które musi zostać wykonane pod pewnymi warunkami. Oto przykładowy kod:Które ORM obsługuje tę

int cat = 1; 
int UserID = 12; 
string qry = "select * from articles"; 
if(cat > 0) 
    qry += " where categoryID = " + cat; 
if(UserID > 0) 
    qry += " AND userid = " + UserID; //The AND may be a WHERE if first condition is false 

Jak widać, mam instrukcję if w zapytaniu. Obecnie używam Entity Framework i nie obsługuje tego rodzaju scenariusz. Czy istnieje ORM, który obsługuje to?

Edytuj Próbowałem pozorować zapytanie. Ale mam około 20 zdań "IF", a kwerydy są bardzo długie.

W ORMs ja patrząc na to:

  • NHibernate
  • LLBLGen
  • Subsonic

Jestem otwarty na wszelkie ORM. Dzięki

+1

LLBLGen obsługuje to, nawet 20 z nich. Jako klauzule where są dodawane jeden lub wiele naraz. – PostMan

+0

@PostMan miałbyś tego przykład? – Luke101

+2

Możesz to zrobić w Entity Framework, patrz odpowiedź tt83 poniżej. Jego odpowiedź jest dla Linq na SQL, ale koncepcja jest taka sama dla Entity Framework. Nie trzeba wylewać dziecka z kąpielą. –

Odpowiedz

2

Prawdopodobnie można to zrobić z dowolnego dostawcy LINQ, ale wiem, że LightSpeed ORM obsługuje go:

var query = UnitOfWork.Articles; 
if (cat > 0) 
    query = query.Where(a => a.CategoryId == cat); 
+0

Próbowałem pozorować zapytanie. Ale mam około 20 zdań "IF", a kwerydy są bardzo długie. Czy nie jest możliwe powiązanie instrukcji IF w samym zapytaniu linq? – Luke101

+0

Jeśli łańcuch instrukcji if w kwerendzie LINQ, zostaną one przetłumaczone na SQL, a to, czy to zadziała, będzie zależeć od dostawcy LINQ. Ale możesz łączyć. Gdzie inwokacje są kontrolowane przez instrukcje typu "po stronie klienta". – itowlson

+0

Ponadto, jeśli Twój rzeczywisty przypadek użycia jest znacznie bardziej skomplikowany, możesz zmodyfikować swoje pytanie, aby nadać mu nieco smaku rzeczywistego przypadku użycia, tak aby nie otrzymywało się takich odpowiedzi jak moje, które opisują tylko banalny przypadek! – itowlson

0

zrobić tego rodzaju rzeczą w NHibernate cały czas.

(robiłem podobne rzeczy w Rails. Jestem trochę zaskoczony, że istnieją ORMs że nie robić wspierać.)

+0

Czy masz przykład tego, jak to zrobić w nhibernate? – Luke101

+0

Niełatwo - mamy własną abstrakcję nad NHibernate, której używam w 99,9% przypadków. Zobacz odpowiedź Kevina powyżej. – Ken

+0

Odpowiedź Kevina przybiła. –

9

można to zrobić przy użyciu LINQ do SQL ...

IQueryable<Article> query = yourDataContext.Articles; 

if (catId > 0) 
    query = query.Where(x => x.CategoryId == catId); 

return query.ToList(); 
+0

Edytowałem kod. czy można dodać wiele instrukcji where w zapytaniu linq? Zobacz kod w pytaniu – Luke101

+0

Tak. IQueryable

w takim przypadku odroczy wykonanie kodu SQL, dopóki go nie zmaterializujesz (wywołaj ToList itp.), Możesz dodać dowolną liczbę warunków, jak chcesz. Tylko wtedy, gdy wywołasz to ToList, faktycznie wykona SQL w bazie danych. –

+0

Czy mogę dodać inne warunkowe, takie jak to: query = query.Where (x => x.CategoryId == catId); query + = query.Where (x => x.userid == ID użytkownika); – Luke101

0

Możesz łatwo budować zapytania w ten sposób przy użyciu HQL NHibernate (Hibernate Query Language). Byłaby to prawie identyczna implementacja, ale osobiście użyłbym parametrów.

public List<Article> GetCat(int cat) 

    { 
     string qry = "select ap from Article a"; 
     if(cat > 0) 
      qry += " where a.categoryID = :cat"; 

     IQuery query = session.CreateQuery(qry).SetInt32("cat",cat); 

     return query.List<Article>(); 
    } 

Powoduje wyświetlenie listy obiektów artykułowych gotowych do użycia.

6

NHibernate obsługuje tej używając API kryteria:

ICriteria criteria = session.CreateCriteria<Article>(); 

if (cat > 0) 
    criteria.Add(Expression.Eq("categoryID", cat)); 
+0

Całkowicie się z tym zgadzam. ICriteria została zaprojektowana specjalnie dla tego typu scenariuszy, dynamicznych zapytań definiowanych w środowisku wykonawczym. –

0

Można użyć predykatu Builder i LINQ do NHibernate do generowania dynamicznych zapytań jest tak:

//using Predicate Builder 
     public List<Location> FindAllMatching(string[] filters) 
     { 
      var db = Session.Linq<Location>(); 
      var expr = PredicateBuilder.False<Location>(); //-OR- 
      foreach (var filter in filters) 
      { 
       string temp = filter; 
       expr = expr.Or(p => p.Name.Contains(temp)); 
      } 

      return db.Where(expr).ToList(); 
     } 

uzyskać przewagę Rodzaj Zapisz Sprawdzanie Query i Compiler.

Można również użyć tego samego podejścia do budowania predykatów z Linq do Sql i Entity Framework.

EDYCJA: Dodano przykład. Może to być coś takiego, jak uzyskać wszystkie lokalizacje pasujące do N regionów świata, gdzie użytkownik wybiera regiony, które chce zobaczyć, nie wiemy, ilu użytkowników wybierze, musimy zbudować wyrażenie (OR) na latać, można zrobić coś takiego:

public ActionResult Action(string[] filters) 
{ 
    /*This values are provided by the user, maybe its better to use 
    an ID instead of the name, but for the example is OK. 
    filters will be something like : string[] filters = {"America", "Europe", "Africa"}; 
    */ 
    List<Location> LocationList = FindAllMatchingRegions(filters); 
    return View(LocationList); 
} 

public List<Location> FindAllMatchingRegions(string[] filters) 
     { 
      var db = Session.Linq<Location>(); 
      var expr = PredicateBuilder.False<Location>(); //-OR- 
      foreach (var filter in filters) 
      { 
       string temp = filter; 
       expr = expr.Or(p => p.Region.Name == filter); 
      } 

      return db.Where(expr).ToList(); 
     } 

Możesz orzeczników Nest przez złożonych scenariuszy, takich jak ten:

Jeśli chcesz zrobić coś takiego

p => p.Price > 99 && 
    p.Price < 999 && 
    (p.Description.Contains ("foo") || p.Description.Contains ("far")) 

można zbudować:

var inner = PredicateBuilder.False<Product>(); 
inner = inner.Or (p => p.Description.Contains ("foo")); 
inner = inner.Or (p => p.Description.Contains ("far")); 

var outer = PredicateBuilder.True<Product>(); 
outer = outer.And (p => p.Price > 99); 
outer = outer.And (p => p.Price < 999); 
outer = outer.And (inner); 

i używać go lubię:

var pr = db.Products.Where(outer).ToList(); 

orzecznika Builder Źródło i przykłady dostępne są na http://www.albahari.com/nutshell/predicatebuilder.aspx

+0

Jestem ciekawy tego podejścia. Czy możesz podać przykład rodzaju danych, które zawierałaby zmienna "Filtry". Ponadto, w jaki sposób osoba dzwoniąca używa wartości zwracanej? – Luke101

+0

Zaktualizowałem swój post na przykładzie, jest to bardzo proste, ale można zrobić prawie wszystko z konstruktorem predykatów, jak Nesting Predicates wewnętrzne/zewnętrzne wyrażenie. – JOBG

0

No Love for LLBLGen? Cóż, może to też zrobić.

Używając adaptera „” styl:

RelationPredicateBucket filters = new RelationPredicateBucket(); 
if (cat > 0) 
    filters.Predicate.Add(Article.Fields.CategoryID == cat); 
if (userId > 0) 
    filters.Predicate.Add(Article.Fields.UserID == userId); 
// And so on. 

var adapter = new DataAccessAdapter(); 
var results = new EntityCollection<Article>(new ArticleFactory()); 
adapter.FetchEntityCollection(results, filters); 

ja podejrzewam większość ORMs powinien być w stanie to zrobić dość łatwo.

10

Jak już wspomniano, LINQ pozwala rozszerzyć dowolne zapytanie, po prostu dodając do niego więcej kryteriów.

var query = 
    from x in xs 
    where x==1 
    select x; 

if (mustAddCriteria1) 
    query = 
    from x in query 
    where ... // criteria 1 
    select x; 

if (mustAddCriteria2) 
    query = 
    from x in query 
    where ... // criteria 2 
    select x; 

I tak dalej. To podejście działa idealnie. Ale prawdopodobnie wiesz, że kompilacja zapytań LINQ jest dość kosztowna: Entity Framework może skompilować około 500 stosunkowo prostych zapytań na sekundę (patrz np. ORMBattle.NET).

Z drugiej strony, wiele narzędzi ORM obsługują skompilowane zapytania:

  • przekazać instancję IQueryable pewnym Compile metody i uzyskać delegata pozwalając, aby go wykonać znacznie szybciej później, bo nie ponownej kompilacji będzie występować w ta sprawa.

Ale jeśli chcielibyśmy spróbować użyć tej metody tutaj, od razu zauważy, że nasze zapytanie jest faktycznie dynamiczna: IQueryable wykonujemy za każdym razem może się różnić od poprzedniej. Obecność części zapytań jest określana przez wartości parametrów zewnętrznych.

Czy możemy więc wykonać takie zapytania, jak skompilowane bez np. jawne buforowanie?

DataObjects.Net 4 obsługuje tak zwaną funkcję "boolean branching". Oznacza to, że każde ciągłe wyrażenie boolowskie jest oceniane podczas kompilacji zapytań, a jego rzeczywista wartość jest wprowadzana do zapytania SQL jako prawdziwa stała boolowska (tj. Nie jako wartość parametru lub jako wyrażenie wykorzystujące parametry).

Ta funkcja umożliwia łatwe generowanie różnych planów zapytań na podstawie wartości takich wyrażeń logicznych. Na przykład.ten kod:

int all = new Random().Next(2); 
    var query = 
    from c in Query<Customer>.All 
    where all!=0 || c.Id=="ALFKI" 
    select c; 

będą wykonywane przy użyciu dwóch różnych zapytań SQL, a co za tym idzie - dwa różne plany zapytanie:

  • Query Plan oparty na indeksie seek (dość szybko), jeśli wszystko == 0
  • planu kwerend na podstawie skanowania indeksu (dość wolno), jeśli wszystko = 0

przypadku, gdy wszyscy == null, zapytań SQL:

SELECT 
    [a].[CustomerId], 
    111 AS [TypeId] , 
    [a].[CompanyName] 
FROM 
    [dbo].[Customers] [a] 
WHERE((CAST(0 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI')); 

przypadku, gdy wszyscy == null, plan zapytania:

|--Compute Scalar(DEFINE:([Expr1002]=(111))) 
    |--Clustered Index Seek(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), SEEK:([a].[CustomerId]=N'ALFKI') ORDERED FORWARD) 

drugim przypadku (gdy wszystko! = Null), zapytań SQL: (! = Null, gdy wszystko)

SELECT 
    [a].[CustomerId], 
    111 AS [TypeId] , 
    [a].[CompanyName] 
FROM 
    [dbo].[Customers] [a] 
WHERE((CAST(1 AS bit) <> 0) OR([a].[CustomerId] = 'ALFKI')); 
-- Notice the^value is changed! 

Drugi przypadek , plan zapytania:

|--Compute Scalar(DEFINE:([Expr1002]=(111))) 
    |--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a])) 
-- There is index scan instead of index seek! 

Zauważ, że prawie każdy inny ORM byłoby skompilować to do zapytania wykorzystując parametr całkowita:

SELECT 
    [a].[CustomerId], 
    111 AS [TypeId] , 
    [a].[CompanyName] 
FROM 
    [dbo].[Customers] [a] 
WHERE((@p <> 0) OR ([a].[CustomerId] = 'ALFKI')); 
--  ^^ parameter is used here 

Ponieważ SQL Server (jak większość baz danych) generuje jedną wersję planu kwerend dla danego zapytania, ma jedyną opcją w tym przypadku - generowania planu ze skanowania indeksu:

|--Compute Scalar(DEFINE:([Expr1002]=(111))) 
    |--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), WHERE:(CONVERT(bit,[@p],0)<>(0) OR [DO40-Tests].[dbo].[Customers].[CustomerId] as [a].[CustomerId]=N'ALFKI')) 

OK, to było "szybkie" wyjaśnienie przydatności tej funkcji. Wróćmy teraz do twojej sprawy.

Boolean rozgałęzienie pozwala wykorzystać je w bardzo prosty sposób:

var categoryId = 1; 
var userId = 1; 

var query = 
    from product in Query<Product>.All 
    let skipCategoryCriteria = !(categoryId > 0) 
    let skipUserCriteria = !(userId > 0) 
    where skipCategoryCriteria ? true : product.Category.Id==categoryId 
    where skipUserCriteria ? true : 
    (
    from order in Query<Order>.All 
    from detail in order.OrderDetails 
    where detail.Product==product 
    select true 
).Any() 
    select product; 

Przykład różni się od Ciebie, ale ilustruje ten pomysł. Użyłem innego modelu głównie, aby móc to przetestować (mój przykład oparty jest na modelu om Northwind).

To zapytanie jest:

  • Nie dynamiczny zapytań, dzięki czemu można bezpiecznie przekazać go do Query.Execute(...) metody, aby to realizowane jako skompilowanego zapytania.
  • Niemniej jednak każde jego wykonanie doprowadzi do tego samego wyniku, co w przypadku "dołączania" do IQueryable.
+0

Zapomniałem dodać, dlaczego jest to wygodne: takie zapytanie może być używane jako skompilowane zapytanie w DO4. Oczywiście, DO4 zadba o to, by użyć odpowiedniego zapytania SQL. Jeśli nie masz tej funkcji, ale konieczne jest wstępne skompilowanie takiego zapytania, powinieneś osiągnąć to samo tylko z zestawem "jeśli" i zestawem skompilowanych zapytań. 2 warunki = 4 skompilowane zapytania. 3 warunki = 8 skompilowanych zapytań i tak dalej. –

+1

@Alex, to dobrze - ale czy możesz wskazać, gdzie jest to pokazane w dokumentacji twojego produktu? Chodzi mi o to, że jeśli funkcja nie jest wykrywalna - nie istnieje. Co jest wstydem dla świetnego produktu, takiego jak DataObjects.net. – Aryeh

+0

Prawda. Właściwie sama dokumentacja (podręcznik) jest pisana właśnie teraz, a ta funkcja nie jest jeszcze tam opisana (nadal mamy zestaw znacznie ważniejszych do opisania ...). Musi pojawić się dość szybko (dni ... tydzień). Najnowsza wersja jest zawsze dostępna tutaj: http://dataobjectsdotnet.googlecode.com/hg/Manual/index.htm –

Powiązane problemy