2009-04-09 8 views
21

Escribí el siguiente método.C# LINQ to SQL: Refactorizando este método Generic GetByID

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 se trata de 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 comprueba la primera propiedad (que siempre es la ID) del parámetro ingresado.

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

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

[Actualización]

La razón por la que no puede ejecutar el Where directamente sobre la mesa es porque recibo esta excepción:

método 'System.Reflection.PropertyInfo [] GetProperties () 'no tiene traducción soportada a SQL.

Porque GetProperties no se puede traducir a SQL.

[Actualización]

Algunas personas han sugerido el uso de una interfaz para T, pero el problema es que el parámetro T será una clase que se genera en automático .designer.cs [DataContextName], y por lo tanto no puedo implementar una interfaz (y no es factible implementar 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 al DataContext, perdiendo así todo el datos escritos).

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

[Actualización]

ahora han puesto en práctica mi código como Neil Williams 'sugerencia, pero todavía estoy teniendo problemas .He aquí algunos fragmentos de código:

Interfaz:

public interface IHasID 
{ 
    int ID { get; set; } 
} 

DataContext [Ver código]:

namespace MusicRepo_DataContext 
{ 
    partial class Artist : IHasID 
    { 
     public int ID 
     { 
      get { return ArtistID; } 
      set { throw new System.NotImplementedException(); } 
     } 
    } 
} 

Método Genérico:

public class DBAccess<T> where T : class, IHasID,new() 
{ 
    public T GetByID(int id) 
    { 
     var dbcontext = DB; 
     var table = dbcontext.GetTable<T>(); 

     return table.SingleOrDefault(e => e.ID.Equals(id)); 
    } 
} 

La excepción está siendo lanzado 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 Denis Troller 's publicado respuesta y el enlace a la publicación en el Code Rant blog, finalmente logré encontrar una solución:

public static PropertyInfo GetPrimaryKey(this Type entityType) 
{ 
    foreach (PropertyInfo property in entityType.GetProperties()) 
    { 
     ColumnAttribute[] attributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), true); 
     if (attributes.Length == 1) 
     { 
      ColumnAttribute columnAttribute = attributes[0]; 
      if (columnAttribute.IsPrimaryKey) 
      { 
       if (property.PropertyType != typeof(int)) 
       { 
        throw new ApplicationException(string.Format("Primary key, '{0}', of type '{1}' is not int", 
           property.Name, entityType)); 
       } 
       return property; 
      } 
     } 
    } 
    throw new ApplicationException(string.Format("No primary key defined for type {0}", entityType.Name)); 
} 

public T GetByID(int id) 
{ 
    var dbcontext = DB; 

    var itemParameter = Expression.Parameter(typeof (T), "item"); 
    var whereExpression = Expression.Lambda<Func<T, bool>> 
     (
     Expression.Equal(
      Expression.Property(
       itemParameter, 
       typeof (T).GetPrimaryKey().Name 
       ), 
      Expression.Constant(id) 
      ), 
     new[] {itemParameter} 
     ); 
    return dbcontext.GetTable<T>().Where(whereExpression).Single(); 
} 
+0

Usted no necesita preocuparse por el diseñador genera archivos o el diseñador edmx sobre escribirlos .. que no implementan la interfaz en el archivo del diseñador que .. Escribiré una clase parcial para las entidades que implementan la interfaz. – meandmycode

+0

Pero eso significa que debo hacer que cada "clase de db" implemente esta interfaz, ¿no? –

+0

Sí, lo haría, pero es un trabajo de una sola vez y luego su código será mucho más robusto. –

Respuesta

18

Lo que se necesita es construir un árbol de expresión que LINQ to SQL puede entender. Suponiendo que su propiedad "id" siempre se denomina "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 hacer el truco. Fue tomado prestamente de this blog. Esto es básicamente lo LINQ  -  SQL no cuando se escribe una consulta como

var Q = from t in Context.GetTable<T)() 
     where t.id == id 
     select t; 

