2009-04-09 14 views
21

Ho scritto il seguente metodo.C# LINQ a SQL: refactoring di questo metodo GetByID generico

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); 
} 

Fondamentalmente si tratta di un metodo in una classe generica in cui T è una classe in un DataContext.

Il metodo ottiene la tabella dal tipo di T (GetTable) e verifica la prima proprietà (sempre l'ID) al parametro immesso.

Il problema di questo è che ho avuto per convertire la tavola degli elementi di una lista prima di eseguire un GetType sulla proprietà, ma questo non è molto conveniente perché tutti gli elementi della tavola devono essere enumerati e convertito in un List.

Come posso refactoring questo metodo per evitare un sull'intera tabella?

[Update]

Il motivo non riesco a eseguire l'Where direttamente sul tavolo è perché ricevo questa eccezione:

MODALITA 'System.Reflection.PropertyInfo [] GetProperties () "non ha traduzione supportata in SQL.

Perché GetProperties non può essere convertito in SQL.

[Update]

Alcune persone hanno suggerito di usare un'interfaccia per T, ma il problema è che il parametro T sarà una classe che viene generato automaticamente in [DataContextName] .designer.cs, e quindi non riesco a farlo implementare un'interfaccia (e non è fattibile implementare le interfacce per tutte queste "classi di database" di LINQ, e inoltre, il file verrà rigenerato una volta aggiunte nuove tabelle al DataContext, perdendo così tutte le dati scritti).

Quindi, ci deve essere un modo migliore per fare questo ...

[Update]

ora ho implementato il mio codice come Neil Williams 'suggerimento, ma sto ancora problemi .Ecco alcuni estratti del codice:

Interfaccia:

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

DataContext [Visualizza codice]:

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

metodo generico:

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)); 
    } 
} 

L'eccezione viene gettata su questa linea: return table.SingleOrDefault(e => e.ID.Equals(id)); e l'eccezione è:

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

[Aggiornamento] Soluzione:

Con l'aiuto di Denis Troller 's postato risposta e il link al post allo Code Rant blog, finalmente sono riuscito a trovare una soluzione:

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

Non è necessario preoccuparsi di generare il progettista file o il progettista edmx oltre a scrivere di loro .. non si implementano l'interfaccia nel file di progettazione che si .. scriveremo una classe parziale per le entità che implementano l'interfaccia. – meandmycode

+0

Ma ciò significa che dovrei rendere ogni "classe db" implementare questa interfaccia, no? –

+0

Sì, sì, ma è un pezzo di lavoro e quindi il tuo codice sarà molto più robusto. –

risposta

18

Quello che vi serve è quello di costruire un albero di espressione che LINQ to SQL può capire. Assumendo che il "id" proprietà è sempre chiamato "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(); 
} 

Questo dovrebbe fare il trucco. E 'stato spudoratamente preso in prestito da this blog. Questo è fondamentalmente ciò che LINQ  -  SQL fa quando si scrive una query come

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

Basta fare il lavoro per LTS perché il compilatore non può creare per voi, dal momento che nulla può imporre che T ha un "id "proprietà, e non è possibile mappare una proprietà" id "arbitraria da un'interfaccia al database.

==== ==== UPDATE

OK, ecco una semplice implementazione per trovare il nome della chiave primaria, presumere che vi sia un solo (non una chiave primaria composta), e supponendo che tutto va bene tipo- saggio (cioè, la chiave primaria è compatibile con il tipo di "corto" si utilizza nella funzione 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

E qual è la soluzione per superare diversi nomi di campo? –

+0

come in automatico, non specificandoli in un parametro astratto della sottoclasse o qualsiasi altra cosa che richiede ulteriore manutenzione –

+0

si potrebbe provare a estrarlo dalla MappingSource di DataContext, credo. Fammi vedere ... –

1

Che cosa succede se si rielabora questo per utilizzare GetTable(). Dove (...) e inserire il filtro lì?

Sarebbe più efficiente, dal momento che il metodo di estensione Where dovrebbe occuparsi del filtraggio meglio di recuperare l'intera tabella in un elenco.

+0

vedere il mio aggiornamento nella risposta –

1

Alcuni pensieri ...

è sufficiente rimuovere la chiamata ToList(), SingleOrDefault funziona con un IEnumerably che presumo tavolo è.

Memorizzare la chiamata a e.GetType(). GetProperties(). First() per restituire PropertyInfo.

Non è sufficiente aggiungere un vincolo a T che li obbliga a implementare un'interfaccia che espone la proprietà Id?

0

Forse l'esecuzione di una query potrebbe essere una buona 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

Il nome della colonna varia da tabella a tabella –

+0

OK, non ho capito che i nomi delle colonne della chiave primaria possono variare. Vedo che esiste già una soluzione per ottenere il nome della colonna della chiave primaria. Cordiali saluti –

0

quanto riguarda:

System.NotSupportedException: il membro 'MusicRepo_DataContext.IHasID. io D 'non ha traduzione supportata in SQL.

La semplice soluzione al problema iniziale è specificare un'espressione. Vedi sotto, funziona come un fascino per me.

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

Ok, controlla questa implementazione demo. Il tentativo di ottenere GetById generico con datacontext (Linq To Sql). Compatibile anche con proprietà multi chiave.

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)); 
    } 
} 

Risultato:

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