C # LINQ to SQL: Refactoring cette méthode GetByID générique

c# expression-trees generics linq-to-sql

Question

J'ai écrit la méthode suivante.

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

Fondamentalement, c'est une méthode dans une classe générique où T est une classe dans un DataContext.

La méthode extrait la table du type de T ( GetTable ) et recherche la première propriété (toujours l'ID) du paramètre entré.

Le problème, c’est que j’ai dû convertir la table des éléments en une liste pour pouvoir exécuter un GetType sur la propriété, mais cela n’est pas très pratique car tous les éléments de la table doivent être énumérés et convertis en List .

Comment puis-je refactoriser cette méthode pour éviter une ToList de ToList sur toute la table?

[Mettre à jour]

La raison pour laquelle je ne peux pas exécuter directement l' Where sur la table est que je reçois cette exception:

La méthode 'System.Reflection.PropertyInfo [] GetProperties ()' n'a aucune traduction prise en charge en SQL.

Parce que GetProperties ne peut pas être traduit en SQL.

[Mettre à jour]

Certaines personnes ont suggéré d'utiliser une interface pour T , mais le problème est que le paramètre T sera une classe générée automatiquement dans [DataContextName] .designer.cs . Je ne peux donc pas lui demander d' implémenter une interface (l'implémentation n'est pas réalisable les interfaces pour toutes ces "classes de base de données" de LINQ; et aussi, le fichier sera régénéré une fois que j’ajoute de nouvelles tables au DataContext, perdant ainsi toutes les données écrites).

Donc, il doit y avoir un meilleur moyen de faire cela ...

[Mettre à jour]

J'ai maintenant implémenté mon code comme suggéré par Neil Williams , mais j'ai toujours des problèmes. Voici des extraits du code:

Interface:

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

DataContext [Voir le code]:

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

Méthode générique:

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'exception est levée sur cette ligne: return table.SingleOrDefault(e => e.ID.Equals(id)); et l'exception est:

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

[Mise à jour] Solution:

Avec l'aide de la réponse postée de Denis Troller et du lien vers l'article sur le blog Code Rant , j'ai finalement réussi à trouver une solution:

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

Réponse acceptée

Ce dont vous avez besoin est de créer un arbre d’expression que LINQ to SQL peut comprendre. En supposant que votre propriété "id" soit toujours nommée "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();
}

Cela devrait faire l'affaire. Il a été emprunté sans vergogne à ce blog . C’est essentiellement ce que LINQ to SQL fait lorsque vous écrivez une requête telle que

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

Vous ne faites que le travail pour LTS car le compilateur ne peut pas le créer pour vous, car rien ne peut imposer que T ait une propriété "id" et vous ne pouvez pas mapper une propriété "id" arbitraire d'une interface à la base de données.

==== UPDATE ====

OK, voici une implémentation simple pour trouver le nom de la clé primaire, en supposant qu’il n’en existe qu’un (pas une clé primaire composite), et en supposant que tout est bien typé (c’est-à-dire que votre clé primaire est compatible avec le type "court" utiliser dans la fonction 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;
}

Réponse populaire

Quelques idées...

Supprimez simplement l'appel ToList (), SingleOrDefault fonctionne avec un IEnumerably que je suppose que la table est.

Mettre en cache l'appel à e.GetType (). GetProperties (). First () pour obtenir le PropertyInfo renvoyé.

Pouvez-vous simplement ajouter à T une contrainte qui les obligerait à implémenter une interface qui expose la propriété Id?



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow