2009-12-08 9 views
5

Tengo una parte opcional de la consulta que debe ejecutarse en una determinada condición. Aquí está el código de ejemplo:Qué ORM admite esto

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 

Como puede ver, tengo una instrucción if en la consulta. Actualmente estoy usando Entity Framework y no es compatible con este tipo de escenario. ¿Hay un ORM que apoye esto?

Editar Traté de fingir la consulta. Pero tengo alrededor de 20 declaraciones "IF" y las consultas son muy largas.

Los ORM que estaba mirando fueron:

  • NHibernate
  • LLBLGen
  • subsónico

Estoy abierto a cualquier ORM. Gracias

+1

LLBLGen es compatible con esto, incluso 20 de ellos. Como las cláusulas where se agregan una o muchas a la vez. – PostMan

+0

@PostMan ¿Tendría un ejemplo de esto? – Luke101

+2

Puede hacer esto en Entity Framework vea la respuesta de tt83 a continuación. Su respuesta es para Linq a SQL, pero el concepto es el mismo para Entity Framework. No es necesario tirar al bebé con el agua del baño. –

Respuesta

2

probablemente Usted puede hacer esto con cualquier proveedor de LINQ, pero sé que el LightSpeed ORM lo admite:

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

Traté de fingir la consulta. Pero tengo alrededor de 20 declaraciones "IF" y las consultas son muy largas. ¿No es posible encadenar las sentencias IF en la consulta linq? – Luke101

+0

Si encadena los enunciados if en la consulta LINQ, se traducirán a SQL y si esto funcionará dependerá del proveedor de LINQ. Pero puede encadenar. Donde las invocaciones están bajo el control de las declaraciones if del lado del cliente. – itowlson

+0

Además, si su caso de uso real es considerablemente más complicado, es posible que desee editar su pregunta para darle un poco más de sabor a su caso de uso real para que no obtenga un montón de respuestas como la mía que solo describen el caso trivial! – itowlson

0

hago este tipo de cosas en NHibernate todo el tiempo.

(he hecho cosas similares en los carriles. Estoy un poco sorprendido de que no son ORM que no lo hacen apoyo esto.)

+0

¿Tendría un ejemplo de cómo se hace en nhibernate? – Luke101

+0

No es fácil: tenemos nuestra propia abstracción sobre NHibernate que uso el 99.9% del tiempo. Vea la respuesta de Kevin arriba. – Ken

+0

La respuesta de Kevin la clavó. –

9

esto se puede hacer usando LINQ to SQL ...

IQueryable<Article> query = yourDataContext.Articles; 

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

return query.ToList(); 
+0

He editado el código. ¿es posible agregar múltiples sentencias where en una consulta linq? Ver el código en la pregunta – Luke101

+0

Sí. IQueryable

en este caso diferirá la ejecución de SQL hasta que se materialice (llame a ToList, etc.) puede agregar tantos elementos condicionales como desee. Solo cuando llame a ToList, realmente ejecutará el SQL contra la base de datos. –

+0

¿Añadiría otro condicional como este: query = query.Where (x => x.CategoryId == catId); query + = query.Where (x => x.userid == ID de usuario); – Luke101

0

Puede crear fácilmente consultas de esta manera utilizando NHibernate's HQL (Hibernate Query Language). Sería una implementación casi idéntica, pero personalmente usaría parámetros.

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

Esto devuelve una lista <> Artículo de objetos listos para su uso.

6

NHibernate apoya este uso de la API Criterios:

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

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

Estoy totalmente de acuerdo con esto. El ICriteria se diseñó específicamente para este tipo de escenario, consultas dinámicas definidas en tiempo de ejecución. –

0

Usted puede utilizar el generador de predicados y LINQ to NHibernate para generar la consulta de dinámica de esta manera:

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

Usted consigue la ventaja de Tipo de guardar Verificación de consultas y compiladores.

También puede usar el mismo enfoque de generador de predicados con Linq a Sql y Entity Framework.

EDITAR: Ejemplo agregado. Podría ser algo así como obtener todas las ubicaciones que coincidan con N regiones del mundo, donde el usuario seleccione las regiones que quiere ver, no sabemos cuántas seleccionará el usuario, debemos construir la expresión (O) en el Fly, se puede hacer algo como:

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

se pueden ingresar predicados nido para un complejo de escenarios como este:

Si quieres hacer algo como

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

se puede construir:

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

Y usarlo como:

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

El constructor de origen y ejemplos de predicados están disponibles en http://www.albahari.com/nutshell/predicatebuilder.aspx

+0

Tengo curiosidad de este enfoque. Puede dar un ejemplo del tipo de datos que contendría la variable "Filtros". Además, ¿cómo usaría la persona que llama el valor de retorno? – Luke101

+0

Actualicé mi publicación con un ejemplo, es bastante simple pero puede hacer casi cualquier cosa con el generador de predicados, como Nesting Predicates inner/outer expression. – JOBG

0

Sin amor por LLBLGen? Bueno, puede hacerlo también.

Usando el estilo 'adaptador':

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

sospecharía la mayoría de ORM debería ser capaz de hacer esto con bastante facilidad.

10

Como ya se ha mencionado aquí, LINQ permite ampliar cualquier consulta simplemente añadiendo más criterios.

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; 

Y así sucesivamente. Este enfoque funciona perfectamente. Pero es probable que sepa que la compilación de consultas LINQ es bastante costosa: p. Entity Framework puede compilar aproximadamente 500 consultas relativamente simples por segundo (ver, por ejemplo, ORMBattle.NET).

Por otro lado, muchas herramientas ORM admite consultas compiladas:

  • Se pasa una instancia IQueryable hasta cierto Compile método, y obtener un delegado que permite ejecutar mucho más rápido después, porque ninguna recompilación ocurriría en este caso.

Pero si nos gustaría probar a utilizar este enfoque aquí, inmediatamente notamos que nuestra consulta es en realidad dinámica: IQueryable que se ejecute cada vez puede ser diferente a la anterior. La presencia de partes de consulta está determinada por valores de parámetros externos.

Entonces, ¿podemos ejecutar esas consultas compiladas sin, p. Ej., caché explícito?

DataObjects.Net 4 es compatible con la función llamada "ramificación booleana". Implica que cualquier expresión booleana constante se evalúa durante la compilación de consultas y su valor real se inyecta en la consulta SQL como una constante booleana verdadera (es decir, no como un valor de parámetro o como una expresión que utiliza parámetros).

Esta característica permite generar diferentes planes de consulta dependiendo de los valores de tales expresiones booleanas con facilidad. P.ej.este código:

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

se ejecutará mediante dos consultas SQL diferentes, y por lo tanto - dos diferentes planes de consulta:

  • de consultas plan basado en búsqueda de índice (bastante rápido), si todo == 0
  • plan de consulta basada en la exploración de índice (bastante lento), si todo = 0

caso cuando todos == null, consulta SQL:

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

caso cuando todos == null, plan de consulta:

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

segundo caso (cuando todos! = Null), consulta SQL: (! = Null cuando todo)

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! 

Segundo caso , plan de consulta:

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

Tenga en cuenta que casi cualquier otro ORM sería compilar este a una consulta que utiliza parámetro entero:

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

Desde SQL Server (así como la mayoría de las bases de datos) genera una única versión del plan de consulta para una consulta en particular, tiene la única opción en este caso - generar un plan con el recorrido de índice:

|--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, esa fue una explicación "rápida" de la utilidad de esta característica. Regresemos a su caso ahora.

ramificación booleana permite ponerlo en práctica de manera muy simple:

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; 

El ejemplo diferente del suyo, pero ilustra la idea. Usé un modelo diferente principalmente para poder probar esto (mi ejemplo se basa en el modelo de Northwind).

Esta consulta es:

    No
  • una consulta dinámica, por lo que puede pasar con seguridad a Query.Execute(...) método para conseguirlo ejecutado como consulta compilada.
  • Sin embargo, cada una de sus ejecuciones dará lugar al mismo resultado como si esto se hiciera con "anexar" al IQueryable.
+0

Olvidé agregar por qué es conveniente: tal consulta se puede usar como consulta compilada en DO4. Obviamente, DO4 se preocupará por usar la consulta SQL apropiada. Si no tiene esta característica, pero es necesario precompilar dicha consulta, debe lograr lo mismo solo con un conjunto de "si" y un conjunto de consultas compiladas. 2 condiciones = 4 consultas compiladas. 3 condiciones = 8 consultas compiladas, y así sucesivamente. –

+1

@Alex, esto es bueno, pero ¿puede indicar dónde se muestra esto en la documentación de su producto? El punto que estoy haciendo es, si una característica no es detectable, no existe. Lo cual es una pena para un gran producto como DataObjects.net. – Aryeh

+0

Es cierto. En realidad, la documentación en sí misma (manual) se está escribiendo en este momento, y esta característica no se describe todavía (todavía tenemos un conjunto de muchos más importantes que describir ...). Debe aparecer bastante pronto (días ... semana). Su última revisión está siempre disponible aquí: http://dataobjectsdotnet.googlecode.com/hg/Manual/index.htm –