2009-12-08 11 views
5

Ho una parte facoltativa di query che deve essere eseguita su una determinata condizione. Ecco il codice di esempio:Quale ORM supporta questo

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 

Come si può vedere, ho un'istruzione if nella query. Attualmente sto usando Entity Framework e non supporta questo tipo di scenario. C'è un ORM là fuori che supporta questo?

Modifica Ho cercato di nascondere la query. Ma ho circa 20 dichiarazioni "IF" e le querys sono molto lunghe.

I ORM stavo guardando erano:

  • NHibernate
  • LLBLGen
  • Subsonic

Sono aperto a qualsiasi ORM. Grazie

+1

LLBLGen supporta questo, anche 20 di loro. Come le clausole where vengono aggiunte una o più alla volta. – PostMan

+0

@PostMan vuoi fare un esempio? – Luke101

+2

È possibile farlo in Entity Framework, vedere la risposta di tt83 di seguito. La sua risposta è per Linq to SQL ma il concetto è lo stesso per Entity Framework. Non c'è bisogno di buttare fuori il bambino con l'acqua del bagno. –

risposta

2

Probabilmente si può fare questo con qualsiasi provider LINQ, ma so che la LightSpeed ORM lo sostiene:

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

Ho provato a eliminare la query. Ma ho circa 20 dichiarazioni "IF" e le querys sono molto lunghe. Non è possibile concatenare le istruzioni IF nella query linq stessa? – Luke101

+0

Se si concatenano le istruzioni if ​​nella query LINQ, verranno tradotte in SQL e se ciò funzionerà dipenderà dal provider LINQ. Ma puoi incatenare. Dove le invocazioni sono sotto il controllo delle affermazioni sul lato client. – itowlson

+0

Inoltre, se il tuo caso d'uso reale è considerevolmente più complicato, potresti voler modificare la tua domanda per dare un tocco in più al tuo caso d'uso reale in modo da non ottenere un carico di risposte come le mie che descrivono solo il caso banale! – itowlson

0

faccio questo genere di cose in NHibernate per tutto il tempo.

(Ho fatto cose simili in Rails. Sono un po 'sorpreso che ci sono ORM che non farlo supporto questo.)

+0

Vuoi avere un esempio di come si fa in nhibernate? – Luke101

+0

Non facilmente: abbiamo la nostra astrazione su NHibernate che uso il 99,9% delle volte. Vedi la risposta di Kevin sopra. – Ken

+0

La risposta di Kevin lo ha inchiodato. –

9

questo può essere fatto utilizzando LINQ to SQL ...

IQueryable<Article> query = yourDataContext.Articles; 

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

return query.ToList(); 
+0

Ho editato il codice. è possibile aggiungere più istruzioni where in una query linq? Vedere il codice nella domanda – Luke101

+0

Sì. IQueryable

in questo caso rinvia l'esecuzione SQL fino a quando non lo materializzi (chiama ToList, ecc.) Puoi aggiungere tutti i condizionali che desideri. Solo quando si chiama ToList verrà effettivamente eseguito l'SQL sul database. –

+0

Vorrei aggiungere un altro condizionale come questo: query = query.Where (x => x.CategoryId == catId); query + = query.Where (x => x.userid == UserID); – Luke101

0

È possibile creare facilmente query in questo modo utilizzando HQL di NHibernate (linguaggio di interrogazione Hibernate). Sarebbe un'implementazione quasi identica, ma personalmente utilizzerei i parametri.

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>(); 
    } 

Questo restituisce un oggetto <> di oggetti Articolo pronto per l'uso.

6

NHibernate supporta questa utilizzando l'API Criteri:

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

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

Sono assolutamente d'accordo con questo. ICriteria è stato progettato specificamente per questo tipo di scenario, query dinamiche definite in fase di esecuzione. –

0

È possibile utilizzare il predicato Builder e LINQ to NHibernate per generare domanda di dinamica come questa:

//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(); 
     } 

Si ottiene il vantaggio di Tipo file Query e controllo del compilatore.

È inoltre possibile utilizzare lo stesso approccio del generatore di predicati con Linq su Sql e Entity Framework.

MODIFICA: Aggiunto esempio. Potrebbe essere qualcosa come ottenere tutte le posizioni corrispondenti a N regioni del mondo, dove l'utente seleziona le regioni che vuole vedere, non sappiamo quanti l'utente selezionerà, dobbiamo costruire l'espressione (OR) sul volare, si può fare qualcosa di simile:

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(); 
     } 

potete predicati Nest per scenari complessi come questo:

Se si vuole fare qualcosa di simile

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

si può costruire:

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); 

e usarlo come:

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

il predicato Builder Fonte e gli esempi sono disponibili presso http://www.albahari.com/nutshell/predicatebuilder.aspx

