简体   繁体   中英

How to execute multiple raw SQL commands in a single transaction?

I need to insert several values into several tables using .net core app.

First table [AI_Table] contains keys and second table [AI_TableLanguage] contains names in mutiple languages. [AI_Table] has IDENTITY primary key, so DB generates it. I need to get that key from [AI_Table] and insert it into [AI_TableLanguage].

Firstly, I am not sure get max key is the right approach, and secondly, what if two or more users approach this table at the same time? Will it deadlock, or what? So my question is does or if not, how can this http post action result act as one single transaction, so noone can interrupt these inserts?

General idea:

  • insert into [AI_Table] (DB generates a PK).
  • get that PK and insert it into [AI_TableLanguage] three times.

I read few other questions that got answered and also read the Microsoft documentation. I couldn't find any answer that uses RAW SQL. Also, I couldn't find if the whole IActionResult acts as single transaction.

[HttpPost, ActionName("Create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(IzvorDropDown IzvorDropDown, InstitucijaDropDown InstitucijaDropDown, UpitTabeleCreate UpitTabeleCreate)
{
    //insert
    await context.Database.ExecuteSqlCommandAsync($"INSERT INTO AI_Table (IAICode, VOD, VDO, user_insert, user_update, date_insert, date_update) VALUES ({IzvorDropDown.IAIcode},{UpitTabeleCreate.VOD},{UpitTabeleCreate.VDO},{TrenutniKorisnik},{TrenutniKorisnik},{TrenutnoVreme},{TrenutnoVreme})");            

    //get max key
    var item = await context.NextIAITableCodeGenerator.FromSql($"SELECT ISNULL(MAX(IAITableCode), 0) AS NextIAIcode FROM AI_Table").ToListAsync();
    int IAITableCode = item[0].NextIAIcode;     

    //insert continue
    await context.Database.ExecuteSqlCommandAsync($"INSERT INTO AI_TableLanguage (IAITableCode, LanguageID, TableName) VALUES ({IAITableCode},1,{UpitTabeleCreate.TableNameCyr})");  //cirilica - radi
    await context.Database.ExecuteSqlCommandAsync($"INSERT INTO AI_TableLanguage (IAITableCode, LanguageID, TableName) VALUES ({IAITableCode},2,{UpitTabeleCreate.TableNameLat})"); //latinica
    await context.Database.ExecuteSqlCommandAsync($"INSERT INTO AI_TableLanguage (IAITableCode, LanguageID, TableName) VALUES ({IAITableCode},3,{UpitTabeleCreate.TableNameEng})"); //engleski

    return RedirectToAction(nameof(Index));
}

The code itself works. It inserts correctly into DB, but I don't know if it will continue to do so when there are many users trying to access these tables at the same time.

You are right to assume that this will lead to concurrency issues.

Use a stored procedure and do a search for how to use SCOPE_IDENTITY

Databases work on commit logs. And in every commit, database checks the final version(state) of the record(or sequence) and the version number provided by the commit log itself and if there is a inconsistency between these informations you may take a exception like "This record has been changed by another user since you started" . So it is important to make these(related with each other in one transaction) insert/update/delete operations with a single commit . So that you can use database's consistency mechanisms and can be sure about the behaviour of the code in any load circumstance.

One way to be 100% sure you get the correct IDENTITY is to use the OUTPUT clause documentation

In this example FieldID is the IDENTITY KEY to the table TableXYZ

Stored Prodcedure example:

USE [DataBaseX]
GO
IF OBJECT_ID('spTableXYZAdd') IS NOT NULL
    DROP PROC spTableXYZAdd
GO

CREATE PROCEDURE dbo.spTableXYZAdd
                @FieldOne int,
                @FieldTwo int,
                @FieldThree DateTime,
                @ReturnIdentityValue int OUTPUT

AS
BEGIN

        DECLARE @TempTableXYZ table ( TempID int )

        INSERT INTO TableXYZ (FieldOne,FieldTwo,FieldThree) 
        OUTPUT INSERTED.FieldID into @TempTableXYZ
        VALUES (@FieldOne,@FieldTwo,@FieldThree)

        SELECT @ReturnIdentityValue = (SELECT TempID FROM @TempTableXYZ)
END
GO

Your code will be something like this:

public static int AddSomeRecord(RecordClass oRecordObj)
{
    int iRetVal = 0;
    using (SqlConnection conn = Connection.GetConnection())
    {
        using (SqlCommand cmd = new SqlCommand("spTableXYZAdd", conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@FieldOne", oRecordObj.FieldOne);
            cmd.Parameters.AddWithValue("@FieldTwo", oRecordObj.FieldTwo);
            cmd.Parameters.AddWithValue("@FieldThree", oRecordObj.FieldThree);
            cmd.Parameters.Add("@ReturnIdentityValue", SqlDbType.Int).Direction = ParameterDirection.Output;
            conn.Open();
            cmd.ExecuteNonQuery();
            iRetVal = Convert.ToInt32(cmd.Parameters["@ReturnIdentityValue"].Value);
        }
    }
    return iRetVal;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM