sql-server - 如何使用 List 参数编译 LINQ 查询?

标签 sql-server linq caching compilation .net-4.0

我的 LINQ 查询之一有问题。查询如下所示:

        Dim elements = (From filterSum In dataContext.GetTable(Of TblFilterSum)
                        Join filterProdInSum In dataContext.GetTable(Of TblFilterProdsInSum)
                    On filterSum.SumID Equals filterProdInSum.SumID
                        Join filterProd In dataContext.GetTable(Of TblFilterProd)
                    On filterProdInSum.ProdID Equals filterProd.ProdID
                        Join filterElementInProd In dataContext.GetTable(Of TblFilterElementsInProd)
                    On filterProd.ProdID Equals filterElementInProd.ProdID
                        Join filterElement In dataContext.GetTable(Of TblFilterElement)
                    On filterElementInProd.ElementID Equals filterElement.ElementID
                        Where sumIDs.Contains(filterSum.SumID)).Select(Function(r) New With {.SumID = r.filterSum.SumID, .ProdID = r.filterProd.ProdID, .filterElement = r.filterElement, .IsNotSum = r.filterSum.IsNot, .IsNotProd = r.filterProd.IsNot}).ToList

此查询从表定义的 5 维问题空间加载记录:

  • tblFilterSum
  • tblFilterProdsInSum
  • tblFilterProd
  • tblFilterElementsInProd
  • tblFilterElement

我在 Where 子句中使用的过滤器是 tblFilterSum.SumID 位于名为 sumIDs 的 List(Of Integer)。 Linq 查询在逻辑上是无可挑剔的,并且正好有我需要的结果。然而,执行它需要很长时间,平均执行时间为 20 秒。这是 LINQ 查询生成的 SQL:

-- Region Parameters
DECLARE @p0 Int = 12168
DECLARE @p1 Int = 12157
DECLARE @p2 Int = 11948
DECLARE @p3 Int = 11951
DECLARE @p4 Int = 11952
DECLARE @p5 Int = 11950
DECLARE @p6 Int = 11961
DECLARE @p7 Int = 12153
DECLARE @p8 Int = 12154
DECLARE @p9 Int = 12149
DECLARE @p10 Int = 12158
DECLARE @p11 Int = 11954
DECLARE @p12 Int = 11955
DECLARE @p13 Int = 11956
DECLARE @p14 Int = 11957
DECLARE @p15 Int = 11958
DECLARE @p16 Int = 11959
DECLARE @p17 Int = 12159
DECLARE @p18 Int = 12164
DECLARE @p19 Int = 12150
DECLARE @p20 Int = 12151
DECLARE @p21 Int = 12152
DECLARE @p22 Int = 12156
DECLARE @p23 Int = 12161
DECLARE @p24 Int = 12167
DECLARE @p25 Int = 11962
DECLARE @p26 Int = 12155
DECLARE @p27 Int = 12183
DECLARE @p28 Int = 12182
DECLARE @p29 Int = 12165
DECLARE @p30 Int = 12166
DECLARE @p31 Int = 11953
DECLARE @p32 Int = 12163
DECLARE @p33 Int = 12181
DECLARE @p34 Int = 12180
DECLARE @p35 Int = 12160
DECLARE @p36 Int = 12162
-- EndRegion
SELECT [t0].[SumID], [t2].[ProdID], [t4].[ElementID], [t4].[Field], [t4].[Operator], [t4].[Result], [t4].[IsCustom], [t4].[IsNot], [t4].[DisplayOrder], [t4].[FilteredColumnID], [t4].[ColumnMappingID], [t4].[ResultPointerToAssetPath], [t4].[ResultCustomColumnMappingID], [t0].[IsNot] AS [IsNotSum], [t2].[IsNot] AS [IsNotProd]
FROM [dbo].[tblFilterSum] AS [t0]
INNER JOIN [dbo].[tblFilterProdsInSum] AS [t1] ON [t0].[SumID] = [t1].[SumID]
INNER JOIN [dbo].[tblFilterProd] AS [t2] ON [t1].[ProdID] = [t2].[ProdID]
INNER JOIN [dbo].[tblFilterElementsInProd] AS [t3] ON [t2].[ProdID] = [t3].[ProdID]
INNER JOIN [dbo].[tblFilterElement] AS [t4] ON [t3].[ElementID] = [t4].[ElementID]
WHERE [t0].[SumID] IN (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36)
-- Context: SqlProvider(Sql2008) Model: MappedMetaModel Build: 4.6.1055.0
GO

如果我直接运行它,它会立即运行,因此问题是 .NET Framework 4.0 下的 LINQ 每次执行时都会编译查询,并且没有任何缓存。虽然有可能缓存结果:System.Data.Linq.CompiledQuery.Compile是一种有用的方法,可以用它来编译 LINQ 查询。它返回的 Func 可以使用不同的参数一次又一次地重用,围绕 .NET Framework 4.0 中使用的始终编译策略工作。这个想法是为每个可能的查询创建一个唯一的签名,并使用共享字典,我们可以将每种查询编译一次,存储到缓存中,然后将其与其他属性一起重用。这听起来不错,但看看可能的用法:

