通过Generic类型参数访问属性

时间:2010-06-17 06:40:44

标签: c# wpf linq-to-sql generics

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

class Repository<T> where T:class
{
    public IQueryable<T> 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<T>().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<ContactModel> _contactRepository = new Repository<ContactModel>();

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

解决方案:

最后使用了Ray的动态表达解决方案:

public IQueryable<TModel> 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<TModel>().Where(Expression.Lambda<Func<TModel, bool>>(expr, param));

2 个答案:

答案 0 :(得分:13)

界面解决方案

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

 public interface IName
 {
   string Name { get; }
 }

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

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

备用界面解决方案

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

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

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

反思解决方案

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

class Repository<T> where T:class
{
  static PropertyInfo _nameProperty = typeof(T).GetProperty("Name");

  public IQueryable<T> SearchExact(string keyword)
  {
    return db.GetTable<T>().Where(i => (string)_nameProperty.GetValue(i) == keyword);
  }
}

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

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

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

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

表达式解决方案

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

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

因此会被称为:

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

答案 1 :(得分:3)

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

EG:

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

然后你必须把它称为:

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

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

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