поздравляю с первым вопросом.
Давайте начнем с рассмотрения подхода к фильтрации коллекции данных на основе некоторых пользовательских фильтров.Я предполагаю, что NameValueCollection
Type
, который вы предпочитаете передавать в своих фильтрах, содержит PropertyNames в качестве ключей и PropertyValues в качестве значения.
Прежде чем приступить к фильтрации всей коллекции, давайте сначала выясним, как определитьесть ли у одного объекта свойства, соответствующие нашим фильтрам.И поскольку мы не знаем Type
нашего объекта до времени выполнения, нам нужно будет использовать Generics в C # для этого.
Шаг 1
- ПолучитьВсе свойства класса
Нам потребуется получить все свойства нашего универсального класса, например, <TClass>
.Выполнение этого с помощью Reflection считается медленным, и Мэтт Уоррен объясняет Почему в .NET медленное отражение и как его обойти.Поэтому мы будем реализовывать кэширование модели компонентов класса, чтобы получить ее PropertyDescriptorCollection
, которая существует в пространстве имен System.ComponentModel.PropertyDescriptorCollection.
Кэш компонентов
private static IDictionary<string, PropertyDescriptorCollection> _componentsCache
= new Dictionary<string, PropertyDescriptorCollection>();
ключ нашего Dictionary
представляет имя универсального класса, а значение содержит PropertyDescriptorCollection
этого заданного класса.
internal static bool InnerFilter<T>(T obj, NameValueCollection filters)
where T : class
{
Type type = typeof(T);
PropertyDescriptorCollection typeDescriptor = null;
if (_componentsCache.ContainsKey(type.Name))
typeDescriptor = _componentsCache[type.Name];
else
{
typeDescriptor = TypeDescriptor.GetProperties(type);
_componentsCache.Add(type.Name, typeDescriptor);
}
}
Шаг 2
- Проход по фильтрам
После того, как мы получили PropertyDescriptorCollection
для универсального класса T
в переменной typeDescriptor
, как показано выше, теперь давайте пройдемся по нашим фильтрам и посмотрим, соответствует ли какое-либо из его имен свойств любому из наших ключей фильтра.Если T
имеет имя свойства, которое соответствует любому из наших ключей фильтра, теперь мы проверяем, соответствует ли фактическое значение свойства нашему значению фильтра.Чтобы улучшить качество нашей функции поиска / фильтрации, мы будем использовать Регулярные выражения в C # , чтобы определить, является ли сравнение попаданием или промахом.
for (int i = 0; i < filters.Count; i++)
{
string filterName = filters.GetKey(i);
string filterValue = filters[i];
PropertyDescriptor propDescriptor = typeDescriptor[filterName];
if (propDescriptor == null)
continue;
else
{
string propValue = propDescriptor.GetValue(obj).ToString();
bool isMatch = Regex.IsMatch(propValue, $"({filterValue})");
if (isMatch)
return true;
else
continue;
}
}
Шаг 3
- Реализация методов расширения.
Чтобы сделать код, который мы написали, простым в использовании и повторном использовании, мы собираемся реализовать Методы расширения в C # , чтобымы можем лучше повторно использовать наши функции в любом месте нашего проекта.
- универсальная функция фильтра коллекции, которая использует вышеуказанную функцию.
, поскольку IQueryable<T>
можно преобразовать в IEnumerable<T>
с помощью. Where()
функция в System.Linq
, мы собираемся использовать это в нашем вызове функции, как показано ниже.
public static IEnumerable<T> Filter<T>(this IEnumerable<T> collection, NameValueCollection filters)
where T : class
{
if (filters.Count < 1)
return collection;
return collection.Where(x => x.InnerFilter(filters));
}
Шаг 4
Соберите все вместе.
Теперь, когда у нас есть все, что нам нужно, давайте посмотрим, как конечный / полный код выглядит как один блок кода в одном классе static
.
public static class Question54484908
{
private static IDictionary<string, PropertyDescriptorCollection> _componentsCache = new Dictionary<string, PropertyDescriptorCollection> ();
public static IEnumerable<T> Filter<T> (this IEnumerable<T> collection, NameValueCollection filters)
where T : class
{
if (filters.Count < 1)
return collection;
return collection.Where (x => x.InnerFilter (filters));
}
internal static bool InnerFilter<T> (this T obj, NameValueCollection filters)
where T : class
{
Type type = typeof (T);
PropertyDescriptorCollection typeDescriptor = null;
if (_componentsCache.ContainsKey (type.Name))
typeDescriptor = _componentsCache[type.Name];
else {
typeDescriptor = TypeDescriptor.GetProperties (type);
_componentsCache.Add (type.Name, typeDescriptor);
}
for (int i = 0; i < filters.Count; i++) {
string filterName = filters.GetKey (i);
string filterValue = filters[i];
PropertyDescriptor propDescriptor = typeDescriptor[filterName];
if (propDescriptor == null)
continue;
else {
string propValue = propDescriptor.GetValue (obj).ToString ();
bool isMatch = Regex.IsMatch (propValue, $"({filterValue})");
if (isMatch)
return true;
else
continue;
}
}
return false;
}
}
FINALLY
ФильтрацияIEnumerable<T>
, List<T>
, массивы
Вот как вы собираетесь использовать тВыше кода в любом месте вашего проекта.
private IEnumerable<Question> _questions;
_questions = new List<Question>()
{
new Question("Question 1","How do i work with tuples"),
new Question("Question 2","How to use Queryable.Where when type is set at runtime?")
};
var filters = new NameValueCollection
{
{ "Description", "work" }
};
var results = _questions.Filter(filters);
Фильтрация DbSet<T>
Каждый DbContext
имеет функцию .Set<T>
, которая возвращает DbSet<T>
, который может использоваться как IQueryable<T>
и, таким образом, наша функция может быть использована так же, как показано ниже.
Пример
_dbContext.Set<Question>().Filter(filters);
Надеюсь, что это ответ на ваш вопрос или скорее направит вас в правильном направлении.