C # LINQ to SQL: refactorizando este método Generic GetByID

Escribí el siguiente método.

public T GetByID(int id) { var dbcontext = DB; var table = dbcontext.GetTable(); 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 comprueba la primera propiedad (siempre el ID) del 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 deben enumerarse y convertirse en una List .

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

[Actualizar]

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

El método ‘System.Reflection.PropertyInfo [] GetProperties ()’ no tiene traducción soportada a 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 genere automáticamente en [DataContextName] .designer.cs , y por lo tanto no puedo hacer que implemente una interfaz (y no es factible implementarlo). 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í todos los datos escritos).

Entonces, 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 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 where T : class, IHasID,new() { public T GetByID(int id) { var dbcontext = DB; var table = dbcontext.GetTable(); return table.SingleOrDefault(e => e.ID.Equals(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 a la publicación en el blog de Code Rant , 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> ( Expression.Equal( Expression.Property( itemParameter, typeof (T).GetPrimaryKey().Name ), Expression.Constant(id) ), new[] {itemParameter} ); return dbcontext.GetTable().Where(whereExpression).Single(); } 

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

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

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

 var Q = from t in Context.GetTable 

Simplemente haces el trabajo para LTS porque el comstackdor no puede crear eso para ti, ya que nada puede hacer cumplir que T tiene una propiedad "id", y no puedes mapear una propiedad "id" arbitraria desde una interfaz a la base de datos.

==== ACTUALIZACIÓN ====

Bien, aquí hay una implementación simple para encontrar el nombre de la clave primaria, suponiendo que solo hay una (no una clave primaria compuesta), y suponiendo que todo está bien escrito (es decir, su clave principal es compatible con el tipo "corto" que uso en la función GetById):

 public virtual T GetById(short id) { var itemParameter = Expression.Parameter(typeof(T), "item"); var whereExpression = Expression.Lambda> ( Expression.Equal( Expression.Property( itemParameter, GetPrimaryKeyName() ), Expression.Constant(id) ), new[] { itemParameter } ); var table = DB.GetTable(); return table.Where(whereExpression).Single(); } public string GetPrimaryKeyName() { var type = Mapping.GetMetaType(typeof(T)); var PK = (from m in type.DataMembers where m.IsPrimaryKey select m).Single(); return PK.Name; } 

¿Qué sucede si vuelve a trabajar con esto para usar GetTable (). Where (…) y poner allí su filtro?

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.

Algunos pensamientos…

Simplemente elimine la llamada a Lista (), SingleOrDefault funciona con un IEnumerablemente, supongo que es una tabla.

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

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

Tal vez ejecutar 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(query).FirstOrDefault(); } } 

Respecto a:

System.NotSupportedException: el miembro ‘MusicRepo_DataContext.IHasID.ID’ 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(); } } } } 

Ok, mira esta implementación de demostración. Es un bash 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().InsertOnSubmit(new DataHelperDb1() { Name = "DataHelperDb1Desc1", Id = 1 }); dc.GetTable().InsertOnSubmit(new DataHelperDb2() { Name = "DataHelperDb2Desc1", Key1 = "A", Key2 = "1" }); dc.SubmitChanges(); Console.WriteLine("Name:" + GetByID(dc.GetTable(), 1).Name); Console.WriteLine(""); Console.WriteLine(""); Console.WriteLine("Name:" + GetByID(dc.GetTable(), 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; public Table DataHelperD2; } [Table(Name = "DataHelperDb1")] public class DataHelperDb1 : Entity { [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 { [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 where TEntity : new() { public static TEntity SearchObjInstance(TKey key) { var res = new TEntity(); var targhetPropertyInfos = GetPrimaryKey().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 GetPrimaryKey() { 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(Table source, TKey id) where TEntity : Entity, new() { var searchObj = Entity.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