使用 postgresql 处理 EF core 中原始查询参数列表的正确方法

mic*_*arz 5 entity-framework asp.net-core

处理我需要包含在 WHERE IN 查询中的项目列表的正确方法是什么?在我的用例中,我需要使用日期时间列表。我当前的解决方案非常糟糕,因为我在查询中嵌入了数据,并且我更喜欢使用查询参数。

            var n = String.Join(",", dates.Select(item => $"'{item.ToString()}'::timestamp"));
            var q = $@"
SELECT c.date AS date, a.id AS user_id, a.user_name AS user_name, a.email AS user_email, COALESCE(sum(te.amount), 0) AS summary
FROM asp_net_users a
         CROSS JOIN (SELECT *
                     FROM calendar
                     WHERE date IN({n})
) c
         LEFT JOIN time_entries te on a.id = te.user_id AND c.date = te.date
WHERE a.department_guid = '95b7538d-3830-48d7-ba06-ad7c51a57191'
GROUP BY c.date, a.id
HAVING COALESCE(sum(te.amount), 0) > 480
ORDER BY c.date
";
Run Code Online (Sandbox Code Playgroud)

当我尝试在查询参数中使用日期作为数组时,我收到以下错误:

operator does not exist: timestamp without time zone = timestamp[]
Run Code Online (Sandbox Code Playgroud)

ilk*_*ran 3

如果很少使用数组输入,这可能是过度设计,但我认为值得尝试。您可以尝试自定义数据库参数和扩展来解决您的问题

首先要事。您需要自定义 DbParameter 类型

public class SqlListParameter : DbParameter
{
    private string _internalName;
    protected string InternalName
    {
        get => _internalName;
        set => _internalName = NormalizeParameterName(value);
    }

    public override DbType DbType { get; set; }
    public override ParameterDirection Direction { get; set; }
    public override bool IsNullable { get; set; }

    public override string SourceColumn { get; set; }
    public override object Value { get; set; }
    public override bool SourceColumnNullMapping { get; set; }
    public override int Size { get; set; }

    public override string ParameterName
    {
        get => InternalName;
        set => InternalName = value;
    }

    public override void ResetDbType()
    {

    }

    public SqlListParameter(string parameterName)
    {
        InternalName = parameterName;
    }

    internal static string NormalizeParameterName(string parameterName)
    {
        return string.IsNullOrWhiteSpace(parameterName) || parameterName.First() == '@'
            ? parameterName
            : "@" + parameterName;
    }
}
Run Code Online (Sandbox Code Playgroud)

并扩展Database.SqlQuery以能够使用我们的自定义 DbParameter

public static class SqlQueryExtensions
{
    public static DbRawSqlQuery<TElement> ExtendedSqlQuery<TElement>(this Database db, string sql, params object[] parameters)
    {
        var listParameters = parameters.Where(n => n.GetType() == typeof(SqlListParameter)).ToArray();

        sql = listParameters.Aggregate(sql, (dbSql, parameter) => ApplyList(dbSql, parameter as SqlListParameter));

        parameters = parameters.Where(n => n.GetType() != typeof(SqlListParameter)).ToArray();

        return db.SqlQuery<TElement>(sql, parameters);
    }

    private static string ApplyList(string sql, SqlListParameter parameter)
    {
        var list = parameter.Value as IEnumerable<int>;

        if (list == null)
            throw new SqlListException("SqlListParameter value should has could be casted to IEnumerable<int>");

        var joinedListItems = string.Join(",", list);

        return sql.Replace(parameter.ParameterName, joinedListItems);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后你可以像下面这样使用它

using (var context = new TestDbContext())
{
       var parameters = new object[]
    {
        new SqlListParameter("@identsList") { Value = new [] { 1, 2 }}, 
        new SqlParameter("@nameParam", SqlDbType.VarChar) { Value = "Test1"}, 
    };

    var isFoundEntry = context.Database.ExtendedSqlQuery<TestEntity>("select * from TestEntities where Id in (@identsList) and name=@nameParam", parameters).Any();
}
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你。