C# 中的动态查询实现方案和技巧

622362ec219e782a793430c7a4e9a7b2.jpeg

概述:想象一下,制作一个图书馆应用程序,用户可以毫不费力地按书名、作者或流派查找书籍。传统的搜索方法将您淹没在代码中。但不要害怕!C# 中的动态查询可以节省一天的时间。✅在我们的故事中,为每个书籍属性制定单独的搜索方法成为一个令人头疼的问题。代码变成了嵌套的 if 或 switch case 语句的迷宫,是一场噩梦:public IEnumerableBook GetBooks(string propertyToFilter, string keyword) {     switch (propertyToFilter)     {         case Title:        

想象一下,制作一个图书馆应用程序,用户可以毫不费力地按书名、作者或流派查找书籍。传统的搜索方法将您淹没在代码中。但不要害怕!C# 中的动态查询可以节省一天的时间。

✅在我们的故事中,为每个书籍属性制定单独的搜索方法成为一个令人头疼的问题。代码变成了嵌套的 if 或 switch case 语句的迷宫,是一场噩梦:

public IEnumerable<Book> GetBooks(string propertyToFilter, string keyword)  
{  
    switch (propertyToFilter)  
    {  
        case "Title":  
            return await _books.Where(e => e.Title == keyword).ToListAsync();  
        case "Author":  
            return await _books.Where(e => e.Author == keyword).ToListAsync();  
        case "Genre":  
            return await _books.Where(e => e.Genre == keyword).ToListAsync();  
        // More cases for other properties  
    }  
}

随着库的扩展,这些代码会变成一团乱麻,在不断变化的需求的重压下崩溃。

✅输入动态查询,与泛型一起发挥其功能:

IQueryable<T> TextFilter<T>(IQueryable<T> source, string keyword)  
{  
    // The instructions and information in the rest of this article  
}

可以将此方法应用于任何实体,在所有字符串属性中搜索关键字。此外,您可以灵活地扩展该方法以支持其他数据类型。

摆脱僵化的束缚。无缝适应不断变化的数据结构。轻松浏览复杂的过滤器。

在软件开发的动态环境中,经常会出现查询的性质需要根据运行时条件进行调整的情况。本文探讨了 C# 中的各种技术,这些技术使用 IQueryable 和表达式树根据运行时状态执行不同的查询。我们将深入探讨一个真实场景,并通过实际示例演示如何实现动态查询。

了解 IQueryable 和表达式树

在深入研究真实世界的例子之前,让我们简要了解一下基本原理。C# 中的 IQueryable 由两个主要组件组成:

  1. 表达: 当前查询组件的语言和数据源无关的表示形式,描述为表达式树。

  2. 供应商: LINQ 提供程序的实例,负责将查询具体化为一个值或一组值。

在动态查询中,提供程序保持不变,而表达式树随每个查询而变化。

下面是用于根据运行时状态执行不同查询的各种技术:

1.在表达式树中使用运行时状态

2. 调用其他 LINQ 方法

3. 改变传递到 LINQ 方法中的表达式树

4. 使用工厂方法构造表达式树

5. 将方法调用节点添加到 IQueryable 的表达式树中

6. 利用动态 LINQ 库

实际场景:管理员工数据

假设您有一个包含员工数据的 HR 应用程序,每个应用程序都具有不同的属性,例如工资、部门和绩效评级。HR 管理员希望能够根据各种标准动态筛选和分析员工数据。挑战在于构建一个灵活的查询系统,可以处理不同的员工属性和动态用户输入。

var employees = new List<Employee>  
{  
    new(Firstname: "Alice", Lastname: "Williams", Salary: 60000, Department: "IT", PerformanceRating: 4),  
    new(Firstname: "Bob", Lastname: "Brown", Salary: 75000, Department: "HR", PerformanceRating: 3),  
    new(Firstname: "Charlie", Lastname: "Taylor", Salary: 50000, Department: "Finance", PerformanceRating: 5),  
};  
var employeeSource = employees.AsQueryable();  
  
record Employee(string Firstname, string Lastname, decimal Salary, string Department, int? PerformanceRating);

📌动态查询技术

