Ядро Entity Framework — ExecuteSqlCommand: МАКСИМАЛЬНАЯ функция, возвращающая неправильное значение

#sql-server #.net-core #entity-framework-core #asp.net-core-2.0

#sql-сервер #.net-ядро #entity-framework-core #asp.net-core-2.0

Вопрос:

Я пытаюсь сделать простую вещь: получить максимальное значение столбца. Знаете, это довольно просто, просто запустите SELECT MAX (столбец) ИЗ таблицы;

Проблема заключается в том, когда я пытаюсь сделать это в моем проекте .NET Core 2.1, используя Entity Framework.

У меня есть функция, которая должна возвращать следующее значение столбца.

 private int getColumNextValue(string table, string column)
{
  string query = $"SELECT MAX({column})   1 FROM {table};";    
  return base.context.Database.ExecuteSqlCommand(query);
}
  

Запрос генерируется корректно:

введите описание изображения здесь

Однако вместо реального значения возвращается значение -1.

введите описание изображения здесь

Но когда я запускаю точно такой же запрос в Sql Server Management Studio, результат оказывается правильным:

введите описание изображения здесь

Что происходит?

Комментарии:

1. MAX(ID) 1 может легко возвращать дубликаты, если, например, удалена последняя строка. Вот почему он редко используется для генерации идентификаторов. Вместо этого используйте IDENTITY или ПОСЛЕДОВАТЕЛЬНОСТЬ . У EF также нет проблем при работе со значениями идентификаторов. Для настройки внешних ключей не обязательно заранее знать идентификаторы. При добавлении нового корневого объекта и вложенных объектов EF сгенерирует соответствующие инструкции для установки всех идентификаторов в соответствии с отношениями, настроенными в DbContext

2. Привет @PanagiotisKanavos; Спасибо за совет. Я понимаю стратегию идентификации, но дело в том, что я имею дело с устаревшей базой данных, которую ее создатель не использовал IDENTITY , поэтому я должен использовать стратегию MAX 1… Печально, но это реальность. Итак, мне нужно выполнить запрос insert в устаревшую базу данных через вторичное соединение, вот почему мне нужно выполнить запрос «на лету», не привязывая его к модели.

3. Dapper не меняет того факта, что MAX 1 создает дубликаты. Вы все равно получили бы их, независимо от того, использовали ли вы Dapper или straight ADO.NET SqlCommand. Фактически, если у вас более 1 одновременного пользователя, вы рискуете, что оба они получат одинаковое максимальное значение и попытаются вставить данные, используя один и тот же идентификатор

4. Фактически, EF Core предлагает стратегию HiLo key для безопасной генерации идентификаторов на клиенте.

5. Большое вам спасибо за помощь, @PanagiotisKanavos.

Ответ №1:

Для ExecuteSqlCommand она возвращает только количество затронутых строк. Это не привело бы к выполнению запроса и возврату результата.

В качестве обходного пути вы могли бы попробовать, например:

 public class ApplicationDbContext : IdentityDbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
    public DbSet<User> User { get; set; }


    public async Task<T> ExecuteScalarAsync<T>(string rawSql, params object[] parameters)
    {
        var conn = Database.GetDbConnection();
        using (var command = conn.CreateCommand())
        {
            command.CommandText = rawSql;
            if (parameters != null)
                foreach (var p in parameters)
                    command.Parameters.Add(p);
            await conn.OpenAsync();
            return (T)await command.ExecuteScalarAsync();
        }
    }
}
  

И используйте ExecuteScalarAsync подобное

 public async Task<IActionResult> Index()
{
    string query = $"SELECT MAX(SEQ)   1 FROM [User];";
    var result = await _context.ExecuteScalarAsync<int>(query);
    return View();
}
  

Комментарии:

1. Вы открываете соединение, связанное с контекстом, но не закрываете его впоследствии, что нехорошо.

Ответ №2:

Я использовал QueryFirstOrDefault<int> для решения своей проблемы. Я написал этот вспомогательный метод для повторного использования:

 private int GetColumNextValue(string table, string column)
{
  using (SqlConnection conn = new SqlConnection(this.configuration.GetConnectionString("MyConnection")))
  {
    string query = $"SELECT MAX({column})   1 FROM {table};";
    return conn.QueryFirstOrDefault<int>(query);
  }
}
  

Я надеюсь, что это может помочь другим людям.