Reference · Database · SQL Server

Stored Procedure, function, dan memanggil SP dari .NET

Ringkasan satu halaman. Dari Lesson 0020. Skrip lab: dotnet/lab/0020-stored-procedure.sql.

Kerangka SP [1] [3]

CREATE OR ALTER PROCEDURE dbo.usp_AdjustStock      -- usp_, bukan sp_ (system procedure)
    @ProductId int,
    @Delta     int,
    @NewStock  int = NULL OUTPUT                   -- default boleh konstanta atau NULL
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;
    BEGIN TRY
        BEGIN TRANSACTION;
        IF NOT EXISTS (SELECT 1 FROM dbo.Product WHERE Id = @ProductId)
            THROW 50001, N'Product tidak ditemukan.', 1;
        UPDATE dbo.Product SET Stock = Stock + @Delta
        WHERE Id = @ProductId AND Stock + @Delta >= 0;
        IF @@ROWCOUNT = 0
            THROW 50002, N'Stock tidak boleh negatif.', 1;
        SELECT @NewStock = Stock FROM dbo.Product WHERE Id = @ProductId;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
        THROW;                                      -- wajib: error di CATCH tidak sampai ke aplikasi
    END CATCH
END;
GO
DECLARE @s int, @status int;
EXEC @status = dbo.usp_AdjustStock @ProductId = 1, @Delta = -5, @NewStock = @s OUTPUT;  -- OUTPUT di EXEC juga

SP vs function [5]

SPScalar UDFInline TVF
Ubah dataYaTidakTidak
Di dalam queryTidak (EXEC saja, juga tidak di CTE)Ekspresi di SELECT/WHEREDi FROM, bisa di-JOIN
Hasil0..n result set, OUTPUT, RETURN1 nilai1 tabel (satu SELECT)
TRY/CATCH, transaction, dynamic SQL, temp tableYaTidakTidak

Scalar UDF di SELECT banyak baris = pemrosesan per baris (lambat); SQL Server 2019+ bisa meng-inline sebagian. Untuk logika query yang dipakai ulang → inline TVF.

SP vs query dari aplikasi [6]

AspekSPEF Core (berparameter)
Performahanya EXEC lewat jaringan; plan dibuat saat eksekusi pertama lalu dipakai ulang (bisa merugikan: parameter sniffing)plan juga di-cache; CRUD biasa setara
Keamananizin EXECUTE tanpa izin ke tabel; parameter mencegah injectionselalu berparameter kecuali raw SQL digabung string
Testabilitysulit di-unit-testlogika di Service C#, mudah di-test
Versioningskrip database, terpisah dari rilisikut kode, review, Migration

Default: EF Core untuk CRUD. SP: set-based berat, query yang sulit di LINQ, atau kebijakan DBA.

Memanggil dari .NET [7] [8]

// EF Core 8 — aman: FormattableString, nilai menjadi DbParameter
var list = await context.Products
    .FromSql($"EXECUTE dbo.usp_GetProductsByCategory @CategoryId={categoryId}")
    .AsNoTracking().ToListAsync();                 // tanpa .Where setelah EXEC
int rows = await context.Database.ExecuteSqlAsync(   // -1: SP memakai SET NOCOUNT ON
    $"EXECUTE dbo.usp_AdjustStock @ProductId={id}, @Delta={delta}");

// BERBAHAYA                                       // AMAN bila terpaksa raw
context.Products.FromSqlRaw($"... = '{input}'");  context.Products.FromSqlRaw("... = {0}", input);

// Dapper
var p = new DynamicParameters();
p.Add("@CategoryId", 6);
p.Add("@TotalCount", dbType: DbType.Int32, direction: ParameterDirection.Output);
var rowsDto = await conn.QueryAsync<ProductDto>("dbo.usp_GetProductsByCategory", p,
    commandType: CommandType.StoredProcedure);
int total = p.Get<int>("@TotalCount");

Jawaban 60–90 detik

Default EF Core/LINQ untuk CRUD: logika di C#, bisa di-unit-test, ikut versioning aplikasi, tetap berparameter dan plan-nya di-cache. SP untuk operasi set-based berat, query yang sulit di LINQ, atau bila akses harus lewat izin EXECUTE. SP tidak otomatis lebih cepat (plan dipakai ulang → parameter sniffing), dan biayanya testability serta deploy terpisah. Panggil dengan FromSql/ExecuteSql atau Dapper CommandType.StoredProcedure; jangan pernah FromSqlRaw dengan $"...".

Sumber: [1] CREATE PROCEDURE · [2] TRY...CATCH · [3] THROW · [4] RAISERROR · [5] Create user-defined functions · [6] Stored procedures · [7] EF Core: SQL Queries · [8] Dapper README · [9] SQL injection · [10] SqlCommand.ExecuteNonQuery