现在,让我们探讨各种技术来处理基于用户输入的动态查询。

1.在表达式树中使用运行时状态

考虑管理员希望根据动态薪资范围筛选员工的场景:

decimal minSalary = 55000;  
decimal maxSalary = 75000;  
  
var employeeQuery = employeeSource  
    .Where(x => x.Salary >= minSalary && x.Salary <= maxSalary);  
  
Console.WriteLine(string.Join(",", employeeQuery.Select(x => $"{x.Firstname} {x.Lastname}")));  
// Output: Alice Williams,Bob Brown

**好处:**此方法提供了一种基于简单运行时条件调整查询的直接方法。

2. 调用其他 LINQ 方法

管理员可能还希望根据绩效评级对员工进行动态排序:

bool sortByRating = true;  
employeeQuery = employeeSource;  
  
if (sortByRating)  
    employeeQuery = employeeQuery.OrderBy(x => x.PerformanceRating);  
  
Console.WriteLine(string.Join(",", employeeQuery.Select(x => $"{x.Firstname} {x.Lastname}")));  
// Output: Bob Brown,Alice Williams,Charlie Taylor

**好处:**此方法允许有条件地应用各种 LINQ 方法,从而根据特定的运行时方案定制查询。

3. 改变传递到 LINQ 方法中的表达式树

使用 .NET 中的 LINQ 方法,可以根据运行时状态使用不同的表达式。

在此方案中,管理员希望根据部门和绩效评级动态筛选员工:

using System.Linq.Expressions;  
  
string targetDepartment = "IT";  
int? targetRating = 4;  
  
Expression<Func<Employee, bool>> expr = (targetDepartment, targetRating) switch  
{  
    ("" or null, null) => x => true,  
    (_, null) => x => x.Department.Equals(targetDepartment),  
    ("" or null, _) => x => x.PerformanceRating >= targetRating,  
    (_, _) => x => x.Department.Equals(targetDepartment) && x.PerformanceRating >= targetRating  
};  
  
employeeQuery = employeeSource.Where(expr);  
  
Console.WriteLine(string.Join(",", employeeQuery.Select(x => $"{x.Firstname} {x.Lastname}")));  
// Output: Alice Williams

**好处:**此技术提供了一种基于多个运行时条件动态构造表达式的灵活方法。

4. 使用工厂方法构造表达式树

到目前为止,我们一直在处理一些示例,在这些示例中,我们知道在编译时知道元素和查询的类型,特别是使用字符串和 IQueryable<string>。但是,您可能需要修改不同元素类型的查询或根据元素类型添加组件。可以使用 System.Linq.Expressions.Expression 中的方法从头开始生成表达式树,以便在运行时为特定元素类型自定义表达式。

在探讨我们的方案之前,让我们先介绍构造 Expression<TDelegate> 的过程。请按照下列步骤操作:

1) 导入必要的命名空间:

using System.Linq.Expressions;

2) 使用 Parameter factory 方法为 lambda 表达式中的每个参数创建 ParameterExpression 对象:

ParameterExpression parameter = Expression.Parameter(typeof(string), "x");

3) 使用您定义的 ParameterExpression(s) 和 Expression 提供的工厂方法构建 LambdaExpression 的正文。例如,您可以构造一个像 x.StartsWith(“a”) 这样的表达式,如下所示:

Expression body = Expression.Call(  
    parameter,  
    typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),  
    Expression.Constant("a")  
);

4) 使用合适的 Lambda 工厂方法重载,将参数和正文包含在具有编译时类型的 Expression<TDelegate> 中:

Expression<Func<string, bool>> lambda = Expression.Lambda<Func<string, bool>>(body, parameter);

5) 编译 lambda 表达式以获取委托:

Func<string, bool> function = lambda.Compile();

6) 这是完整的例子:

using System;  
using System.Linq.Expressions;  
  
class Program  
{  
    static void Main()  
    {  
        // Step 2: Define ParameterExpression objects for each parameter  
        ParameterExpression parameter = Expression.Parameter(typeof(string), "x");  
  
        // Step 3: Construct the body of your LambdaExpression  
        Expression body = Expression.Call(  
            parameter,  
            typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),  
            Expression.Constant("a")  
        );  
  
