【问题标题】:Dapper batch queries instead of a single query executed many timesDapper 批量查询而不是多次执行的单个查询
【发布时间】:2016-08-04 13:06:28
【问题描述】:

我正在尝试优化一些查询,我有一个疯狂的查询。基本想法是我得到一堆房间,里面有一些相应的会议。我目前运行查询以获取所有房间,然后为每个房间我需要获取会议,在那里我对每个房间进行查询。这为大量数据库连接打开了大门(即 1000 个房间,每个房间都必须打开一个连接来拉开会议),我想改为批量进行。我正在使用 dapper 将我的查询映射到模型,并且我正在尝试使用描述的列表参数here

SELECT 
      mm.id,
      mm.organizer_name as Organizer,
      mm.subject as Subject,
      mm.start_time as StartTime,
      mm.end_time as EndTime,
      (mm.deleted_at IS NOT NULL) as WasCancelled,
      (am.interactive = 0 AND am.cancelled_at IS NOT NULL) as WasNoShow,
      c.name as name
FROM master_meeting mm
LEFT JOIN master_meeting__exchange mme ON mme.id=mm.id
LEFT JOIN master_meeting__forwarded_exchange mmfe ON mmfe.id=mm.id
LEFT JOIN meeting_instance__exchange mie ON mie.meeting_id=mm.id
LEFT JOIN meeting_instance__forwarded_exchange mife ON mife.meeting_id=mm.id
LEFT JOIN appointment_meta__exchange ame ON mie.item_id=ame.item_id
LEFT JOIN appointment_meta__exchange ame2 ON mife.item_id=ame2.item_id
LEFT JOIN appointment_meta am ON am.id=ame.id
LEFT JOIN appointment_meta am2 ON am2.id=ame2.id                
LEFT JOIN calendar c on mie.calendar_id=c.id
WHERE mie.calendar_id = @Id OR mife.calendar_id=@Id 
AND mm.start_time BETWEEN @StartTime AND @EndTime

在不详细介绍疯狂的长连接序列的情况下,我目前必须执行此查询,很多。它最初被写成:

List<Result> resultSet = new List<Result>();

foreach(int id in idList){
         resultSet.AddRange(
             _queryHandler.Handle(
                 new MeetingQuery(id, "FixedStartTime", "FixedEndTime")
             )
         );            
}

这反过来又调用了很多次并运行查询:

_connection.Query<Meeting>(sql, 
    new { 
        Id = query.id, 
        StartTime = query.StartTime, 
        EndTime = query.EndTime 
    }
);

这显然需要相当多的数据库连接,我想通过让 dapper 执行多个查询来避免这种情况,但是如果我尝试将参数添加为如下所示的列表,则会出现以下错误:

class Parameters {
    int Id;
    string StartTime;
    string EndTime;
}
List<Parameters> parameters = new List<Parameters>();
foreach(int id in idList)
     parameters.Add(new Parameters(id, "SameStartTime", "SameEndTime");

然后我会像这样使用参数列表:

_connection.Query<Meeting>(sql,parameters);

我得到的错误是:

dapper 附加信息:在此上下文中不允许使用可枚举的参数序列(数组、列表等)

【问题讨论】:

    标签: mysql dapper


    【解决方案1】:

    首先,可以为多个查询重用单个连接,因此您可以使用同一连接通过多个 Dapper“查询”调用检索所有数据。

    类似于以下内容(这与您显示的查询不完全相同,因为我是在自己的计算机上使用本地数据库进行测试的;应该很容易看到如何更改它以使用您的查询,不过)-

    private static IEnumerable<Record> UnbatchedRetrieval(IEnumerable<Parameters> parameters)
    {
        var allResults = new List<Record>();
        using (var conn = GetConnection())
        {
            foreach (var parameter in parameters)
            {
                allResults.AddRange(
                    conn.Query<Record>(
                        "SELECT Id, Title FROM Posts WHERE Id = @id",
                        parameter
                    )
                );
            }
        }
        return allResults;
    }
    
    public class Parameters
    {
        public int Id { get; set; }
    }
    

    但是,如果确实是您想通过批处理来减少查询的数量,那么 Dapper 中没有任何东西可以很容易地做到这一点,因为每个参数都必须唯一命名,但情况并非如此如果您提供一个类型的多个实例作为“参数”值(例如,将有“n”个 Id 值都称为“Id”)。

    您可以做一些小技巧来生成一个查询字符串,该字符串将返回来自多个参数集的结果,例如以下 -

    private static IEnumerable<Record> BatchedRetrieval(IEnumerable<Parameters> parameters)
    {
        using (var conn = GetConnection)
        {
            var select = "SELECT Id, Title FROM Posts";
            var where = "Id = {0}";
    
            var sqlParameters = new DynamicParameters();
            var combinedWheres =
                "(" +
                string.Join(
                    ") OR (",
                    parameters.Select((parameter, index) =>
                    {
                        sqlParameters.Add("id" + index, parameter.Id);
                        return string.Format(where, "@id" + index);
                    })
                ) +
                ")";
    
            return conn.Query<Record>(
                select + " WHERE " + combinedWheres,
                sqlParameters
            );
        }
    }
    
    public class Parameters
    {
        public int Id { get; set; }
    }
    

    .. 但这感觉有点脏。但是,如果您绝对确定一个接一个地执行这些查询是一个性能瓶颈,那么它可能是一个探索的选项。

    要考虑的另一件事 - 当您需要 1000 个不同 id 的数据时,1000 个查询中的每个查询的开始时间和结束时间是否总是相同的?如果是这样,那么您可以将查询更改为以下内容:

    private static IEnumerable<Record> EfficientBatchedRetrieval(
        IEnumerable<int> ids,
        DateTime startTime,
        DateTime endTime)
    {
        using (var conn = GetConnection())
        {
            return conn.Query<Record>(
                @"SELECT 
                        mm.id,
                        mm.organizer_name as Organizer,
                        mm.subject as Subject,
                        mm.start_time as StartTime,
                        mm.end_time as EndTime,
                        (mm.deleted_at IS NOT NULL) as WasCancelled,
                        (am.interactive = 0 AND am.cancelled_at IS NOT NULL) as WasNoShow,
                        c.name as name
                FROM master_meeting mm
                LEFT JOIN master_meeting__exchange mme ON mme.id=mm.id
                LEFT JOIN master_meeting__forwarded_exchange mmfe ON mmfe.id=mm.id
                LEFT JOIN meeting_instance__exchange mie ON mie.meeting_id=mm.id
                LEFT JOIN meeting_instance__forwarded_exchange mife ON mife.meeting_id=mm.id
                LEFT JOIN appointment_meta__exchange ame ON mie.item_id=ame.item_id
                LEFT JOIN appointment_meta__exchange ame2 ON mife.item_id=ame2.item_id
                LEFT JOIN appointment_meta am ON am.id=ame.id
                LEFT JOIN appointment_meta am2 ON am2.id=ame2.id
                LEFT JOIN calendar c on mie.calendar_id=c.id
                WHERE mie.calendar_id IN @Ids OR mife.calendar_id IN @Ids
                AND mm.start_time BETWEEN @StartTime AND @EndTime",
                new { Ids = ids, StartTime = startTime, EndTime = endTime }
            );
        }
    }
    

    但是,如果您使用大量 id 调用它,可能会出现问题,因为 Dapper 转换 IN 子句的方式 - 如 https://stackoverflow.com/a/19938414/3813189 中所述(有人警告不要将它与大量值)。

    如果该方法失败,则可以执行类似于此处建议的临时表批量加载的操作:https://stackoverflow.com/a/9947259/3813189,您可以在其中将所有需要数据的键放入临时表中,然后执行查询连接到该表的键(然后在您获得数据后再次将其删除)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多