У меня проблема с одним из моих запросов 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, но, к сожалению, я не нашел контактную информацию.
Фелипе, спасибо за ваш ответ, но вы, кажется, не понял вопроса. Поскольку (к сожалению) я использую .NET Framework 4.0, поэтому я не могу использовать Entity Framework 5+, у которого .NET 4.5+ как зависимость. Поскольку автоматическое кэширование является особенностью Entity Framework 5+, которое не применимо в моем случае, автоматическое кэширование вообще невозможно в среде, о которой мы говорим. Поскольку автоматическое кэширование в моей ситуации невозможно, я намереваюсь выполнять ручное кэширование, и мне нужно работать со списком. Вот о чем идет речь. –
Поскольку автоматическое кэширование в моем случае невозможно (как вы можете видеть из тегов, которые содержат .NET 4), я намереваюсь выполнять ручное кеширование (как вы можете видеть из вопроса), и проблема в том, что CompiledQuery.Compile не поддержка параметров списка и имеет конечное число возможных параметров, что делает параметр стиля отражения бесполезным. Вы отвечаете, предлагая переключить Содержит что-то пользовательское, чтобы разрешить автоматическую компиляцию, но это недостижимая функция. –
О, я не заметил, что вы не можете использовать EF 5+. Я предлагаю добавить в этот вопрос фразу. Я не знаю, как решить вашу проблему :( – felipe