        // Step 4: Wrap parameters and body in an Expression\<TDelegate>  
        Expression<Func<string, bool>> lambda = Expression.Lambda<Func<string, bool>>(body, parameter);  
  
        // Step 5: Compile the lambda expression to get the delegate  
        Func<string, bool> function = lambda.Compile();  
  
        // Test the compiled function  
        bool result = function("apple");  
        Console.WriteLine(result); // Output: True  
    }  
}

我们的场景:

请考虑具有两种实体类型:

record Employee(string Firstname, string Lastname, decimal Salary, string Department, int? PerformanceRating);  
  
record Task(string Title, string Description);

您希望筛选和检索在其中一个字符串字段中具有特定文本的实体。

对于“任务”,可以在“标题”和“说明”属性中搜索:

string term1 = "Project abc";  
var tasksQry = new List<Task>()  
    .AsQueryable()  
    .Where(x => x.Description.Contains(term1) || x.Title.Contains(term1));

对于“员工”,在“名称”和“部门”属性中:

string term2 = "Alice";  
var employeesQry = new List<Employee>()  
    .AsQueryable()  
    .Where(x => x.Firstname.Contains(term2) || x.Lastname.Contains(term2));

以下函数允许将此筛选添加到任何现有查询中,而不考虑特定元素类型,而不是为 IQueryable<Task> 和 IQueryable<Employee> 创建单独的函数:

using System.Reflection;

string employeeSearchKeyword = "Alice";
string taskSearchKeyword = "Project abc";

IQueryable<T> TextFilter<T>(IQueryable<T> source, string term)
{
    if (string.IsNullOrEmpty(term))
        return source;

    // T stands for the type of element in the query, decided at compile time
    Type elementType = typeof(T);

    // Retrieve all string properties from this specific type
    PropertyInfo[] stringProperties =
        elementType.GetProperties()
            .Where(x => x.PropertyType == typeof(string))
            .ToArray();
    if (!stringProperties.Any())
        return source;

    // Identify the correct String.Contains overload
    MethodInfo containsMethod =
        typeof(string).GetMethod("Contains", new[] { typeof(string) })!;

    // Create a parameter for the expression tree, represented as 'x' in 'x => x.PropertyName.Contains("term")'
    // Define a ParameterExpression object
    ParameterExpression prm = Expression.Parameter(elementType);

    // Map each property to an expression tree node
    IEnumerable<Expression> expressions = stringProperties
        .Select<PropertyInfo, Expression>(prp =>
            // Construct an expression tree node for each property, like x.PropertyName.Contains("term")
            Expression.Call( // .Contains(...) 
                Expression.Property( // .PropertyName
                    prm, // x 
                    prp
                ),
                containsMethod,
                Expression.Constant(term) // "term" 
            )
        );

    // Combine all the resulting expression nodes using || (OR operator).
    Expression body = expressions
        .Aggregate(
            (prev, current) => Expression.Or(prev, current)
        );

    // Encapsulate the expression body in a compile-time-typed lambda expression
    Expression<Func<T, bool>> lambda =
        Expression.Lambda<Func<T, bool>>(body, prm);

    // Because the lambda is compile-time-typed (albeit with a generic parameter), we can use it with the Where method
    return source.Where(lambda);
}

employeeQuery = TextFilter(employeeSource, employeeSearchKeyword);
Console.WriteLine(string.Join(",", employeeQuery.Select(x => $"{x.Firstname} {x.Lastname}")));
// Output: Alice Williams

var taskQuery = TextFilter(taskSource, taskSearchKeyword);
Console.WriteLine(string.Join(",",
    taskQuery.Select(x => $"Task Detail:\n\tTitle: {x.Title}\n\tDescription: {x.Description}\n")));
// Output: Task Detail:
//              Title: Project abc Status Report
//              Description: give a quick summary of how the project has gone before the time period

**好处:**此方法支持动态创建复杂查询,以适应各种搜索条件。

5. 将方法调用节点添加到 IQueryable 的表达式树中

如果使用的是 IQueryable 而不是 IQueryable<T>,则无法轻松使用泛型 LINQ 方法。解决此问题的一种方法是构造内部表达式树,如前所述,然后使用反射调用正确的 LINQ 方法,同时为其提供表达式树。

另一种选择是通过将整个树放在 MethodCallExpression 中来复制 LINQ 方法的操作,该 MethodCallExpression 的作用类似于对 LINQ 方法的调用。

在管理员希望根据动态条件筛选员工并处理非类型化查询的情况下:

IQueryable TextFilter_Untyped(IQueryable source, string term)
{
    if (string.IsNullOrEmpty(term))
        return source;

    Type elementType = source.ElementType;

    // Retrieve all string properties from this specific type
    PropertyInfo[] stringProperties =
        elementType.GetProperties()
            .Where(x => x.PropertyType == typeof(string))
            .ToArray();
    if (!stringProperties.Any())
        return source;

    // Identify the correct String.Contains overload
    MethodInfo containsMethod =
        typeof(string).GetMethod("Contains", new[] { typeof(string) })!;

    // Create a parameter for the expression tree, represented as 'x' in 'x => x.PropertyName.Contains("term")'
    // Define a ParameterExpression object
    ParameterExpression prm = Expression.Parameter(elementType);

    // Map each property to an expression tree node
    IEnumerable<Expression> expressions = stringProperties
        .Select<PropertyInfo, Expression>(prp =>
            // Construct an expression tree node for each property, like x.PropertyName.Contains("term")
            Expression.Call( // .Contains(...) 
                Expression.Property( // .PropertyName
                    prm, // x 
                    prp
                ),
                containsMethod,
                Expression.Constant(term) // "term" 
            )
        );

    // Combine all the resulting expression nodes using || (OR operator).
    Expression body = expressions
        .Aggregate(
            (prev, current) => Expression.Or(prev, current)
        );
    if (body is null)
        return source;

    Expression filteredTree = Expression.Call(
        typeof(Queryable),
        "Where",
        new[] { elementType },
        source.Expression,
        Expression.Lambda(body, prm!)
    );

    return source.Provider.CreateQuery(filteredTree);
}

var eQuery = TextFilter_Untyped(employeeSource, "Charlie");

Console.WriteLine("5. Adding Method Call Nodes to IQueryable's Expression Tree:");
Console.WriteLine(string.Join(",", eQuery.Cast<Employee>().Select(x => $"{x.Firstname} {x.Lastname}")));
// Output: Charlie Taylor

在此方案中,当您没有编译时 T 通用占位符时,请使用不需要编译时类型信息的 Lambda 重载。这将导致创建 LambdaExpression 而不是 Expression<TDelegate>

**好处:**这种方法有助于将筛选逻辑动态应用于 IQueryable,而无需编译时类型信息。

6. 利用动态 LINQ 库

使用工厂方法制作表达式树是很困难的。将字符串放在一起更简单。动态 LINQ 库具有与常规 LINQ 方法匹配的 IQueryable 的额外方法,但它们使用具有特殊格式的字符串而不是表达式树。该库将字符串转换为正确的表达式树,并返回翻译后的 IQueryable

从 NuGet 获取动态 LINQ 库:

dotnet add package System.Linq.Dynamic.Core --version 1.3.10

导入必要的命名空间:

using System.Linq.Dynamic.Core;

在管理员希望使用字符串语法编写查询的更简单方法的情况下:

IQueryable TextFilter_Strings(IQueryable source, string term) {
    if (string.IsNullOrEmpty(term)) 
     return source; 

    var elementType = source.ElementType;

    // Retrieve all string properties from this specific type
    var stringProperties = 
        elementType.GetProperties()
            .Where(x => x.PropertyType == typeof(string))
            .ToArray();
    if (!stringProperties.Any()) { return source; }

    // Build the string expression
    string filterExpr = string.Join(" || ",
        stringProperties.Select(prp => $"{prp.Name}.Contains(@0)"));
    
    return source.Where(filterExpr, term);
}

var qry = TextFilter_Untyped(employeeSource, "HR");

