Доступ к свойствам через параметр общего типа - PullRequest
5 голосов
/ 17 июня 2010

Я пытаюсь создать общий репозиторий для моих моделей. В настоящее время у меня есть 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 я вызываю метод поиска следующим образом:

Repository<ContactModel> _contactRepository = new Repository<ContactModel>();

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

Решение:

Наконец-то мы выбрали решение Ray Dynamic Expression:

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 ]

13 голосов
/ 17 июня 2010

Интерфейсное решение

Если вы можете добавить интерфейс к вашему объекту, вы можете использовать его. Например, вы можете определить:

 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», вы все равно можете использовать решение nterface, добавив свойство «Name» к каждому и имея его метод получения и установки доступа к другому свойству.

Экспресс-решение

Если решение IName не будет работать и вам нужно преобразование SQL для работы, вы можете сделать это, создав запрос 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)
3 голосов
/ 17 июня 2010

Метод Рэя довольно хорош, и если у вас есть возможность добавить интерфейс, безусловно, лучший, но если по какой-то причине вы не можете добавить интерфейс к этим классам (часть библиотеки классов, которую вы не можете редактировать или что-то еще)) тогда вы могли бы также рассмотреть возможность передачи 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 без какого-либо интерфейса, но это имеет недостаток, заключающийся в том, что нет никакой проверки во время компиляции, которая гарантирует, что передаваемые классы действительно имеют свойство Name, а также имеет побочный эффект, заключающийся в том, что LINQ не будет переведен в SQLи фильтрация будет происходить в .NET (это означает, что сервер SQL может получить больше, чем необходимо).

Вы также можете использовать динамический запрос LINQ для достижения этого побочного эффекта SQL, но он имеет тот жеперечисленные выше проблемы с типобезопасностью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...