Для получения Id при окончании действия HttpPost (ADO.NET и WEB API) нужно перезагружать страницу

Я использую WebApi(ADO.NET и JS с FetchApi) для формы и таблицы. конечный результат Как видно из картинки, имеется Id 79 и после него идёт Id = 0(только что создал новую строку). Но после того как я перезагружу страницу Id примет нормальное значение. Стоит уточнить что с Ef Core всё происходит гладко

Post метод в контроллере:

[HttpPost]
    public async Task<ActionResult<FormAbsence>> Create(FormAbsence model)
    {
        if (model != null)
        {
            var result = await _changeDataRepository.CreateDataAsync(model);
            if (result == 0)
                return this.StatusCode((int)HttpStatusCode.InternalServerError);
            return Ok(model);
        }
        return BadRequest();
    }

Метод CreateDataAsync():

public async Task<int> CreateDataAsync(FormAbsence model)
    {
        _context.Connection();
        int countChangeData = 0;
        if (model == null)
            return countChangeData;
     
        var sqlExpression =
            "INSERT INTO FormAbsence (reason, start_date, duration, discounted, description)" +
            $" VALUES ({((int)model.Reason)}, '{model.StartDate.Date}', {model.Duration}, '{model.Discounted}', '{model.Description}')";
        using (SqlConnection connection = new SqlConnection(_context.ConnectString))
        {
            await connection.OpenAsync();

            SqlCommand command = new SqlCommand(sqlExpression, connection);
            countChangeData = await command.ExecuteNonQueryAsync();
        }
        return countChangeData;
    }

И главный класс с перечеслением:

public class FormAbsence
{
    public int Id { get; set; }
    public Causes Reason { get; set; }
    public DateTime StartDate { get; set; }
    public int Duration { get; set; }
    public bool Discounted { get; set; }
    public string Description { get; set; }
}public enum Causes
{
    Отпуск,
    Больничный,
    Прогул
}

Что касается html.

<form name="absenceForm">
    <input name="id" type="hidden" value="0" />
    <div class="form-group col-md-5">
        <label for="validationDefault01">Причина отсутствия:</label>
        <select class="form-select" name="reason" id="validationDefault01" aria-label="Default select example" required>
            <option value="0">Отпуск</option>
            <option value="1">Больничный</option>
            <option value="2">Прогул</option>
        </select>
        <div class="valid-feedback">
            Все хорошо!
        </div>
        <div class="invalid-feedback">
            Это поле обязательно
        </div>
    </div>
    <div class="panel-body">
        <button type="submit" id="submit" class="btn btn-primary">Сохранить</button>
        <a id="reset" class="btn btn-primary">Сбросить</a>
    </div>
</form>

в js я использую fetchApi:

async function CreateFormAbsence(Freason, FstartDate, Fduration, Fdiscounted, Fdescription) {
            const response = await fetch("/api/Values", {
                method: "POST",
                headers: { "Accept": "application/json", "Content-Type": "application/json" },
                body: JSON.stringify({
                    reason: parseInt(Freason, 10),
                    startDate: FstartDate,
                    duration: Fduration,
                    discounted: Fdiscounted,
                    description: Fdescription
                })
            });
            if (response.ok) {
                let result = await response.json();
                document.querySelector("tbody").append(row(result));
                reset();
                console.log("создана новая форма");
            }

и последняя функция row (добавление новой строки и заполнение её данными) и events для кнопок 'сохранить' и сбросить

        function row(form) {
        let tr = document.createElement('tr');
        tr.setAttribute("data-rowid", form.id);

        let idTd = document.createElement("td");
        idTd.append(form.id);
        tr.append(idTd);

        let reasonTd = document.createElement("td");
        reasonTd.append(form.reason);
        tr.append(reasonTd);
        
        let startDateId = document.createElement("td");
        startDateId.append(form.startDate);
        tr.append(startDateId);

        let durationId = document.createElement("td");
        durationId.append(form.duration)
        tr.append(durationId);

        let discountedId = document.createElement("td");
        discountedId.append(form.discounted);
        tr.append(discountedId);

        let descriptionId = document.createElement("td");
        descriptionId.append(form.description);
        tr.append(descriptionId)}
document.forms["absenceForm"].addEventListener("submit", e => {
        e.preventDefault();
        form = document.forms["absenceForm"];
        let id = form.elements["id"].value;
        let reason = form.elements["reason"].value;
        let startDate = form.elements["startDate"].value;
        let duration = form.elements["duration"].value;
        let discounted = form.elements["discounted"].value;
        let description = form.elements["description"].value;
        if (id == 0) {
            CreateFormAbsence(reason, startDate, duration, discounted, description)
        } else {
            EditFormAbsecne(id, reason, startDate, duration, discounted, description);
        }
    });

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

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

EF Core после добавления (INSERT) данных в БД сразу же выполняет получение (SELECT) ID из неё. А в рукопашном коде у вас я вижу только INSERT.

Это же нужно сделать и вам.

public async Task<int> CreateDataAsync(FormAbsence model)
{
    if (model == null)
        return 0;

    var sqlExpression =
        "INSERT INTO FormAbsence (reason, start_date, duration, discounted, description) " +
        "VALUES (@reason, @startDate, @duration, @discounted, @description); " +
        "SELECT scope_identity();";

    using (var connection = new SqlConnection(_context.ConnectString))
    {
        await connection.OpenAsync();

        using (var command = new SqlCommand(sqlExpression, connection))
        {
            command.Parameters.Add("reason", SqlDbType.NVarChar).Value = model.Reason;
            command.Parameters.Add("startDate", SqlDbType.DateTime2).Value = model.StartDate.Date;
            command.Parameters.Add("duration", SqlDbType.Int).Value = model.Duration;
            command.Parameters.Add("discounted", SqlDbType.NVarChar).Value = model.Discounted;
            command.Parameters.Add("description", SqlDbType.NVarChar).Value = model.Description;

            int id = (int)(decimal)(await command.ExecuteScalarAsync());
            model.Id = id;
            return id;
        }
    }
}

Обязательно используйте параметризованные запросы! Это полностью избавит от sql-инъекций и повысит производительность.

Я не знаю, какие именно типы у вас в таблице, поэтому сами отредактируйте значения SqlDbType.

Аналогично, я не уверен в возвращаемом типе id. Укажите правильный.

Ваш метод возвращает Task<int> - что значит этот int? Я вернул id, но это не имеет большого смысла. Решайте сами.


Если включить логирование в EF Core, то при вставке данных будет сгенерирован примерно такой запрос (Orders и UserId - название таблицы и колонки в моей БД):

SET NOCOUNT ON;
INSERT INTO [Orders] ([UserId])
VALUES (@p0);
SELECT [Id]
FROM [Orders]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();

Как видим, сразу после вставки осуществляется получение только что сгенерированного айдишника функцией scope_identity() и это значение возвращается. И EF Core обновляет Id в сущности.

Отмечу, что SCOPE_IDENTITY - это функция Sql Server. При работе с другой СУБД нужно будет использовать что-то другое. ORM освобождают нас от переписывания кода при переходе на другую СУБД (впрочем, не всегда).

Судя по SqlConnection, SqlCommand вы работаете именно с Sql Server.

Что такое NOCOUNT. Нужно ли это вам - думайте сами.

→ Ссылка