c# - ¿Cómo aplicar un filtro en los resultados de LINQtoSQL?
linq-to-sql attributes (3)
Con el control ListBox es posible alimentarlo con un DataSource, nombrar un DisplayMember y un ValueMember y mediante un poco de magia mostrará un campo del DataSource y devolverá un ValueMember seleccionado. Puede funcionar con un resultado de linq a sql sin siquiera saber algo específico sobre la tabla con la que se alimenta.
¿La reflexión y los atributos no están haciendo magia? ¡Como funciona! Tengo la necesidad de hacer algo similar, pero no sé por dónde empezar. Soy un principiante para LINQtoSQL.
Esto es lo que quiero hacer. Tengo una tabla fuente que quiero filtrar. La tabla fuente puede ser cualquier cosa pero se originará a partir de algún DataContext.
var MySourceTable =
from MyRecord in Context.GetTable<MySourceTable>()
select new
{
Value = MyRecord.ID,
Display = MyRecord.Name,
FilterValue = MyRecord.Value
};
Bajo mi control, quiero poder filtrar MySourceTable en algún valor dado. El control no sabe qué tabla se utiliza (MySourceTable en el ejemplo anterior) y el control solo conoce los tres nombres, ID, Nombre y Valor de los campos en el registro que debería usar.
La consulta de filtro debería verse como el ejemplo a continuación.
var MyTable
from Record in MySourceTable
where FilterValue == GivenValue
select new
{
Value = Record.ID,
Display = Record.Name,
};
¿Alguien puede aconsejarme sobre dónde comenzar?
Encontré una manera, funciona pero no es un método completamente satisfactorio.
El ''problema'' (en comparación con mi pregunta original) es que no usa linq-to-sql para filtrar la fuente. Pero funciona y por el momento está bien para mí.
Recapitulación: en un módulo de alto nivel, preparo una declaración LINQtoSQL que dará como resultado un <Objeto> IQueriable, efectivamente un objeto IQueriable. Este objeto se le da a un módulo de nivel inferior junto con tres nombres, uno para la ID, uno para la pantalla y otro para el filtrado. Cuando necesito más control sobre la fuente, por ejemplo, para datos no filtrados que darán lugar a resultados enormes o para consultas complejas de LINQtoSQL, usaré una función de delegado. Eso me dará todo el control que quiero.
En un nivel bajo, tengo un <Objeto> IQueriable y no tengo ningún conocimiento sobre el Objeto, excepto que Wat Reflection puede decirme. Genero una tabla de resultados que quiero usar en un formato específico de control. (la clase Record). Para cualquier cosa más compleja que mi código estándar no pueda manejar, ofrezco un delegado que debe dar como resultado una lista de objetos en los que el objeto debe tener al menos una propiedad llamada ''Pantalla'' y una propiedad llamada ''Valor''. Otras propiedades son posibles pero no serán utilizadas.
Esta es la solución que finalmente llegué a trabajar:
public partial class MySelector : UserControl
{
class Record
{
public object Display { get; set; }
public object Value { get; set; }
}
....
public string MyDisplayMember { get; set; }
public string MyValueMember { get; set; }
public string MyExternalMember { get; set; }
....
static Object Filter(MySelector sender, Object criterium)
{
IQueryable source = sender.MySource as IQueryable;
if (source == null) return null;
List<Record> result = new List<Record>();
// drawback: this foreach loop will trigger a unfiltered SQL command.
foreach (var record in source)
{
MethodInfo DisplayGetter = null;
MethodInfo ValueGetter = null;
bool AddRecord = false;
foreach (PropertyInfo property in record.GetType().GetProperties())
{
if (property.Name == sender.MyDisplayMember)
{
DisplayGetter = property.GetGetMethod();
}
else if (property.Name == sender.MyValueMember)
{
ValueGetter = property.GetGetMethod();
}
else if (property.Name == sender.MyExternalMember)
{
MethodInfo ExternalGetter = property.GetGetMethod();
if (ExternalGetter == null)
{
break;
}
else
{
object external = ExternalGetter.Invoke(record, new object[] { });
AddRecord = external.Equals(criterium);
if (!AddRecord)
{
break;
}
}
}
if (AddRecord && (DisplayGetter != null) && (ValueGetter != null))
{
break;
}
}
if (AddRecord && (DisplayGetter != null) && (ValueGetter != null))
{
Record r = new Record();
r.Display = (DisplayGetter == null)
? null
: DisplayGetter.Invoke(record, new object[] { });
r.Value = (ValueGetter == null)
? null
: ValueGetter.Invoke(record, new object[] { });
result.Add(r);
}
}
return result;
}
}
Escribí un motor de filtro que toma una Propiedad y Valor como una cadena, y puedo usar eso como una cláusula where.
IQueryable<T> FilterFunction<T>(IQueryable<T> query)
{
ParameterExpression p = Expression.Parameter(typeof(T), "notused");
Expression<Func<T, bool>> wherePredicate =
Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Call(Expression.Property(p, FilterProperty), "ToString", new Type[0]),
Expression.Constant(FilterValue)), p);
return query.Where(wherePredicate);
}
Debería poder pasar una Expression<Func<T, TResult>>
construida de forma similar a query.Select()
Si entiendo tu pregunta correctamente, creo que esto funcionará:
string DisplayProperty = "Name";
string ValueProperty = "ID";
IQueryable<Record> SelectRecordProperties<T>(IQueryable<T> query)
{
ParameterExpression p = Expression.Parameter(typeof(T), "notused");
MethodInfo ctorMethod = typeof(Record).GetMethod("Create");
Expression<Func<T, Record>> selectPredicate =
Expression.Lambda<Func<T, Record>>(
Expression.Call(ctorMethod,
Expression.PropertyOrField(p, DisplayProperty),
Expression.PropertyOrField(p, ValueProperty)), p);
return query.Select(selectPredicate);
}
class Record
{
public static Record Create(string display, string value)
{
return new Record() { Display = display, Value = value };
}
public object Display { get; set; }
public object Value { get; set; }
}
Entonces, para su función completa, necesitaría combinar estas dos ideas para que su filtrado funcione.
Por cierto, hay muchas maneras posibles de construir el árbol de expresiones para esto, había alguna herramienta que encontré en un punto que me mostraría el árbol de expresiones, creo, para que pudieras escribir manualmente la consulta de linq y ver cómo. Net construye la expresión, luego modifica este código para compilarlo en base a eso para posiblemente obtener un árbol de expresión más eficiente.
Parece que lo que te estás perdiendo está en la condición where de tu consulta. Debe tener un aspecto como este:
var MyTable =
from Record in MySourceTable
where Record.FilterValue == GivenValue
select new
{
Value = Record.ID,
Display = Record.Name,
};
GivenValue es presumiblemente una variable o propiedad local que contiene lo que sea que desee comparar FilterValue contra. Pero FilterValue es una propiedad del tipo anónimo que creó en su primera consulta que creó MySourceTable. En su segunda consulta, Record es una instancia de ese tipo anónimo, y debe usar esa referencia a la instancia en todas las demás partes de la consulta para hacer referencia a la instancia que está verificando para la cláusula where o seleccionando para la cláusula select. Si solo pone FilterValue allí, entonces no tiene idea de lo que quiere decir.