Как сделать проверку в базе данных?

Как сделать так чтобы когда приходил запрос в базу данных была проверка на наличие в ней пользователя, использую SqlLite и C#. Я сделал так, но данную проверку я же не буду писать в каждом методе, да и не дело это всегда возвращать объект user) Помогите)

public class DataBaseCommands
{
    public static async Task AddUsers(int userId, string userName,decimal balance)
    {
        await using var db = new DBTelegramContext();
        if (!(await db.Users.AnyAsync(u => u.UserId == userId)))
        {
            User user = new User { UserId = userId, UserName = userName, Balance = balance };
            await db.Users.AddAsync(user);
            await db.SaveChangesAsync();
        }
    }
}

Класс таблицы

public partial class User
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string UserName { get; set; }
    public decimal Balance { get; set; }
    public decimal GeneralPay { get; set; }
}

Ответы (1 шт):

Автор решения: Alexander Petrov

Во-первых, непонятно, зачем в классе User два айдишника. Если они действительно имеют смысл, то опишите их предназначение (добавьте информацию в вопрос).
Я бы оставил только одно свойство Id.

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public decimal Balance { get; set; }
    public decimal GeneralPay { get; set; }
}

Во-вторых, поменяем сигнатуру метода.
Он добавляет одного пользователя, значит название должно быть AddUser.
Метод SaveChangesAsync возвращает количество вставленных записей. Не нужно его игнорировать, лучше вернуть его из метода. Это даст возможность контролировать его работу. Меняем Task на Task<int>.
Метод AddAsync тоже имеет возвращаемое значение. Т. к. в данном конкретном случае оно нами никак не используется, декларируем это явно: _ = await db.Users.AddAsync(user); - Discards.

В-третьих, при попытке вставки записи с неуникальным ключом будет выброшено исключение. Вот его-то и нужно ловить в блоке try-catch. Тут возможны два варианта.

В методе не делаем реакции на возможные исключения. Просто вставляем запись:

public static async Task<int> AddUser(int userId, string userName, decimal balance)
{
    User user = new User { Id = userId, UserName = userName, Balance = balance };
    using var db = new DBTelegramContext();
    _ = await db.Users.AddAsync(user);
    return await db.SaveChangesAsync();
}

А вызов метода оборачиваем в try-catch:

try
{
    _ = await DataBaseCommands.AddUser(1, "user1", 101);
}
catch (DbUpdateException e)
{
    // Тут реакция на ошибки
    Console.WriteLine(e);
}

Другой вариант. try-catch внутри метода:

public static async Task<int> AddUser(int userId, string userName, decimal balance)
{
    User user = new User { Id = userId, UserName = userName, Balance = balance };
    using var db = new DBTelegramContext();

    try
    {
        _ = await db.Users.AddAsync(user);
        return await db.SaveChangesAsync();
    }
    catch (DbUpdateException e)
    {
        Console.WriteLine(e);
        // Тут реакция на ошибку: логирование и т. п.
        return 0;
    }
}

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


В обоих случаях вам же нужно не просто подавить ошибку, а как-то проинформировать пользователя и попытаться исправить положение. Ведь это может оказаться катастрофой, что данные не вставлены и пропали.

int entriesWritten = await DataBaseCommands.AddUser(1, "user1", 101);
if (entriesWritten != 1) // что-то пошло не так

Самое главное: почему плохо делать ручную проверку перед вставкой?

Допустим, вот эта строка завершилась успешно, записи с таким id в таблице нет:

if (!(await db.Users.AnyAsync(u => u.UserId == userId)))

И тут в другом потоке или процессе происходит вставка в эту таблицу записи с таким же значением id. И когда дело доходит до вызова SaveChangesAsync в нашем потоке, то он завершится ошибкой.

То есть это очень ненадёжная проверка! С большим сожалением могу отметить, что такой способ постоянно приводят в блогах, статьях и в сотнях ответов на разных языках программирования тут, на stackoverflow.

Конечно, sqlite - принципиально однопользовательская БД и с ней нельзя работать из нескольких потоков/процессов. И поэтому с ней проверка с if будет работать корректно. Но лучше сразу приучить себя к правильной обработке всех возможных ошибок.


И ещё одно примечание.

Вызов AnyAsync сделает запрос к БД. Вызов SaveChangesAsync - ещё один запрос. Итого два. А самое дорогое в работе с базами данных - это именно отправка запросов. Следует минимизировать их количество.

→ Ссылка