Как заставить запрос LINQ to SQL использовать индекс, содержащий столбец BIT в SQL Server Compact? - PullRequest
2 голосов
/ 18 июля 2011

Я использую следующий запрос LINQ to SQL в SQL Server Compact DB ...

from article in context.OutboundArticles
where !article.IsDeleted
select article

... который генерирует следующий SQL:

SELECT [t0].[Id], [t0].[Text], [t0].[IsDeleted]
FROM [OutboundArticle] AS [t0]
WHERE NOT ([t0].[IsDeleted] = 1)

Это было бы прекрасно, если бы не тот факт, что в столбце IsDeleted есть индекс, а SQL Server Compact не будет использовать индекс, если SQL не выглядит следующим образом:

SELECT [t0].[Id], [t0].[Text], [t0].[IsDeleted]
FROM [OutboundArticle] AS [t0]
WHERE [t0].[IsDeleted] = CONVERT(BIT, 0)

Итак, вопрос в том, как убедить LINQ to SQL сгенерировать «CONVERT (BIT, 0)»? Я уже попробовал следующее ...

from article in context.OutboundArticles
where article.IsDeleted == Convert.ToBoolean(0)
select article

... но сгенерированный SQL выглядит так же.

Ответы [ 2 ]

2 голосов
/ 19 июля 2011

После большого количества копаний кажется, что 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>();
    }
}
0 голосов
/ 18 июля 2011

Проблема (я думаю) может заключаться в том, что он конвертирует ваш индексный столбец в другой тип данных.

Я бы попробовал это сначала

where !(article => article.isdeleted = true)

В противном случае, возможно, эти ...

where !article.isdeleted.toboolean

ИЛИ

where !article.isdeleted = true
...