通过Generic类型参数访问属性

我正在尝试为我的模型创建一个通用存储库。 目前我有3种不同的型号,它们之间没有任何关系。 (联系人,备注,提醒)。

class Repository where T:class { public IQueryable SearchExact(string keyword) { //Is there a way i can make the below line generic //return db.ContactModels.Where(i => i.Name == keyword) //I also tried 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 } } 

在MainViewModel中,我像这样调用Search方法:

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

解:

最后是Ray的动态表达解决方案:

 public IQueryable SearchExact(string searchKeyword, string 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)); 

接口方案

如果可以为对象添加接口,则可以使用该接口。 例如,您可以定义:

  public interface IName { string Name { get; } } 

然后您的存储库可以声明为:

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

备用接口解决方案

或者,您可以使用第二个通用参数在SearchExact方法上放置“where”:

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

这允许Repository类与不实现IName的对象一起使用,而SearchExact方法只能与实现IName的对象一起使用。

反思解决方案

如果无法为对象添加类似IName的界面,则可以使用reflection:

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

这比使用接口慢,但有时它是唯一的方法。

有关接口解决方案的更多说明以及您可能使用它的原因

在您的评论中,您提到您不能使用界面但不解释原因。 你说“这三个模型中没有任何共同点。所以我认为用它们制作界面是不可能的。” 根据您的问题,我了解到所有三个模型都具有“名称”属性。 在这种情况下,可以在所有三个上实现接口。 只需实现如图所示的界面,并为每个三个类定义实现“,IName”。 这将为您提供本地查询和SQL生成的最佳性能。

即使所讨论的属性并非都被称为“名称”,您仍然可以通过向每个属性添加“Name”属性并使其getter和setter访问其他属性来使用nterface解决方案。

表达方案

如果IName解决方案不起作用并且您需要SQL转换才能工作,则可以通过使用Expressions构建LINQ查询来完成此操作。 这项工作越多,本地使用效率就越低,但会很好地转换为SQL。 代码将是这样的:

 class Repository where T:Class { public IQueryable SearchExact(string 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)); } } 

因此会被称为:

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

Ray的方法非常好,如果你有能力添加一个绝对优越的接口但是如果由于某种原因你无法为这些类添加接口(类库的一部分,你无法编辑或者其他)那么你还可以考虑传递一个Func,其中可以告诉它如何获得名称。

例如:

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

然后你必须把它称为:

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

除了这两个选项之外,你总是可以考虑使用reflection来访问Name属性而不需要任何接口,但这有一个缺点,即没有编译时检查确保你传递的类实际上有一个Name属性。有一个副作用,LINQ将不会被转换为SQL,并且过滤将在.NET中发生(意味着SQL服务器可能会受到更多的打击)。

您还可以使用动态LINQ查询来实现此SQL端效果,但它具有上面列出的相同的非类型安全问题。