2011-08-16 5 views
9

Intento implementar un filtro dinámico de usuario, donde se usan algunas propiedades, selecciona algunos operadores y también selecciona los valores.Expresión dinámica utilizando LINQ. ¿Cómo encontrar las cocinas?

Como no he encontrado todavía una respuesta a this question, traté de usar expresiones LINQ.
Principalmente necesito identificar todas las casas cuyas habitaciones principales son cocinas (cualquier sentido, lo sé).

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 
//using System.Linq.Dynamic; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Room aRoom = new Room() { Name = "a Room" }; 
      Room bRoom = new Room() { Name = "b Room" }; 
      Room cRoom = new Room() { Name = "c Room" }; 

      House myHouse = new House 
      { 
       Rooms = new List<Room>(new Room[] { aRoom }), 
       MainRoom = aRoom 
      }; 
      House yourHouse = new House() 
      { 
       Rooms = new List<Room>(new Room[] { bRoom, cRoom }), 
       MainRoom = bRoom 
      }; 
      House donaldsHouse = new House() 
      { 
       Rooms = new List<Room>(new Room[] { aRoom, bRoom, cRoom }), 
       MainRoom = aRoom 
      }; 

      var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse }); 

      //var kitchens = houses.AsQueryable<House>().Where("MainRoom.Type = RoomType.Kitchen"); 
      //Console.WriteLine("kitchens count = {0}", kitchens.Count()); 

      var houseParam = Expression.Parameter(typeof(House), "house"); 
      var houseMainRoomParam = Expression.Property(houseParam, "MainRoom"); 
      var houseMainRoomTypeParam = Expression.Property(houseMainRoomParam, "Type"); 

      var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType"); 

      var comparison = Expression.Lambda(
       Expression.Equal(houseMainRoomTypeParam, 
       Expression.Constant("Kitchen", typeof(RoomType))) 
       ); 

      // ???????????????????????? DOES NOT WORK 
      var kitchens = houses.AsQueryable().Where(comparison); 

      Console.WriteLine("kitchens count = {0}", kitchens.Count()); 
      Console.ReadKey(); 

     } 
    } 

    public class House 
    { 
     public string Address { get; set; } 
     public double Area { get; set; } 
     public Room MainRoom { get; set; } 
     public List<Room> Rooms { get; set; } 
    } 

    public class Room 
    { 
     public double Area { get; set; } 
     public string Name { get; set; } 
     public RoomType Type { get; set; } 
    } 

    public enum RoomType 
    { 
     Kitchen, 
     Bedroom, 
     Library, 
     Office 
    } 
} 

Respuesta

6
var kitchens = from h in houses 
       where h.MainRoom.Type == RoomType.Kitchen 
       select h; 

Pero debe establecer la propiedad RoomType en las habitaciones antes.

Ok, editar:

por lo que debe volver a definir:

var comparison = Expression.Lambda<Func<House, bool>>(... 

Entonces, cuando lo utiliza:

var kitchens = houses.AsQueryable().Where(comparison.Compile()); 

Edición # 2:

Ok, aquí ir:

var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType"); 



// ???????????????????????? DOES NOT WORK 
var comparison = Expression.Lambda<Func<House, bool>>(
    Expression.Equal(houseMainRoomTypeParam, 
    Expression.Constant(Enum.Parse(typeof(RoomType), "Kitchen"), typeof(RoomType))), houseParam); 



// ???????????????????????? DOES NOT WORK 
var kitchens = houses.AsQueryable().Where(comparison); 

Edición # 3: de, para sus necesidades, estoy fuera de ideas por ahora. Te doy una última:

Declarar un método de extensión sobre el tipo String:

internal static object Prepare(this string value, Type type) 
{ 
    if (type.IsEnum) 
     return Enum.Parse(type, value); 

    return value; 
} 

luego usarlo en esa expresión como:

Expression.Constant("Kitchen".Prepare(typeof(RoomType)), typeof(RoomType)) 

Eso es porque al parecer las enumeraciones son tratados de manera diferente. Esa extensión dejará la cadena inalterada para otros tipos. Drawback: tiene que agregar otro typeof() allí.

+0

Necesito una consulta * dynamic *. Esta consulta es estática. Si el usuario seleccionará otra propiedad en lugar de "Tipo" de habitación, su método debe actualizarse ... el RoomType es Cocina (0) por defecto. – serhio

+0

buen comentario. Ahora la excepción de tiempo de ejecución es "Los tipos de argumento no coinciden" cuando se crea la "comparación". PD. No es necesario "Compilar" a priori ... – serhio

+0

@serhio Compruebe si esto le conviene. – Vladimir

-1

qué pasa con esto

var kitchens = houses 
       .SelectMany(h => h.Rooms, (h, r) => new {House = h, Room = r}) 
       .Where(hr => hr.Room.Type == RoomType.Kitchen) 
       .Select(hr => hr.House); 
+2

Necesito una consulta * dynamic *. Esta consulta es estática.Si el usuario seleccionará otra propiedad en lugar de "Tipo" de habitación, su método debe actualizarse ... – serhio

0
// ???????????????????????? DOES NOT WORK 
var kitchens = houses.AsQueryable().Where(comparison); 

El método Where toma un Func<House, bool> o una Expression<Func<House, bool>> como el parámetro, pero la variable comparison es de tipo LambdaExpression, que no coincide. Es necesario utilizar otra sobrecarga del método:

var comparison = Expression.Lambda<Func<House, bool>>(
       Expression.Equal(houseMainRoomTypeParam, 
       Expression.Constant("Kitchen", typeof(RoomType)))); 
//now the type of comparison is Expression<Func<House, bool>> 

//the overload in Expression.cs 
public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters); 
+0

Ya lo entendí de Visual Studio. Gracias. Sin embargo, no sé cómo proceder. – serhio

0

no edificar la cláusula where de esa manera - Creo que es más complejo de lo que debe ser para sus necesidades. En su lugar, se pueden combinar en cláusulas como esta:

var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse }); 

// A basic predicate which always returns true: 
Func<House, bool> housePredicate = h => 1 == 1; 

// A room name which you got from user input: 
string userEnteredName = "a Room"; 

// Add the room name predicate if appropriate: 
if (!string.IsNullOrWhiteSpace(userEnteredName)) 
{ 
    housePredicate += h => h.MainRoom.Name == userEnteredName; 
} 

// A room type which you got from user input: 
RoomType? userSelectedRoomType = RoomType.Kitchen; 

// Add the room type predicate if appropriate: 
if (userSelectedRoomType.HasValue) 
{ 
    housePredicate += h => h.MainRoom.Type == userSelectedRoomType.Value; 
} 

// MainRoom.Name = \"a Room\" and Rooms.Count = 3 or 
// ????????????????????????? 
var aRoomsHouses = houses.AsQueryable<House>().Where(housePredicate); 

me probaron éste, honesto :)

+0

gracias, lo probaré. Para ser más claro, trato de hacer algo como aquí: http://stackoverflow.com/q/7026855/185593 – serhio

+0

A menos que necesites incluir orden de resultados especificado por el usuario, te recomendaría usar predicados como este. Si necesita permitir que los usuarios pidan resultados, recomendaría dynamic linq. –

+0

como se puede ver en la GUI desde el enlace del primer comentario, no puedo saber * a priori * la propiedad (es) para aplicar al filtro. Entonces las variables como "userEnteredName" no tienen sentido en mi código, ya que no trato con la propiedad "Name", sino con una propiedad (es) desconocida (lista de) y operador (s) desconocido (s). – serhio

-1

Para añadir un nuevo tipo de Enum dinámica LINQ, debe agregar el siguiente código:

typeof(Enum), 
typeof(T) 

T : Enum type 

en tipos predefinidos de dinámica. Funciona para mi.

+0

sea más claro a qué se refería ... – nawfal

Cuestiones relacionadas