2016-10-14 8 views
3

У меня проблема с одним из моих запросов LINQ. Запрос выглядит следующим образом:Как скомпилировать запрос LINQ с параметром List?

 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 находится внутри List(Of Integer) называется sumIDs. Запрос Linq логически безупречен и имеет только тот результат, который мне нужен. Тем не менее, выполнение этого требует в течение вечности, 20s время выполнения в среднем. Это SQL генерирует запрос LINQ:

-- 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 

Это работает мгновенно, если я запускаю его непосредственно, поэтому проблема заключается в том, что LINQ под .NET Framework 4.0 компилирует в запросе каждый раз, когда она исполняется, и нет кэша бы то ни было. Существует возможность кэшировать результаты: 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, но это другая проблема. Реальная проблема заключается в том, что количество параметров не может быть больше шестнадцати, а мой List может содержать легко большее количество элементов. С этой проблемой, решение будет преобразовывать sumIDs в String, с разделителем между каждым номером и строкой будет начинаться и заканчиваться с этим сепаратором, например:

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

и проверить, содержит ли этот String учитывая tblFilterSum.SumID превращается в String так:

"," & tblFilterSum.SumID & "," 

, но в то время как это будет работать, он не сможет использовать индексы, и было бы сравнивать строки, которые были бы очень медленно. Поскольку у меня есть эти проблемы, и задача была срочной, я реализовал это с помощью SQL, но задаюсь вопросом, могу ли я скомпилировать запрос LINQ и убедиться, что произвольно многие параметры могут быть переданы, например, как List. Отсюда мой вопрос:

Как скомпилировать запрос LINQ с параметром List?

EDIT:

Если бы я знал, как связаться с создателями LINQ, то я полагаю, что я мог бы найти ответ сам. Я искал google для LINQ, но, к сожалению, я не нашел контактную информацию.

ответ

1

Запросов для: Contains()cannot be automatically cached. Таким образом, решение должно состоять в том, чтобы избежать метода Contains. Для этого нам понадобится создать эквивалентное выражение (f => f.SumID == x || f.SumId == y ...) через Linq Expressions API.

В следующем решении используется 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 
+0

Фелипе, спасибо за ваш ответ, но вы, кажется, не понял вопроса. Поскольку (к сожалению) я использую .NET Framework 4.0, поэтому я не могу использовать Entity Framework 5+, у которого .NET 4.5+ как зависимость. Поскольку автоматическое кэширование является особенностью Entity Framework 5+, которое не применимо в моем случае, автоматическое кэширование вообще невозможно в среде, о которой мы говорим. Поскольку автоматическое кэширование в моей ситуации невозможно, я намереваюсь выполнять ручное кэширование, и мне нужно работать со списком. Вот о чем идет речь. –

+0

Поскольку автоматическое кэширование в моем случае невозможно (как вы можете видеть из тегов, которые содержат .NET 4), я намереваюсь выполнять ручное кеширование (как вы можете видеть из вопроса), и проблема в том, что CompiledQuery.Compile не поддержка параметров списка и имеет конечное число возможных параметров, что делает параметр стиля отражения бесполезным. Вы отвечаете, предлагая переключить Содержит что-то пользовательское, чтобы разрешить автоматическую компиляцию, но это недостижимая функция. –

+0

О, я не заметил, что вы не можете использовать EF 5+. Я предлагаю добавить в этот вопрос фразу. Я не знаю, как решить вашу проблему :( – felipe