Accès aux propriétés via le paramètre de type générique

J’essaie de créer un référentiel générique pour mes modèles. Actuellement, j’ai 3 modèles différents qui n’ont pas de relation entre eux. (Contacts, Notes, Rappels).

class Repository where T:class { public IQueryable SearchExact(ssortingng keyword) { //Is there a way i can make the below line generic //return db.ContactModels.Where(i => i.Name == keyword) //I also sortinged db.GetTable().Where(i => i.Name == keyword) //But the variable i doesn't have the Name property since it would know it only in the runtime //db also has a method ITable GetTable(Type modelType) but don't think if that would help me } } 

Dans MainViewModel, j’appelle la méthode Search comme suit:

 Repository _contactRepository = new Repository(); public void Search(ssortingng keyword) { var filteredList = _contactRepository.SearchExact(keyword).ToList(); } 

Solution:

Enfin allé avec la solution Dynamic Expression de Ray:

 public IQueryable SearchExact(ssortingng searchKeyword, ssortingng columnName) { ParameterExpression param = Expression.Parameter(typeof(TModel), "i"); Expression left = Expression.Property(param, typeof(TModel).GetProperty(columnName)); Expression right = Expression.Constant(searchKeyword); Expression expr = Expression.Equal(left, right); } query = db.GetTable().Where(Expression.Lambda<Func>(expr, param)); 

Solution d’interface

Si vous pouvez append une interface à votre object, vous pouvez l’utiliser. Par exemple, vous pouvez définir:

  public interface IName { ssortingng Name { get; } } 

Ensuite, votre référentiel pourrait être déclaré comme:

 class Repository where T:class, IName { public IQueryable SearchExact(ssortingng keyword) { return db.GetTable().Where(i => i.Name == keyword); } } 

Solution d’interface alternative

Sinon, vous pouvez mettre le “où” sur votre méthode SearchExact en utilisant un deuxième paramètre générique:

 class Repository where T:class { public IQueryable SearchExact(ssortingng keyword) where U: T,IName { return db.GetTable().Where(i => i.Name == keyword); } } 

Cela permet à la classe Repository d’être utilisée avec des objects qui n’implémentent pas IName, alors que la méthode SearchExact ne peut être utilisée qu’avec des objects qui implémentent IName.

Solution de reflection

Si vous ne pouvez pas append une interface de type IName à vos objects, vous pouvez utiliser la reflection:

 class Repository where T:class { static PropertyInfo _nameProperty = typeof(T).GetProperty("Name"); public IQueryable SearchExact(ssortingng keyword) { return db.GetTable().Where(i => (ssortingng)_nameProperty.GetValue(i) == keyword); } } 

C’est plus lent que d’utiliser une interface, mais c’est parfois le seul moyen.

Plus de notes sur la solution d’interface et pourquoi vous pourriez l’utiliser

Dans votre commentaire, vous indiquez que vous ne pouvez pas utiliser une interface mais n’expliquez pas pourquoi. Vous dites “Rien de commun n’est présent dans les trois modèles. Je pense donc qu’il est impossible de créer une interface à partir de ceux-ci.” D’après votre question, j’ai compris que les trois modèles possèdent une propriété “Nom”. Dans ce cas, il est possible d’implémenter une interface sur les trois. Il suffit de mettre en œuvre l’interface comme indiqué et “, IName” dans chacune de vos trois définitions de classe. Cela vous donnera les meilleures performances pour les requêtes locales et la génération SQL.

Même si les propriétés en question ne sont pas toutes appelées “Nom”, vous pouvez toujours utiliser la solution nterface en ajoutant une propriété “Nom” à chacune et en laissant son getter et son setter accéder à l’autre propriété.

Solution d’expression

Si la solution IName ne fonctionne pas et que vous avez besoin de la conversion SQL, vous pouvez le faire en créant votre requête LINQ à l’aide d’Expressions. Cela nécessite plus de travail et est nettement moins efficace pour une utilisation locale mais convertira bien en SQL. Le code ressemblerait à ceci:

 class Repository where T:Class { public IQueryable SearchExact(ssortingng keyword, Expression> getNameExpression) { var param = Expression.Parameter(typeof(T), "i"); return db.GetTable().Where( Expression.Lambda>( Expression.Equal( Expression.Invoke( Expression.Constant(getNameExpression), param), Expression.Constant(keyword), param)); } } 

et on l’appellerait ainsi:

 repository.SearchExact("Text To Find", i => i.Name) 

La méthode de Ray est assez bonne, et si vous avez la possibilité d’append une interface définitivement supérieure, cependant si pour une raison quelconque vous ne pouvez pas append d’interface à ces classes (partie d’une bibliothèque de classes que vous ne pouvez pas éditer), alors vous pourrait également envisager de passer un Func dans lequel pourrait dire comment obtenir le nom.

PAR EXEMPLE:

 class Repository { public IQueryable SearchExact(ssortingng keyword, Func getSearchField) { return db.GetTable().Where(i => getSearchField(i) == keyword); } } 

Vous devrez alors l’appeler comme suit:

 var filteredList = _contactRepository.SearchExact(keyword, cr => cr.Name).ToList(); 

Outre ces deux options, vous pouvez toujours utiliser la reflection pour accéder à la propriété Name sans interface, mais l’inconvénient est qu’il n’y a pas de vérification à la compilation qui permet de s’assurer que les classes que vous passez ont effectivement une propriété Name et aussi L’effet secondaire est que le LINQ ne sera pas traduit en SQL et que le filtrage aura lieu en .NET (ce qui signifie que le serveur SQL pourrait être touché plus que nécessaire).

Vous pouvez également utiliser une requête LINQ dynamic pour obtenir cet effet côté SQL, mais les mêmes problèmes que ceux décrits ci-dessus ne concernent pas le type.