对 IN 语句使用具有大量值的 OracleDataReader

Use OracleDataReader with a large amount of values for an IN statement

目前,我正在尝试实现数据 reader 以执行特别大的查询。当前的实现使用 Entity Framework,但由于查询的性质,它非常慢(大约 4 分半钟)。

这是当前使用 EF 的实现:

public List<SomeDataModel> GetSomeData(List<string> SomeValues, string setId)
{
    var ret = new List<SomeDataModel>();

    using(var context = new SomeDBContext())
    {
        var data = context.SomeEntity.Where(x => x.SetId == setId && SomeValues.Contains(x.SomeValue));
        data.ForEach(x => ret.Add(mapper.Map<SomeDataModel>(x))); // mapper is an instance of AutoMapper via dependency injection
    }
    return ret; 
}

理想情况下,我想生成一个更基本的查询字符串并通过 OracleDataReader 提取数据。问题是:在 Oracle 的 IN 语句中,您只能有 1000 个值。 SomeValues 参数可以是 5,000 到 25,000 之间的任何值,所以我想在后端 EF 会自行生成多个查询,但正如我所说,它非常慢。

这就是我试图采取的方向:

public List<SomeDataModel> GetSomeData(List<string> SomeValues, string setId)
{
    var ret = new List<SomeDataModel>();
    const int MAX_CHUNK_SIZE = 1000;
    var totalPages = (int)Math.Ceiling((decimal)SomeValues.Count / MAX_CHUNK_SIZE);

    for(var i = 0; i < totalPages; i++)        
    {
            var chunkItems = SomeValues.Skip(i * MAX_CHUNK_SIZE).Take(MAX_CHUNK_SIZE).ToList();
            pageList.Add(chunkItems);
    }

        using (var context = new CASTDbContext())
        {
            var connStr = context.Database.Connection.ConnectionString;
            using (var conn = new OracleConnection(connStr))
            {
                foreach(var page in pageList)
                {
                    var queryStr = string.Format("SELECT * FROM SomeTable WHERE SomeColumn IN ({0})", "(" + string.Join(",", page.ToArray())  + ")");
                    var cmd = new OracleCommand(queryStr, conn);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while(reader.Read())
                        {
                            var newItem = new SomeDataModel();
                            newItem.Something = reader["Something"].ToString();
                            ret.Add(newItem);
                        }
                    }
                }                                      
            }
        }
    return ret; 
}

我想期望的结果是为 reader 高效地生成多个查询,或者构造一个可以有效处理这种情况的查询。我在第二个示例中所拥有的是目前的占位符代码。

起初,

如果我们使用一对列,Oracle 可以在 IN 列表中获取超过 1000 个值。

所以,下面会抛出错误:

SELECT * FROM TABLE 
WHERE COL1 IN (VAL1, VAL2,... VAL1000, VAL1001);

但以下内容会起作用:

SELECT * FROM TABLE 
WHERE (COL,1) IN ( (VAL1,1), (VAL2,1),... (VAL1000,1), 
(VAL1001,1).....(VAL9999,1) ); 
-- we have used pair of value and 1 to be compared with col and 1

希望,这会给你解决问题的方向。是的,我不确定性能,所以请最后检查一下。

干杯!!

可能有帮助的东西:

根据您的 SomeEntity 和 SomeDataModel 的外观,您可以从数据库中加载不需要的值,甚至可以跳过延迟加载,因为在填充数据模型时它引用了一个并不急切的相关实体加载。对于 Automapper,我建议在 Linq 查询中利用 ProjectTo 方法。这将确保数据命中仅发生一次,并且 returns 只是数据模型所需的字段。

var results = context.SomeEntity
    .Where(x => x.SetId == setId && SomeValues.Contains(x.SomeValue))
    .ProjectTo<SomeDataModel>()
    .ToList();

或使用 Automapper 9:

var results = mapper.ProjectTo<SomeDataModel>(context.SomeEntity
    .Where(x => x.SetId == setId && SomeValues.Contains(x.SomeValue)))
    .ToList();

像这样查询大量任意值永远不会有效率。您还应该检查数据是否已编入索引。一旦您捕获 SQL 正在 运行 检索您的数据,将其通过合适的分析器(我对 Oracle 工具不是很熟悉)来查看是否有索引建议。