【问题标题】:Optional Filters on .NET Core Query to Postgres?.NET Core 查询到 Postgres 的可选过滤器?
【发布时间】:2020-01-03 03:20:30
【问题描述】:

我正在使用最新的.NET Core(在C#)和最新的PostgreSQL 构建一个API。

一个典型的查询会返回来自多个连接表的数据。例如,job 查询将返回包括薪水、地址、职位头衔等数据。

我需要一种方法在此查询中包含 WHERE 子句,基于一个或多个过滤器,这些过滤器可能存在也可能不存在(例如,按职位发布日期过滤,但前提是用户选择)。

一种方法是使用一堆 if 语句并手动修改查询(例如,如果传入工资要求,则连接 WHERE salary > 100000。这看起来很脏。

从这里的答案来看,Postgres 似乎有空合并 (How to query postgres on optional params?),例如

SELECT * from jobswhere 
  ($1 is null or salary = $1) and 
  ($2 is null or jobPostDate= $2);

使用 Dapper 或 Linq To SQL 是否有更简洁的方法?首选的方法是什么?

【问题讨论】:

  • 您所说的“如果...要求被传入”到底是什么意思?那看起来像什么?
  • 我的意思是请求来自前端的API。有时,用户会要求按薪水、职位发布日期、职位或其他一些此类要求过滤数据。如果这样的“要求被传入”,那么我需要在对数据库的查询中包含一个WHERE 子句。或WHERE AND,如果有多个。
  • 它们究竟是如何传递给 API 的?查询参数,您有一个模型,其中包含每个可能的过滤器值的可为空属性?
  • @MattU 正确。

标签: c# postgresql .net-core


【解决方案1】:

我建议SqlKata 编写您的查询。它包含一个方法When(),该方法必须先通过一个条件,然后才能在生成的 SQL 中包含 Where 子句。

它在后台使用 Dapper。

如果使用 linq,你可以这样做

IQueryable query = dbContext.Blogs;
if (isSo)
{
    query = query.Where(...);
}
query.ToArray();

【讨论】:

  • 我正在使用 LINQ,看看这个。
【解决方案2】:

我能想到几个选项。第一种是使用“谓词构建器”。该类可能如下所示(来自this answer):

PredicateBuilder 类

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }

    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
                                                      Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
        return Expression.Lambda<Func<T, bool>>
          (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
                                                       Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
        return Expression.Lambda<Func<T, bool>>
          (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
    }
}

然后去实现它:

var things = context.Set<Thing>().AsQueryable();
var filter = PredicateBuilder.True<Thing>();

// For each search filter
if (filter1 != null)
{
    filter = filter.And(t => t.SomeProperty == filter1);
}

things = things.Where(filter);

当然,这仍然需要每个过滤器的if 语句。你在问如何摆脱这种情况。不过有时我觉得它更干净一些。


Postgres 函数

另一种可能性是在 Postgres 中创建一个执行查询并获取过滤器参数的函数,该函数执行条件过滤,而不是在 C# 代码中完成。

CREATE FUNCTION thing_search (
    IN p_filter_1 VARCHAR(50),
    -- other filter params
) RETURNS TABLE (
    id INT,
    -- other columns you'll return
)
AS $$
BEGIN
    RETURN QUERY
        SELECT
            id,
            -- other columns
        FROM things
        WHERE
            (p_filter_1 IS NULL OR column_1 = p_filter_1)
            AND -- other filters
END;
$$
LANGUAGE plpgsql;

然后要使用它,请使用 EF Core 的DbSet&lt;T&gt;.FromSqlInterpolated

var things = context.Set<Thing>()
    .FromSqlInterpolated(
        $"select * from thing_search ( {filter1}, {other_filter_params} )"
    );

这至少消除了代码内部过滤的需要,但当然这意味着您的代码必须直接调用 Postgres 函数,而不是使用 DbSets。这并不意味着这是一个不好的方法。如果它更有意义并增加可读性/可维护性,请随意采用这条路线。

【讨论】:

  • 看看这个,谢谢马特。会回来接受答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-19
  • 1970-01-01
  • 2022-01-22
  • 2015-03-25
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
相关资源
最近更新 更多