如何在 C# 中过滤动态列和动态 属性 的 LINQ 查询?
How to filter LINQ query on dynamic columns and dynamic property in C#?
我在获取 LINQ 查询中的过滤记录以根据条件动态检索列时遇到问题,属性 也基于条件(例如包含、等于、StartsWith、EndsWith)。
我有如下记录列表 -
List<Employee> employees = new List<Employee>()
{
new Employee()
{
name ="Andy", cityCriteria="Florida West", state ="NYC"
},
new Employee()
{
name = "John", cityCriteria = "West Virginia", state = "Arizona"
},
new Employee()
{
name = "Nichole", cityCriteria = "East Florida", state = "NYC"
}
};
所以,这只是一些示例记录,数据将来自数据库,而且会有很多记录。
现在,我想要实现的是,如果有任何视频 posted 与列表中的城市匹配,我必须通知所有人。所以,我可以接收 NotificationValue 作为 City:Florida:startsWith , City:Florida: Equals , City:Florida:Contains 等,也可能有状态标准。
那么,我如何在列表中动态过滤记录,如果输入是开始于我应该使用 StartsWith ex
If Input is City:Florida:startsWith -->
var result = employees.where(i=>i.CityCriteria.StartsWith("Florida").toList();
If Input is City:Florida:Contains -->
var result = employees.where(i=>i.CityCriteria.Contains("Florida").toList();
If Input is City:Florida:EndsWith -->
var result = employees.where(i=>i.CityCriteria.EndsWith("Florida").toList();
If Input is City:Florida:Equals -->
var result = employees.where(i=>i.CityCriteria.Equals("Florida").toList();
我不想使用多个条件并形成 Where 子句。我希望它是动态的,就像我收到的开头一样,它应该替换 LINQ 查询开始、结束、等于等,而且它应该灵活地使用动态列,就像我必须对 State、Country、Zip 等应用相同的逻辑一样'
如果可能请post一些示例代码
希望对您有所帮助:
private IEnumerable<Employee> FilterDynamically(IEnumerable<Employee> employees, string input, string cityName)
{
switch (input.ToLower())
{
case "starts with":
return employees.Where(x => x.cityCriteria.StartsWith(cityName));
case "ends with":
return employees.Where(x => x.cityCriteria.EndsWith(cityName));
case "contains":
return employees.Where(x => x.cityCriteria.Contains(cityName));
case "equals":
return employees.Where(x => x.cityCriteria.Equals(cityName));
default:
return Enumerable.Empty<Employee>();
}
}
可能有很多解决方案,但通常喜欢构建一个可扩展的解决方案,并在我自己的项目中得到证明。
//In case of addition filters u just need to update this class and everything will work else where
public class Filters
{
Filters() {
maps.Add("startswith", StartsWith);
maps.Add("Contains", Contains);
maps.Add("Endswith", Endswith);
maps.Add("Equals", Equals);
}
public static readonly Filters Instance = new Filters();
public Func<Employee, string, bool> GetFilter(string filterClause)
=> maps.ContainsKey (filterClause) ? maps[filterClause] : None;
Func<Employee, string, bool> StartsWith = (e, value) => e.cityCriteria.StartsWith(value);
Func<Employee, string, bool> Contains = (e, value) => e.cityCriteria.Contains(value);
Func<Employee, string, bool> Endswith = (e, value) => e.cityCriteria.EndsWith(value);
Func<Employee, string, bool> Equals = (e, value) => e.cityCriteria.Equals(value);
//In case none of the filter cluase do not match
Func<Employee, string, bool> None = (e, value) => true;
//Filter clauses are made case insensitive by passing stringcomparer
Dictionary<string, Func<Employee, string, bool>> maps =
new Dictionary<string, Func<Employee, string, bool>>(StringComparer.OrdinalIgnoreCase);
}
易于使用和一致性的扩展方法
public static class EmployeeExtensions
{
public static IEnumerable<Employee> Filter(this IEnumerable<Employee> employees, string filterClause, string filterValue)
=> employees.Where(x => Filters.Instance.GetFilter(filterClause)(x, filterValue));
}
用法如下
public class Usage
{
public void Test()
{
var filteredEmployees =
new Employee[0]
.Filter("startswith", "florida")
.ToList();
}
}
获取 属性 值的一个简单快速的解决方案是使用 DynamicMethod。
这是我的做法,这是一个可行的解决方案:
using Newtonsoft.Json;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace NUnitTestProject1
{
public class Tests
{
static List<Employee> employees = new List<Employee>()
{
new Employee()
{
Name = "Andy", City = "Florida West", State = "NYC"
},
new Employee()
{
Name = "John", City = "West Virginia", State = "Arizona"
},
new Employee()
{
Name = "Nichole", City = "East Florida", State = "NYC"
}
};
public enum Comparison
{
StartsWith,
EndsWith,
Equals
}
public struct Condition
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
public Comparison Comparison { get; set; }
}
[TestCase("City", "Florida", Comparison.StartsWith, "Andy")]
[TestCase("State", "Arizona", Comparison.Equals, "John")]
public void TestConditions(string propertyName, string propertyValue, Comparison comparison, string expectedResult)
{
string jsonCondition = $"{{\"PropertyName\":\"{propertyName}\",\"PropertyValue\":\"{propertyValue}\",\"Comparison\":{(int)comparison}}}";
Condition parsedCondition = JsonConvert.DeserializeObject<Condition>(jsonCondition);
List<Employee> result = new List<Employee>();
var getter = GetPropertGetter(typeof(Employee).ToString(), parsedCondition.PropertyName);
switch (parsedCondition.Comparison)
{
case Comparison.StartsWith:
result = employees.Where(i => (getter(i) as string).StartsWith(parsedCondition.PropertyValue)).ToList();
break;
case Comparison.EndsWith:
result = employees.Where(i => (getter(i) as string).EndsWith(parsedCondition.PropertyValue)).ToList();
break;
case Comparison.Equals:
result = employees.Where(i => (getter(i) as string).Equals(parsedCondition.PropertyValue)).ToList();
break;
}
Assert.That(result.FirstOrDefault().Name, Does.Match(expectedResult));
}
Func<object, object> GetPropertGetter(string typeName, string propertyName)
{
Type t = Type.GetType(typeName);
PropertyInfo pi = t.GetProperty(propertyName);
MethodInfo getter = pi.GetGetMethod();
DynamicMethod dm = new DynamicMethod("GetValue", typeof(object), new Type[] { typeof(object) }, typeof(object), true);
ILGenerator lgen = dm.GetILGenerator();
lgen.Emit(OpCodes.Ldarg_0);
lgen.Emit(OpCodes.Call, getter);
if (getter.ReturnType.GetTypeInfo().IsValueType)
{
lgen.Emit(OpCodes.Box, getter.ReturnType);
}
lgen.Emit(OpCodes.Ret);
return dm.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>;
}
}
internal class Employee
{
private string name;
private string city;
private string state;
public string Name { get => name; set => name = value; }
public string City { get => city; set => city = value; }
public string State { get => state; set => state = value; }
}
}
我在获取 LINQ 查询中的过滤记录以根据条件动态检索列时遇到问题,属性 也基于条件(例如包含、等于、StartsWith、EndsWith)。
我有如下记录列表 -
List<Employee> employees = new List<Employee>()
{
new Employee()
{
name ="Andy", cityCriteria="Florida West", state ="NYC"
},
new Employee()
{
name = "John", cityCriteria = "West Virginia", state = "Arizona"
},
new Employee()
{
name = "Nichole", cityCriteria = "East Florida", state = "NYC"
}
};
所以,这只是一些示例记录,数据将来自数据库,而且会有很多记录。 现在,我想要实现的是,如果有任何视频 posted 与列表中的城市匹配,我必须通知所有人。所以,我可以接收 NotificationValue 作为 City:Florida:startsWith , City:Florida: Equals , City:Florida:Contains 等,也可能有状态标准。 那么,我如何在列表中动态过滤记录,如果输入是开始于我应该使用 StartsWith ex
If Input is City:Florida:startsWith -->
var result = employees.where(i=>i.CityCriteria.StartsWith("Florida").toList();
If Input is City:Florida:Contains -->
var result = employees.where(i=>i.CityCriteria.Contains("Florida").toList();
If Input is City:Florida:EndsWith -->
var result = employees.where(i=>i.CityCriteria.EndsWith("Florida").toList();
If Input is City:Florida:Equals -->
var result = employees.where(i=>i.CityCriteria.Equals("Florida").toList();
我不想使用多个条件并形成 Where 子句。我希望它是动态的,就像我收到的开头一样,它应该替换 LINQ 查询开始、结束、等于等,而且它应该灵活地使用动态列,就像我必须对 State、Country、Zip 等应用相同的逻辑一样'
如果可能请post一些示例代码
希望对您有所帮助:
private IEnumerable<Employee> FilterDynamically(IEnumerable<Employee> employees, string input, string cityName)
{
switch (input.ToLower())
{
case "starts with":
return employees.Where(x => x.cityCriteria.StartsWith(cityName));
case "ends with":
return employees.Where(x => x.cityCriteria.EndsWith(cityName));
case "contains":
return employees.Where(x => x.cityCriteria.Contains(cityName));
case "equals":
return employees.Where(x => x.cityCriteria.Equals(cityName));
default:
return Enumerable.Empty<Employee>();
}
}
可能有很多解决方案,但通常喜欢构建一个可扩展的解决方案,并在我自己的项目中得到证明。
//In case of addition filters u just need to update this class and everything will work else where
public class Filters
{
Filters() {
maps.Add("startswith", StartsWith);
maps.Add("Contains", Contains);
maps.Add("Endswith", Endswith);
maps.Add("Equals", Equals);
}
public static readonly Filters Instance = new Filters();
public Func<Employee, string, bool> GetFilter(string filterClause)
=> maps.ContainsKey (filterClause) ? maps[filterClause] : None;
Func<Employee, string, bool> StartsWith = (e, value) => e.cityCriteria.StartsWith(value);
Func<Employee, string, bool> Contains = (e, value) => e.cityCriteria.Contains(value);
Func<Employee, string, bool> Endswith = (e, value) => e.cityCriteria.EndsWith(value);
Func<Employee, string, bool> Equals = (e, value) => e.cityCriteria.Equals(value);
//In case none of the filter cluase do not match
Func<Employee, string, bool> None = (e, value) => true;
//Filter clauses are made case insensitive by passing stringcomparer
Dictionary<string, Func<Employee, string, bool>> maps =
new Dictionary<string, Func<Employee, string, bool>>(StringComparer.OrdinalIgnoreCase);
}
易于使用和一致性的扩展方法
public static class EmployeeExtensions
{
public static IEnumerable<Employee> Filter(this IEnumerable<Employee> employees, string filterClause, string filterValue)
=> employees.Where(x => Filters.Instance.GetFilter(filterClause)(x, filterValue));
}
用法如下
public class Usage
{
public void Test()
{
var filteredEmployees =
new Employee[0]
.Filter("startswith", "florida")
.ToList();
}
}
获取 属性 值的一个简单快速的解决方案是使用 DynamicMethod。 这是我的做法,这是一个可行的解决方案:
using Newtonsoft.Json;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace NUnitTestProject1
{
public class Tests
{
static List<Employee> employees = new List<Employee>()
{
new Employee()
{
Name = "Andy", City = "Florida West", State = "NYC"
},
new Employee()
{
Name = "John", City = "West Virginia", State = "Arizona"
},
new Employee()
{
Name = "Nichole", City = "East Florida", State = "NYC"
}
};
public enum Comparison
{
StartsWith,
EndsWith,
Equals
}
public struct Condition
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
public Comparison Comparison { get; set; }
}
[TestCase("City", "Florida", Comparison.StartsWith, "Andy")]
[TestCase("State", "Arizona", Comparison.Equals, "John")]
public void TestConditions(string propertyName, string propertyValue, Comparison comparison, string expectedResult)
{
string jsonCondition = $"{{\"PropertyName\":\"{propertyName}\",\"PropertyValue\":\"{propertyValue}\",\"Comparison\":{(int)comparison}}}";
Condition parsedCondition = JsonConvert.DeserializeObject<Condition>(jsonCondition);
List<Employee> result = new List<Employee>();
var getter = GetPropertGetter(typeof(Employee).ToString(), parsedCondition.PropertyName);
switch (parsedCondition.Comparison)
{
case Comparison.StartsWith:
result = employees.Where(i => (getter(i) as string).StartsWith(parsedCondition.PropertyValue)).ToList();
break;
case Comparison.EndsWith:
result = employees.Where(i => (getter(i) as string).EndsWith(parsedCondition.PropertyValue)).ToList();
break;
case Comparison.Equals:
result = employees.Where(i => (getter(i) as string).Equals(parsedCondition.PropertyValue)).ToList();
break;
}
Assert.That(result.FirstOrDefault().Name, Does.Match(expectedResult));
}
Func<object, object> GetPropertGetter(string typeName, string propertyName)
{
Type t = Type.GetType(typeName);
PropertyInfo pi = t.GetProperty(propertyName);
MethodInfo getter = pi.GetGetMethod();
DynamicMethod dm = new DynamicMethod("GetValue", typeof(object), new Type[] { typeof(object) }, typeof(object), true);
ILGenerator lgen = dm.GetILGenerator();
lgen.Emit(OpCodes.Ldarg_0);
lgen.Emit(OpCodes.Call, getter);
if (getter.ReturnType.GetTypeInfo().IsValueType)
{
lgen.Emit(OpCodes.Box, getter.ReturnType);
}
lgen.Emit(OpCodes.Ret);
return dm.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>;
}
}
internal class Employee
{
private string name;
private string city;
private string state;
public string Name { get => name; set => name = value; }
public string City { get => city; set => city = value; }
public string State { get => state; set => state = value; }
}
}