C # LINQ to SQL: Refactorizando este método GetByID genérico

c# expression-trees generics linq-to-sql

Pregunta

Escribí el siguiente método.

public T GetByID(int id)
{
    var dbcontext = DB;
    var table = dbcontext.GetTable<T>();
    return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}

Básicamente es un método en una clase genérica donde T es una clase en un DataContext.

El método obtiene la tabla del tipo de T ( GetTable ) y verifica la primera propiedad (siempre es el ID) al parámetro ingresado.

El problema con esto es que primero tuve que convertir la tabla de elementos en una lista para ejecutar un GetType en la propiedad, pero esto no es muy conveniente porque todos los elementos de la tabla tienen que ser enumerados y convertidos en una List .

¿Cómo puedo refactorizar este método para evitar un ToList en toda la tabla?

[Actualizar]

La razón por la que no puedo ejecutar el Where directamente en la tabla es porque recibí esta excepción:

El método 'System.Reflection.PropertyInfo [] GetProperties ()' no tiene traducción compatible con SQL.

Porque GetProperties no se puede traducir a SQL.

[Actualizar]

Algunas personas han sugerido usar una interfaz para T , pero el problema es que el parámetro T será una clase que se genera automáticamente en [DataContextName] .designer.cs , por lo que no puedo hacer que implemente una interfaz (y no es factible implementarla las interfaces para todas estas "clases de base de datos" de LINQ, y también, el archivo se regenerará una vez que agregue nuevas tablas a DataContext, perdiendo así todos los datos escritos).

Por lo tanto, tiene que haber una mejor manera de hacer esto ...

[Actualizar]

Ahora he implementado mi código como la sugerencia de Neil Williams , pero todavía tengo problemas. Aquí hay extractos del código:

Interfaz:

public T GetByID(int id)
{
    var dbcontext = DB;
    var table = dbcontext.GetTable<T>();
    return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}

DataContext [Ver código]:

public T GetByID(int id)
{
    var dbcontext = DB;
    var table = dbcontext.GetTable<T>();
    return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}

Método genérico:

public T GetByID(int id)
{
    var dbcontext = DB;
    var table = dbcontext.GetTable<T>();
    return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}

La excepción se está return table.SingleOrDefault(e => e.ID.Equals(id)); en esta línea: return table.SingleOrDefault(e => e.ID.Equals(id)); y la excepción es:

System.NotSupportedException: The member 'MusicRepo_DataContext.IHasID.ID' has no supported translation to SQL.

[Actualización] Solución:

Con la ayuda de la respuesta publicada de Denis Troller y el enlace al post en el blog Code Rant , finalmente logré encontrar una solución:

public T GetByID(int id)
{
    var dbcontext = DB;
    var table = dbcontext.GetTable<T>();
    return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}

Respuesta aceptada

Lo que necesita es construir un árbol de expresiones que LINQ to SQL pueda entender. Suponiendo que su propiedad "id" siempre se llame "id":

public virtual T GetById<T>(short id)
{
    var itemParameter = Expression.Parameter(typeof(T), "item");
    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                itemParameter,
                "id"
                ),
            Expression.Constant(id)
            ),
        new[] { itemParameter }
        );
    var table = DB.GetTable<T>();
    return table.Where(whereExpression).Single();
}

Esto debería funcionar. Fue prestado descaradamente de este blog . Esto es básicamente lo que hace LINQ to SQL cuando escribe una consulta como

public virtual T GetById<T>(short id)
{
    var itemParameter = Expression.Parameter(typeof(T), "item");
    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                itemParameter,
                "id"
                ),
            Expression.Constant(id)
            ),
        new[] { itemParameter }
        );
    var table = DB.GetTable<T>();
    return table.Where(whereExpression).Single();
}

Simplemente haga el trabajo para LTS porque el compilador no puede crearlo por usted, ya que nada puede exigir que T tenga una propiedad "id", y no puede asignar una propiedad "id" arbitraria desde una interfaz a la base de datos.

==== ACTUALIZACIÓN ====

De acuerdo, aquí hay una implementación simple para encontrar el nombre de la clave principal, asumiendo que solo hay una (no una clave primaria compuesta), y asumiendo que todo es bien tipográfico (es decir, su clave principal es compatible con el tipo "corto") usar en la función GetById):

public virtual T GetById<T>(short id)
{
    var itemParameter = Expression.Parameter(typeof(T), "item");
    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                itemParameter,
                "id"
                ),
            Expression.Constant(id)
            ),
        new[] { itemParameter }
        );
    var table = DB.GetTable<T>();
    return table.Where(whereExpression).Single();
}

Respuesta popular

Algunos pensamientos...

Simplemente elimine la llamada ToList (), SingleOrDefault funciona con un IEnumerable que supongo que la tabla es.

Almacenar en caché la llamada a e.GetType (). GetProperties (). Primero () para obtener el PropertyInfo devuelto.

¿No puede simplemente agregar una restricción a T que los obligaría a implementar una interfaz que expone la propiedad Id?




Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué