我能想到几个选项。第一种是使用“谓词构建器”。该类可能如下所示(来自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<T>.FromSqlInterpolated:
var things = context.Set<Thing>()
.FromSqlInterpolated(
$"select * from thing_search ( {filter1}, {other_filter_params} )"
);
这至少消除了代码内部过滤的需要,但当然这意味着您的代码必须直接调用 Postgres 函数,而不是使用 DbSets。这并不意味着这是一个不好的方法。如果它更有意义并增加可读性/可维护性,请随意采用这条路线。