【问题标题】:How to filter LINQ query on dynamic columns and dynamic property in C#?如何在 C# 中过滤动态列和动态属性的 LINQ 查询?
【发布时间】:2020-07-12 06:19:26
【问题描述】:

我在获取 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"
                }
            };

所以,这只是一些示例记录,这些数据将来自数据库,并且会有很多记录。 现在,我想要实现的是,如果根据列表发布了与城市匹配的任何视频,我必须通知所有人。所以,我可以接收 NotificationValue 作为 City:Florida:startsWith , City:Florida: Equals , City:Florida:Contains 等,也可能有州标准。 那么,我如何在列表中动态过滤记录,例如输入是否以 Starts 开头,我应该使用 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 查询开始、结束、等于等,并且它应该与动态列灵活,就像我必须为州、国家、邮编等应用相同的逻辑'

如果可能,请发布一些示例代码

【问题讨论】:

标签: c# .net asp.net-mvc linq dynamic-linq


【解决方案1】:

获取属性值的简单快速的解决方案是使用 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; }
    }
}

【讨论】:

  • 它对我有用,它正在寻找它的好处。非常感谢你
【解决方案2】:

可能有很多解决方案,但通常像构建一个可扩展的解决方案,并在我自己的项目中得到证明。

//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();
    }
}

【讨论】:

    【解决方案3】:

    希望对你有帮助:

        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>();
            }
        }
    

    【讨论】:

    • 您可以返回Enumerable.Empty&lt;string&gt;(),而不是返回一个新列表。另外,我建议 employees 作为函数参数,因为该变量的范围在问题中不清楚。
    猜你喜欢
    • 1970-01-01
    • 2014-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-14
    相关资源
    最近更新 更多