+0

Sono curioso di questo approccio. Puoi dare un esempio del tipo di dati che la variabile "Filtri" conterrebbe. Inoltre, in che modo il chiamante utilizzerà il valore restituito? – Luke101

+0

Ho aggiornato il mio post con un esempio, è piuttosto semplice ma puoi fare praticamente qualsiasi cosa con il generatore di predicati, come l'espressione interna/esterna di Predicati di nidificazione. – JOBG

0

Nessun amore per LLBLGen? Beh, può farlo anche tu.

Utilizzando lo stile 'adattatore':

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); 

ho il sospetto maggior parte ORM dovrebbero essere in grado di farlo abbastanza facilmente.

10

Come già menzionato qui, LINQ consente di estendere qualsiasi query aggiungendo semplicemente più criteri ad esso.

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; 

E così via. Questo approccio funziona perfettamente. Ma probabilmente, sai che la compilazione di query LINQ è piuttosto costosa: ad es. Entity Framework può compilare solo circa 500 query relativamente semplici al secondo (vedere ad esempio ORMBattle.NET).

D'altra parte, molti strumenti ORM supporta le query compilate:

  • si passa un'istanza IQueryable a qualche metodo Compile, e ottenere un delegato che consente di eseguire molto più veloce più tardi, perché non ricompilazione avverrebbe in questo caso.

Ma se vorremmo provare a utilizzare questo approccio qui, notiamo subito che la nostra query è in realtà dinamica: IQueryable eseguiamo ogni volta che potrebbe differire da quella precedente. La presenza di parti di query è determinata dai valori dei parametri esterni.

Così possiamo eseguire tali query come compilate senza ad es. memorizzazione nella cache esplicita?

DataObjects.Net 4 supporta la cosiddetta funzione "boolean branching". Esso implica che qualsiasi espressione booleana costante viene valutata durante la compilazione della query e il suo valore effettivo viene iniettato nella query SQL come costante booleana vera (cioè non come valore del parametro o come espressione che utilizza parametri).

Questa funzione consente di generare diversi piani di query in base ai valori di tali espressioni booleane con facilità. Per esempio.questo codice:

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

sarà eseguito utilizzando due diversi query SQL, e quindi - due diversi piani di query:

  • Query piano basato su Index Seek (abbastanza veloce), se tutto == 0
  • piano di query sulla base di indice di scansione (piuttosto lento), se tutto = 0

caso quando tutto == null, query SQL:

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

caso quando tutto == null, piano di query:

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

secondo caso (quando tutto! = Null), query SQL: (! Quando tutti = null)

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! 

Secondo caso , piano di query:

|--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! 

Nota che quasi ogni altro ORM sarebbe compilare questo a una query utilizzando parametro intero:

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

Dal momento che SQL Server (così come la maggior parte delle basi di dati) genera una singola versione di piano di query per una determinata query, è l'unica opzione in questo caso - generare un piano con indice di scansione:

|--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, questa era una spiegazione "rapida" dell'utilità di questa funzione. Torniamo al tuo caso ora.

ramificazione booleano consente di implementare in modo molto semplice:

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; 

L'esempio differisce dalla vostra, ma illustra l'idea. Ho usato un modello diverso principalmente per essere in grado di testarlo (il mio esempio si basa sul modello om Northwind).

Questa domanda è:

    Non
  • una query dinamica, in modo da poter tranquillamente passarlo a Query.Execute(...) metodo per ottenerlo eseguito come query compilata.
  • Ciononostante, ciascuna esecuzione porterà allo stesso risultato come se ciò avvenisse con "accodamento" a IQueryable.
+0

Ho dimenticato di aggiungere perché questo è conveniente: tale query può essere utilizzata come query compilata in DO4. Ovviamente, a DO4 interesserà l'utilizzo della query SQL appropriata. Se non si dispone di questa funzione, ma è necessario precompilare una query di questo tipo, è necessario ottenere lo stesso solo con un set di "se" e una serie di query compilate. 2 condizioni = 4 query compilate. 3 condizioni = 8 query compilate e così via. –

+1

@Alex, questo è bello, ma puoi indicare dove viene mostrato nella documentazione del tuo prodotto? Il punto che sto facendo è, se una funzione non è rilevabile - non esiste. Il che è un peccato per un grande prodotto come DataObjects.net. – Aryeh

+0

Vero. In realtà la documentazione stessa (manuale) è stata scritta proprio adesso, e questa funzionalità non è ancora stata descritta (abbiamo ancora un set molto più importante da descrivere ...). Deve apparire abbastanza presto (giorni ... settimana). La sua ultima revisione è sempre disponibile qui: http://dataobjectsdotnet.googlecode.com/hg/Manual/index.htm –

Problemi correlati