Console.WriteLine("6. Leveraging the Dynamic LINQ Library:");
Console.WriteLine(string.Join(",", qry.Cast<Employee>().Select(x => $"{x.Firstname} {x.Lastname}")));
// Output: Bob Brown

好处: 动态 LINQ 库通过接受字符串表达式来简化动态查询的构造。

C# 中的动态查询提供了强大的工具,用于使查询适应不同的运行时条件。通过了解 IQueryable 和表达式树,开发人员可以创建灵活高效的系统,动态响应用户输入。员工管理系统的真实场景展示了这些技术在构建强大且适应性强的软件解决方案中的实际应用。根据方案的复杂程度选择适当的方法,并为应用程序提供动态查询功能。

如果你喜欢我的文章,请给我一个赞!谢谢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: sdn是什么以及它对编程开发有什么帮助? CSDN是一个致力于国IT业的在线社区,它提供了一些功能强大且有益于编程霖发人员的工具和服务,如技术问答、博客、源代码分享、开发者社交网络、职业发展等。此外,CSDN还有一个独特的在线教育平台,为开发人员提供了一系列实践性的培训课程和学习资源,以帮助他们不断提升专业技能和知识结构。 对于编程开发人员来说,CSDN是非常有用的资源库。它可以帮助开发人员寻找与解决各种有关编程方面的问题,以及了解最新的技术趋势和新闻,从而优化自己的技术能力和推动自己的职业发展。此外,CSDN还为开发人员提供了一个互动社区,可以与同行之间分享代码、问题和解决方案。这样,开发人员可以了解各种项目的最佳执行方法,学习如何采用新技术并获得有关编程语言技巧的建议。 总之,CSDN对于编程开发人员来说是非常有益的资源库。它可以帮助开发人员提高技术能力和知识结构,以推动个人和职业发展。 ### 回答2: SDN是软件定义网络(Software Defined Network)的简称,它是一种新型的网络架构。传统的网络架构是基于硬件设备的,随着网络的不断发展,网络设备的硬件性能越来越强大,但仍存在着一些问题,如网络管理繁琐、扩容困难、安全性差等。SDN通过将网络控制器和数据平面进行分离,通过可编程性和集式管理来解决这些问题。 CSDN是国最大的IT和开发者社区,是广大技术爱好者、IT从业人士进行技术交流和学习的平台。CSDN社区包括博客、论坛、培训、下载等多个板块,主要服务于软件开发、大数据、云计算、人工智能等领域。作为IT从业者必备的学习和交流平台,CSDN积累了海量的技术经验和资源,对于技术创新、企业运营等方面都有着深远的影响。CSDN不仅提供技术资源和服务,还组织了众多技术交流会议和峰会,为IT人士提供更多交流和学习的机会。 总而言之,SDN和CSDN虽然是两个不同的概念,但都在IT领域发挥了重要作用。SDN以其创新性的网络架构解决了网络设备的硬件性能问题,CSDN以其强大的IT技术资源和平台服务提高了IT人士的学习效率和创新能力。 ### 回答3: sdn是一个专业的技术社区,为广大的IT技术人员提供技术知识传播、互动交流、职业发展等服务的平台。CSDN的成立,不仅为IT技术人员搭建了一个互相交流、学习和分享的平台,更重要的是,CSDN真正把技术服务作为一项事业。不仅能够为技术人员提供学习和发展的机会,同时也能让技术人员从CSDN找到可以实现合作和商业机会。 CSDN提供了各种优质的技术文章,同时也有大量的iti技术人员在线答疑解惑。通过CSDN,技术人员可以结交志同道合的朋友,分享和交流有用的技术信息。在这里,技术人员可以扩展自己的技术能力,获得更多的机会和资源。 CSDN的影响力和知名度在IT技术领域非常高,拥有海量的粉丝和用户。同时,CSDN还发布了众多优秀的技术产品,满足了广大技术从业者的需求。 总的来说,CSDN是一家专注于技术和服务的公司,为IT技术人员提供了良好的技术知识交流和职业发展的平台。随着技术的发展和CSDN的不断创新,相信CSDN将会继续为广大IT技术人员提供更优质的服务和产品。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值