Dapper批量查询而不是多次执行的单个查询

时间:2016-08-04 13:06:29

标签: mysql dapper

我正在尝试优化一些查询,我有这个疯狂的问题。基本的想法是我得到一堆房间,有一些相应的会议。我目前运行一个查询来获取所有房间,然后前往我需要召开会议的房间,在那里我对每个房间进行查询。这开辟了许多数据库连接(即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附加信息:此上下文中不允许使用可枚举的参数序列(数组,列表等)

1 个答案:

答案 0 :(得分:0)

首先,可以为多个查询重用单个连接,因此您可以使用相同的连接通过多个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”)。

你可以做一些有点hacky来生成一个单一的查询字符串,它将返回多个参数集的结果,如下所示 -

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,您可以将所有需要数据的密钥放入临时表中,然后执行一个连接到该表的键的查询(然后在获得数据后再次将其删除)。