Получить «СЛЕДУЮЩЕЕ ЗНАЧЕНИЕ ДЛЯ» для серверной последовательности SQL с использованием EF Core 3.1 - невозможно? - PullRequest
2 голосов
/ 12 марта 2020

Я пишу новый ASP. NET Core Web API, и одно из моих требований - возможность использовать EF Core 3.1 для получения следующего значения sequence , определенного в мой SQL сервер в качестве идентификатора для записи, которую мне нужно сохранить.

Я изо всех сил пытаюсь найти способ сделать это - в EF 6.x я использовал метод непосредственно на DbContext потомок вот так:

public int GetNextSequenceValue()
{
    var rawQuery = Database.SqlQuery<int>("SELECT NEXT VALUE FOR dbo.TestSequence;");
    var task = rawQuery.SingleAsync();
    int nextVal = task.Result;

    return nextVal;
}

, а для EF Core до 2.1 я мог бы использовать Database.ExecuteSqlCommand() для запуска фрагмента SQL и получения результатов. Но, похоже, в EF Core 3.x мне не повезло ....

Я знаю, что на DbSet есть методы .FromSqlRaw() и .FromSqlInterpolated - но так как я только нужно вернуть следующее значение последовательности (INT), это не сработает. И я также знаю, что эти методы также существуют на уровне context.Database, который выглядит так, как если бы он был действительно близок к тому, что я имел в EF 6.x - но здесь эти методы будут only возвращать количество строк затронуто - я не нашел способа отправить обратно новое значение из SEQUENCE.

Неужели в EF Core 3.x мне действительно приходится возвращаться к старому ADO? . NET код для извлечения этого значения ?? Есть ли ДЕЙСТВИТЕЛЬНО нет способа выполнить произвольный SQL фрагмент и получить некоторые результаты из контекста ??

Ответы [ 2 ]

3 голосов
/ 13 марта 2020

Похоже, что выполнение raw SQL не является приоритетом для EF Core, поэтому до сих пор (EF Core 3.1) он предоставляет публично лишь несколько базовых c ограниченных методов. FromSql требуется тип сущности или тип сущности без ключа , а ExecuteSqlRaw / ExecuteSqlInterpolated - это "современный" мост к ADO. NET ExecuteNonQuery, который возвращает затронутые строки.

Хорошо, что EF Core построен на основе сервисной архитектуры publi c, поэтому его можно использовать для добавления некоторых недостающих функций. Например, службы могут использоваться для создания так называемой IRelationalCommand , которая имеет все DbCommand методы выполнения, в частности ExecuteScalar, необходимые для SQL, о которых идет речь.

Так как Модель EF Core поддерживает последовательности, также есть сервис для создания IRelationalCommand, необходимого для получения следующего значения (используется внутри генераторами значений HiLo).

С учетом сказанного ниже приведен пример реализации рассматриваемого пользовательского метода с использованием вышеупомянутых концепций:

using System;
using System.Globalization;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomExtensions
    {
        public static long GetNextSequenceValue(this DbContext context, string name, string schema = null)
        {
            var sqlGenerator = context.GetService<IUpdateSqlGenerator>();
            var sql = sqlGenerator.GenerateNextSequenceValueOperation(name, schema ?? context.Model.GetDefaultSchema());
            var rawCommandBuilder = context.GetService<IRawSqlCommandBuilder>();
            var command = rawCommandBuilder.Build(sql);
            var connection = context.GetService<IRelationalConnection>();
            var logger = context.GetService<IDiagnosticsLogger<DbLoggerCategory.Database.Command>>();
            var parameters = new RelationalCommandParameterObject(connection, null, null, context, logger);
            var result = command.ExecuteScalar(parameters);
            return Convert.ToInt64(result, CultureInfo.InvariantCulture);
        }
    }
}
2 голосов
/ 13 марта 2020

Если вы хотите запустить произвольный пакет T SQL и вернуть скалярное значение, вы можете сделать это следующим образом:

var p = new SqlParameter("@result", System.Data.SqlDbType.Int);
p.Direction = System.Data.ParameterDirection.Output;
context.Database.ExecuteSqlRaw("set @result = next value for some_seq", p);
var nextVal = (int)p.Value;
...