После большого количества копаний кажется, что LINQ to SQL не может быть убежден, чтобы генерировать CONVERT(BIT, 0)
в запросе.Однако можно принудительно использовать параметр вместо литерала в предложении WHERE, а именно, сначала скомпилировав запрос, как показано ниже:
private static string QueryCompiled(Context context)
{
var compiled = CompiledQuery.Compile(
(Context c, bool isDeleted) =>
(from article in c.OutboundArticles
where article.IsDeleted == isDeleted
select article.Text).Single());
return compiled(context, false);
}
Когда мы запускаем этот запрос, генерируется следующий SQL:
SELECT [t0].[Text]
FROM [OutboundArticle] AS [t0]
WHERE [t0].[IsDeleted] = @p0
-- @p0: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 4.0.30319.1
Обратите внимание на комментарий, @ p0, похоже, набран соответствующим образом, чтобы SQL Server Compact фактически использовал индекс.Я подтвердил это с помощью программы ниже.Программа сначала заполняет новую БД 1000000 строками, а затем запрашивает ее либо скомпилированным, либо обычным запросом.На моей машине время очевидно (среднее из 3 запусков, сначала отбрасывается):
Обычный запрос к БД с индексом: ~ 670ms
Скомпилированный запрос к БД с индексом: ~30 мс
В обоих случаях запрос выполняется только один раз, поэтому скомпилированный запрос не имеет никаких преимуществ от фактической компиляции.Еще одно доказательство того, что скомпилированный запрос фактически использует индекс, в то время как обычный не приходит, когда мы вручную удаляем индекс в БД, а затем снова запускаем те же самые запросы (в среднем из 3 запусков, сначала отбрасываются):* Обычный запрос к БД без индекса: ~ 680 мс
Скомпилированный запрос к БД без индекса: ~ 630 мс
using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Diagnostics;
using System.IO;
using System.Linq;
internal static class Program
{
private static void Main()
{
var dataFile = CreateDatabase();
using (var context = new Context(dataFile))
{
Console.WriteLine("Executing query:");
// Modify this to see the difference between compiled and uncompiled queries
const bool compiled = true;
Stopwatch watch = new Stopwatch();
context.Log = Console.Out;
watch.Start();
if (compiled)
{
Console.WriteLine("Result: " + QueryCompiled(context));
}
else
{
Console.WriteLine("Result: " + QueryNormal(context));
}
watch.Stop();
Console.WriteLine("Elapsed milliseconds: " + watch.ElapsedMilliseconds);
}
}
private static string CreateDatabase()
{
var dataFile = Path.Combine(".", "DB.sdf");
bool databaseExists;
using (var context = new Context(dataFile))
{
databaseExists = context.DatabaseExists();
if (!databaseExists)
{
Console.WriteLine("Creating database (only done on the first run)...");
context.CreateDatabase();
}
}
if (!databaseExists)
{
const int articleCount = 1000000;
const int batchSize = 10000;
var random = new Random();
for (int batchStart = 0; batchStart < articleCount; batchStart += batchSize)
{
using (var context = new Context(dataFile))
{
for (int number = batchStart; number < batchStart + batchSize; ++number)
{
context.OutboundArticles.InsertOnSubmit(
new OutboundArticle()
{
Text = new string((char)random.Next(32, 128), random.Next(32)),
IsDeleted = number != articleCount / 2
});
}
context.SubmitChanges();
}
}
using (var context = new Context(dataFile))
{
context.ExecuteCommand(
"CREATE INDEX IX_OutboundArticle_IsDeleted ON OutboundArticle(IsDeleted)");
}
}
return dataFile;
}
private static string QueryNormal(Context context)
{
return
(from article in context.OutboundArticles
where !article.IsDeleted
select article.Text).Single();
}
private static string QueryCompiled(Context context)
{
var compiled = CompiledQuery.Compile(
(Context c, bool isDeleted) =>
(from article in c.OutboundArticles
where article.IsDeleted == isDeleted
select article.Text).Single());
return compiled(context, false);
}
}
[Table]
internal sealed class OutboundArticle
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
private int Id;
[Column(CanBeNull = false, DbType = "NVARCHAR(32) NOT NULL")]
internal string Text;
[Column]
internal bool IsDeleted;
}
internal sealed class Context : DataContext
{
internal Table<OutboundArticle> OutboundArticles;
internal Context(string fileName) : base(fileName)
{
this.OutboundArticles = this.GetTable<OutboundArticle>();
}
}