que acaba de hacer el trabajo por LTS porque el compilador no puede crear eso para usted, ya que nada puede hacer cumplir que T tiene un "id "propiedad, y no se puede asignar una propiedad de" id "arbitraria desde una interfaz a la base de datos.

ACTUALIZACIÓN ==== ====

bien, he aquí una aplicación sencilla para encontrar el nombre de la clave primaria, suponiendo que sólo hay una (no es una clave principal compuesta), y suponiendo que todo está bien tipo- Wise (es decir, la clave principal es compatible con el tipo "corto" se utiliza 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, 
       GetPrimaryKeyName<T>() 
       ), 
      Expression.Constant(id) 
      ), 
     new[] { itemParameter } 
     ); 
    var table = DB.GetTable<T>(); 
    return table.Where(whereExpression).Single(); 
} 


public string GetPrimaryKeyName<T>() 
{ 
    var type = Mapping.GetMetaType(typeof(T)); 

    var PK = (from m in type.DataMembers 
       where m.IsPrimaryKey 
       select m).Single(); 
    return PK.Name; 
} 
+0

¿Y cuál es la solución para superar diferentes nombres de campo? –

+0

como en forma automática, sin especificarlos en un parámetro abstracto de la clase secundaria o cualquier cosa que requiera mantenimiento adicional –

+0

, podría tratar de extraerlo de la fuente de mapeo de DataContext, creo. Déjame ver ... –

1

¿Qué pasa si se vuelve a trabajar con esto para usar GetTable(). ¿Dónde (...), y colocas tu filtro allí?

Eso sería más eficiente, ya que el método de extensión Where debería encargarse de su filtrado mejor que traer toda la tabla a una lista.

+0

ver mi actualización en la respuesta –

1

Algunos pensamientos ...

Basta con retirar la llamada ToList(), SingleOrDefault trabaja con un IEnumerably que supongo que es la tabla.

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

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

0

Quizás la ejecución de una consulta sea una buena idea.

public static T GetByID(int id) 
    { 
     Type type = typeof(T); 
     //get table name 
     var att = type.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault(); 
     string tablename = att == null ? "" : ((TableAttribute)att).Name; 
     //make a query 
     if (string.IsNullOrEmpty(tablename)) 
      return null; 
     else 
     { 
      string query = string.Format("Select * from {0} where {1} = {2}", new object[] { tablename, "ID", id }); 

      //and execute 
      return dbcontext.ExecuteQuery<T>(query).FirstOrDefault(); 
     } 
    } 
+0

El nombre de la columna varía de la tabla a la tabla –

+0

OK, no he obtenido esa clave primaria. Los nombres de las columnas pueden variar. Veo que ya hay una solución para obtener el nombre de la columna de la clave principal. Saludos –

0

con respecto a:

System.NotSupportedException: el miembro MusicRepo_DataContext.IHasID. yo D 'no tiene traducción soportada a SQL.

La solución simple a su problema inicial es especificar una Expresión. Vea a continuación, funciona como un encanto para mí.

public interface IHasID 
{ 
    int ID { get; set; } 
} 
DataContext [View Code]: 

namespace MusicRepo_DataContext 
{ 
    partial class Artist : IHasID 
    { 
     [Column(Name = "ArtistID", Expression = "ArtistID")] 
     public int ID 
     { 
      get { return ArtistID; } 
      set { throw new System.NotImplementedException(); } 
     } 
    } 
} 
0

bien, compruebe esta aplicación de demostración. Es un intento de obtener GetById genérico con datacontext (Linq To Sql). También es compatible con propiedad multi clave.

using System; 
using System.Data.Linq; 
using System.Data.Linq.Mapping; 
using System.Linq; 
using System.Reflection; 
using System.Collections.Generic; 

public static class Programm 
{ 
    public const string ConnectionString = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=TestDb2;Persist Security Info=True;integrated Security=True"; 

