EF Core 3.1 Несовместимое поведение отображений HasConversion и HasMaxLength в запросах - PullRequest
2 голосов
/ 01 мая 2020

EF Core 3.1 генерирует запрос с низкой производительностью (из-за пропущенного индекса) при фильтрации по столбцу.

Столбец, вызывающий проблему, имеет следующее сопоставление:

builder.Property(p => p.Id)
            .HasConversion(
                value => value.Value,
                dbValue => new LocationId(dbValue)
            )
            .HasMaxLength(50);

LocationId - значение object

Запрос генерируется из следующего кода:

_repository.Query.FirstOrDefaultAsync(l => l.Id == "DD212334234");

Где _repository.Query - это в основном DbSet of Locations.

Сгенерированный запрос:

SELECT TOP 1 * FROM [locations] as [l] WHERE CAST([l].[Id] AS nvarchar(max)) = 'DD212334234'

Обратите внимание на АКТЕР на nvarchar (макс). Идентификатор столбца равен nvarchar (50) и имеет индекс. Вышеприведенный запрос для записей 2.5M выполняется в течение примерно 1.2 с. Если я удаляю CAST и запускаю запрос, он завершается за 100 мс.

Любая помощь в сортировке этого будет высоко оценена.

Обновление: Мой DbContext построен с:

builder.UseSqlServer(options.ConnectionString, x =>
                    {
                        x.MigrationsAssembly(assemblyName);
                        x.MigrationsHistoryTable($"__migrations_{contextName}");
                        x.UseNetTopologySuite();
                    });

Другое обновление:

Если я закомментирую .HasConversion (...), запрос генерируется без CAST. LocationId выглядит так:

public class LocationId : ValueObject
{
    private LocationId(string value)
    {
        Value = value.ToUpperInvariant();
    }

    public string Value { get; }

    public static implicit operator string(LocationId c) => c.Value;
    public static explicit operator LocationId(string s) => new LocationId(s);

     ...
}

1 Ответ

3 голосов
/ 01 мая 2020

Проблема вызвана операторами

public static implicit operator string(LocationId c) => c.Value;
public static explicit operator LocationId(string s) => new LocationId(s);

, поэтому выражение

l => l.Id == "DD212334234"

на самом деле

l => (string)l.Id == "DD212334234"

, что приводит к CAST в переведенном SQL.

Изначально я не смог воспроизвести его, потому что в моем тесте оба преобразования были неявными

public static implicit operator string(LocationId c) => c.Value;
public static implicit operator LocationId(string s) => new LocationId(s);

, поэтому приведенное выше выражение на самом деле

l => l.Id == (LocationId)"DD212334234"

и не вводит CAST в сгенерированном SQL.

Таким образом, решение состоит в том, чтобы либо сделать оператор LocationId неявным, либо использовать явное приведение

l => l.Id == (LocationId)"DD212334234"

или LocationId конструктор

l => l.Id == new LocationId("DD212334234")

или LocationId переменная.

Или, вообще, сравнение типов клиентов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...