Public Shared Function Compile(Of TArg0 As DataContext, TResult)(query As Expression(Of Func(Of TArg0, TResult))) As Func(Of TArg0, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TResult))) As Func(Of TArg0, TArg1, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TResult))) As Func(Of TArg0, TArg1, TArg2, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TResult)

Public Shared Function Compile(Of TArg0 As DataContext, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TArg15, TResult)(query As Expression(Of Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TArg15, TResult))) As Func(Of TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TArg15, TResult)

我很快意识到我有两个问题。这一次,Compile 方法具有不同可能参数值的重载,以及它返回的 Func 的相同数量的参数。这是一个不太严重的问题,因为我可以为我拥有的每个元素数量缓存一个编译查询。它会浪费一些内存,但不会浪费那么多。如果它太多了,我会用 Redis 存储它,但这是一个不同的问题。真正的问题是参数的数量不能超过 16 个,而我的 List 可以很容易地包含更多元素。对于这个问题,解决方案是将 sumIDs 转换为 String,每个数字之间有一个分隔符,字符串将以该分隔符开始和结束,如下所示:

,5,2,7,34,764,346,1,

并检查此 String 是否包含转换为 String 的给定 tblFilterSum.SumID,如下所示:

"," & tblFilterSum.SumID & ","

但是虽然这可行,但它不能使用索引并且它会比较字符串,这会非常慢。由于我遇到了这些问题并且任务很紧迫,我用 SQL 实现了它,但想知道我是否可以编译一个 LINQ 查询并确保任意多的参数可以作为 List 传递,例如。因此我的问题:

如何使用 List 作为参数编译 LINQ 查询?

编辑:

如果我知道如何联系 LINQ 的创建者,那么我相信我自己就能找到答案。我在 google 上搜索了 LINQ,但不幸的是我没有找到联系信息。

最佳答案

使用 Contains() 的查询 cannot be automatically cached .所以解决方案必须是避免 Contains 方法。为此,我们必须通过 Linq 表达式 API 创建等效表达式 (f => f.SumID == x || f.SumId == y ...)。

以下解决方案使用 ExpressionCombiner来自 netfx。这是C#,转换成VB应该很简单。

public static IQueryable<T> WherePropertyIn<T, TProp>(this IQueryable<T> src, Expression<Func<T, TProp>> property, IEnumerable<TProp> values) {
    var valuesList = values.ToList();
    // If no values passed, then nothing matches
    if (!valuesList.Any()) {
        return src.Where(_ => false);
    }
    // This builds the 'f => f.Prop == x || f.Prop == y ...' expression
    Expression<Func<T, bool>> expr = valuesList
                                        .Select(val => property.EqualTo(val)) // Here we have a list of 'f => f.Prop == x' style expressions
                                        .Aggregate(ExpressionCombiner.Or); // And combine them with ||
    return src.Where(expr);
}

// Creates an expression 'f => f.Prop == val' out of expression 'f => f.Prop' and value 'val'
private static Expression<Func<T, bool>> EqualTo<T, TProperty>(this Expression<Func<T, TProperty>> leftHand, TProperty val) {
    // If we don't wrap in property access, LINQ to Entities uses the value directly instead of via a sql parameter, thus breaking caching.
    Expression rightHand = Expression.Constant(val, typeof(TProperty)).WrapInPropertyAccess();
    Expression comparison = Expression.Equal(leftHand.Body, rightHand);
    return Expression.Lambda<Func<T, bool>>(comparison, leftHand.Parameters);
}

/// <summary>
/// Returns an expression around the ConstantExpression, enabling LINQ to Entities to generate parameterized queries
/// </summary>
/// <param name="constant"></param>
/// <returns></returns>
private static UnaryExpression WrapInPropertyAccess(this ConstantExpression constant) {
    Tuple<object> container = new Tuple<object>(constant.Value);
    Expression containerExpression = Expression.Constant(container, typeof(Tuple<object>));
    MemberExpression propAccess = Expression.Property(containerExpression, "Item1");
    UnaryExpression result = Expression.Convert(propAccess, constant.Type); // Cast back the object to the value type
    return result;
}

您的查询现在可以使用此代码:

Dim filterSumSubQuery = dataContext.GetTable(Of TblFilterSum).WherePropertyIn(Function(t) r.SumID,  sumIDs)
' Subquery now contains only the TbFilterSum tuples that have the right SumIDs

关于sql-server - 如何使用 List 参数编译 LINQ 查询?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40046956/

相关文章:

缓存与 Tempview

asp.net - 如何缓存将图像返回到asp.net mvc中的 View 的操作方法的输出?

sql-server - 如何以编程方式确定存储过程是否从另一个数据库中进行选择?

sql - 批量插入时忽略外键约束

sql-server - SQL Server : Programmatically Execute Maintenance Plan

c# - 我可以使用 LINQ GroupBy 更干净地执行此操作吗?

c# - 使用 where 子句时排除字符串的一部分

php - 如何缓存由php生成的图像

sql - 以逗号分隔的列的行列表,在其他列上分组

linq - 在 .NET 3.5 中的枚举列表上使用 List.Find 或 LINQ