    static void Main() 
    { 
     using (var dc = new DataContextDom(ConnectionString)) 
     { 
      if (dc.DatabaseExists()) 
       dc.DeleteDatabase(); 
      dc.CreateDatabase(); 
      dc.GetTable<DataHelperDb1>().InsertOnSubmit(new DataHelperDb1() { Name = "DataHelperDb1Desc1", Id = 1 }); 
      dc.GetTable<DataHelperDb2>().InsertOnSubmit(new DataHelperDb2() { Name = "DataHelperDb2Desc1", Key1 = "A", Key2 = "1" }); 
      dc.SubmitChanges(); 

      Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb1>(), 1).Name); 
      Console.WriteLine(""); 
      Console.WriteLine(""); 
      Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb2>(), new PkClass { Key1 = "A", Key2 = "1" }).Name); 
     } 
    } 

    //Datacontext definition 
    [Database(Name = "TestDb2")] 
    public class DataContextDom : DataContext 
    { 
     public DataContextDom(string connStr) : base(connStr) { } 
     public Table<DataHelperDb1> DataHelperDb1; 
     public Table<DataHelperDb2> DataHelperD2; 
    } 

    [Table(Name = "DataHelperDb1")] 
    public class DataHelperDb1 : Entity<DataHelperDb1, int> 
    { 
     [Column(IsPrimaryKey = true)] 
     public int Id { get; set; } 
     [Column] 
     public string Name { get; set; } 
    } 

    public class PkClass 
    { 
     public string Key1 { get; set; } 
     public string Key2 { get; set; } 
    } 
    [Table(Name = "DataHelperDb2")] 
    public class DataHelperDb2 : Entity<DataHelperDb2, PkClass> 
    { 
     [Column(IsPrimaryKey = true)] 
     public string Key1 { get; set; } 
     [Column(IsPrimaryKey = true)] 
     public string Key2 { get; set; } 
     [Column] 
     public string Name { get; set; } 
    } 

    public class Entity<TEntity, TKey> where TEntity : new() 
    { 
     public static TEntity SearchObjInstance(TKey key) 
     { 
      var res = new TEntity(); 
      var targhetPropertyInfos = GetPrimaryKey<TEntity>().ToList(); 
      if (targhetPropertyInfos.Count == 1) 
      { 
       targhetPropertyInfos.First().SetValue(res, key, null); 
      } 
      else if (targhetPropertyInfos.Count > 1) 
      { 
       var sourcePropertyInfos = key.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); 
       foreach (var sourcePi in sourcePropertyInfos) 
       { 
        var destinationPi = targhetPropertyInfos.FirstOrDefault(x => x.Name == sourcePi.Name); 
        if (destinationPi == null || sourcePi.PropertyType != destinationPi.PropertyType) 
         continue; 

        object value = sourcePi.GetValue(key, null); 
        destinationPi.SetValue(res, value, null); 
       } 
      } 
      return res; 
     } 
    } 

    public static IEnumerable<PropertyInfo> GetPrimaryKey<T>() 
    { 
     foreach (var info in typeof(T).GetProperties().ToList()) 
     { 
      if (info.GetCustomAttributes(false) 
      .Where(x => x.GetType() == typeof(ColumnAttribute)) 
      .Where(x => ((ColumnAttribute)x).IsPrimaryKey) 
      .Any()) 
       yield return info; 
     } 
    } 
    //Move in repository pattern 
    public static TEntity GetByID<TEntity, TKey>(Table<TEntity> source, TKey id) where TEntity : Entity<TEntity, TKey>, new() 
    { 
     var searchObj = Entity<TEntity, TKey>.SearchObjInstance(id); 
     Console.WriteLine(source.Where(e => e.Equals(searchObj)).ToString()); 
     return source.Single(e => e.Equals(searchObj)); 
    } 
} 

Resultado:

SELECT [t0].[Id], [t0].[Name] 
FROM [DataHelperDb1] AS [t0] 
WHERE [t0].[Id] = @p0 

Name:DataHelperDb1Desc1 


SELECT [t0].[Key1], [t0].[Key2], [t0].[Name] 
FROM [DataHelperDb2] AS [t0] 
WHERE ([t0].[Key1] = @p0) AND ([t0].[Key2] = @p1) 

Name:DataHelperDb2Desc1