2010-06-17 18 views
5

Estoy intentando crear un repositorio genérico para mis modelos. Actualmente tengo 3 modelos diferentes que no tienen ninguna relación entre ellos. (Contactos, Notas, Recordatorios).Acceso a propiedades mediante el parámetro de tipo genérico

class Repository<T> where T:class 
{ 
    public IQueryable<T> SearchExact(string keyword) 
    { 
     //Is there a way i can make the below line generic 
     //return db.ContactModels.Where(i => i.Name == keyword)   
     //I also tried db.GetTable<T>().Where(i => i.Name == keyword) 
     //But the variable i doesn't have the Name property since it would know it only in the runtime 
     //db also has a method ITable GetTable(Type modelType) but don't think if that would help me 
    } 
} 

En MainViewModel, me llaman el método de búsqueda de esta manera:

Repository<ContactModel> _contactRepository = new Repository<ContactModel>(); 

public void Search(string keyword) 
{ 
    var filteredList = _contactRepository.SearchExact(keyword).ToList(); 
} 

Solución:

Finalmente fue con solución de expresión dinámica de Ray:

public IQueryable<TModel> SearchExact(string searchKeyword, string columnName) 
{ 
    ParameterExpression param = Expression.Parameter(typeof(TModel), "i"); 
    Expression left = Expression.Property(param, typeof(TModel).GetProperty(columnName)); 
    Expression right = Expression.Constant(searchKeyword); 
    Expression expr = Expression.Equal(left, right); 
} 

query = db.GetTable<TModel>().Where(Expression.Lambda<Func<TModel, bool>>(expr, param)); 

Respuesta

13

Interfaz solución

Si puede agregar una interfaz a su objeto, puede usarlo. Por ejemplo, podría definir:

public interface IName 
{ 
    string Name { get; } 
} 

Entonces su repositorio podría ser declarado como:

class Repository<T> where T:class, IName 
{ 
    public IQueryable<T> SearchExact(string keyword) 
    { 
    return db.GetTable<T>().Where(i => i.Name == keyword); 
    } 
} 

solución de interfaz alternativo

alternativa usted puede poner el "donde" en su método SearchExact por utilizando un segundo parámetro genérico:

class Repository<T> where T:class 
{ 
    public IQueryable<T> SearchExact<U>(string keyword) where U: T,IName 
    { 
    return db.GetTable<U>().Where(i => i.Name == keyword); 
    } 
} 

Esto permite que la clase Repository se use con objetos que no implementan IName, mientras que el método SearchExact solo se puede usar con objetos que implementen IName.

solución Reflexión

Si no se puede agregar una interfaz INAME-como a los objetos, se puede utilizar la reflexión en su lugar:

class Repository<T> where T:class 
{ 
    static PropertyInfo _nameProperty = typeof(T).GetProperty("Name"); 

    public IQueryable<T> SearchExact(string keyword) 
    { 
    return db.GetTable<T>().Where(i => (string)_nameProperty.GetValue(i) == keyword); 
    } 
} 

Esto es más lento que el uso de una interfaz, pero a veces Es la única forma.

Más notas sobre la solución de interfaz y por qué te pueden usarlo

En su comentario usted menciona que usted no puede utilizar una interfaz pero no explican por qué. Usted dice "Nada en común está presente en los tres modelos. Por lo tanto, creo que no es posible crear una interfaz entre ellos". De su pregunta entendí que los tres modelos tienen una propiedad de "Nombre". En ese caso, es es posible implementar una interfaz en los tres. Simplemente implemente la interfaz como se muestra y ", IName" para cada una de las tres definiciones de clase. Esto le proporcionará el mejor rendimiento tanto para las consultas locales como para la generación de SQL.

Incluso si las propiedades en cuestión no se llaman todas "Nombre", todavía puede utilizar la solución nterface agregando una propiedad "Nombre" a cada una y teniendo su getter y setter acceder a la otra propiedad.

solución Expresión

Si la solución INAME no va a funcionar y que necesita la conversión de SQL para trabajar, puede hacer esto mediante la construcción de su consulta LINQ utilizando expresiones. Este trabajo adicional y es significativamente menos eficiente para uso local, pero se convertirá en SQL bien. El código sería algo como esto:

class Repository<T> where T:Class 
{ 
    public IQueryable<T> SearchExact(string keyword, 
            Expression<Func<T,string>> getNameExpression) 
    { 
    var param = Expression.Parameter(typeof(T), "i"); 
    return db.GetTable<T>().Where(
       Expression.Lambda<Func<T,bool>>(
        Expression.Equal(
        Expression.Invoke(
         Expression.Constant(getNameExpression), 
         param), 
        Expression.Constant(keyword), 
        param)); 
    } 
} 

y se llamaría así:

repository.SearchExact("Text To Find", i => i.Name) 
+0

Pensando lateralmente, me gusta. Linq2Sql significará que probablemente necesiten usar algún procesamiento posterior para aplicar la interfaz a las clases. – Spence

+0

El segundo enfoque (poner restricción en el método) me parece mejor, pero probablemente luego cambie el nombre del método SearchExactName. –

+0

@Spence - hay dos formas de hacerlo; puede usar clases parciales para agregar el ': IName', o puede editar el DBML y establecer el tipo base global para entidades (si todos sus tipos deben implementarlo). –

3

método de Ray es bastante buena, y si tiene la posibilidad de añadir una interfaz definitivamente el superior sin embargo, si por alguna razón, no puede agregar una interfaz a estas clases (Parte de una biblioteca de clases que no puede editar o algo así), entonces también podría considerar pasar un Func en el que podría decirle cómo obtener el nombre.

Ejem:

class Repository<T> 
{ 
    public IQueryable<T> SearchExact(string keyword, Func<T, string> getSearchField) 
    { 
    return db.GetTable<T>().Where(i => getSearchField(i) == keyword); 
    } 
} 

Se podría entonces tiene que llamar como:

var filteredList = _contactRepository.SearchExact(keyword, cr => cr.Name).ToList(); 

Aparte de estas dos opciones siempre se puede ver en el uso de reflexión para acceder a la propiedad Name sin ninguna interfaz , pero esto tiene la desventaja de que no hay una verificación en tiempo de compilación que asegure que las clases que estás aprobando realmente tienen una propiedad Name y también tiene el efecto secundario de que el LINQ no se traducirá a SQL y el filtrado ocurrirá en .NET (lo que significa que el servidor SQL podría ser golpeado m más de lo necesario).

También puede usar una consulta LINQ dinámica para lograr este efecto de lado de SQL, pero tiene los mismos problemas de tipo no seguro enumerados anteriormente.

+0

+1 para usar el enfoque funcional alternativo, en lugar del enfoque OO de la solución de Ray. –

+0

@Tim Schneider: +1 Esa debería ser la solución. Déjame confirmar hoy. – Amsakanna

+0

Sí, Func es otra buena manera de hacerlo. Tenga en cuenta que tiene la misma desventaja que la reflexión en términos de traducción: Debido a que 'getSearchField' ya es un delegado (un puntero al código máquina real), LINQ2SQL no tiene forma de analizarlo, por lo que el filtrado se realizará en .NET. El enfoque de la interfaz se traducirá con éxito a SQL, pero para que esto se traduzca debe organizar pasar una Expresión, pero esto hará que la ejecución local sea más lenta. –

Cuestiones relacionadas