C # LINQ to SQL: refactoring di questo metodo GetByID generico

c# expression-trees generics linq-to-sql

Domanda

Ho scritto il seguente metodo.

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 con questo è che ho dovuto convertire la tabella di elementi in una lista prima di eseguire un GetType sulla proprietà, ma questo non è molto conveniente perché tutti gli elementi della tabella devono essere enumerati e convertiti in una List .

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

[Aggiornare]

Il motivo per cui non riesco a eseguire il punto Where direttamente sul tavolo è perché ricevo questa eccezione:

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

Perché GetProperties non può essere tradotto in SQL.

[Aggiornare]

Alcune persone hanno suggerito di usare un'interfaccia per T , ma il problema è che il parametro T sarà una classe generata 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 che aggiungo nuove tabelle a DataContext, perdendo così tutti i dati scritti).

Quindi, ci deve essere un modo migliore per farlo ...

[Aggiornare]

Ora ho implementato il mio codice come suggerimento di Neil Williams , ma ho 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 generata su questa riga: 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 della risposta inviata da Denis Troller e il link al post sul blog Code Rant , sono finalmente 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();
}

Risposta accettata

Quello di cui hai bisogno è creare un albero di espressioni che LINQ to SQL possa comprendere. Supponendo che la tua proprietà "id" sia sempre chiamata "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 questo blog . Questo è fondamentalmente ciò che LINQ to SQL fa quando scrivi 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ò crearlo per te, dal momento che nulla può imporre che T abbia una proprietà "id" e non puoi mappare una proprietà "id" arbitraria da un'interfaccia al database.

==== AGGIORNAMENTO ====

OK, ecco una semplice implementazione per trovare il nome della chiave primaria, supponendo che ci sia solo una (non una chiave primaria composta), e assumendo che tutto sia ben scritto (cioè, la tua chiave primaria è compatibile con il tipo "corto" tu utilizzare 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;
}

Risposta popolare

Alcuni pensieri...

Basta rimuovere la chiamata ToList (), SingleOrDefault funziona con un oggetto IEnumerably che presumo sia la tabella.

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

Non puoi semplicemente aggiungere un vincolo a T che li costringerebbe ad implementare un'interfaccia che espone la proprietà Id?



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché