Bagaimana cara mengirimkan parameter ke metode DbContext.Database.ExecuteSqlCommand?

222

Anggap saja saya memiliki kebutuhan yang valid untuk secara langsung mengeksekusi perintah sql di Entity Framework. Saya mengalami kesulitan mencari tahu cara menggunakan parameter dalam pernyataan sql saya. Contoh berikut (bukan contoh sebenarnya) tidak berfungsi.

var firstName = "John";
var id = 12;
var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);

Metode ExecuteSqlCommand tidak memungkinkan Anda untuk memasukkan parameter bernama seperti di ADO.Net dan dokumentasi untuk metode ini tidak memberikan contoh tentang cara menjalankan kueri parameterisasi.

Bagaimana cara menentukan parameter dengan benar?

jessegavin
sumber

Jawaban:

294

Coba ini:

var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id";

ctx.Database.ExecuteSqlCommand(
    sql,
    new SqlParameter("@FirstName", firstname),
    new SqlParameter("@Id", id));
Robert te Kaat
sumber
2
Ini benar-benar harus jawaban yang ditandai benar, yang di atas rentan terhadap serangan dan bukan praktik terbaik.
Min
8
@Min, jawaban yang diterima tidak lebih rentan terhadap serangan daripada jawaban ini. Mungkin Anda mengira itu menggunakan string. Keset - tidak.
Simon MᶜKenzie
1
Ini harus ditandai sebagai jawaban! Ini adalah satu-satunya jawaban yang benar karena berfungsi dengan DateTime.
Sven
219

Ternyata ini berhasil.

var firstName = "John";
var id = 12;
var sql = "Update [User] SET FirstName = {0} WHERE Id = {1}";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);
jessegavin
sumber
12
Ini akan berhasil, tetapi mekanisme ini memungkinkan untuk injeksi SQL dan juga mencegah database menggunakan kembali rencana eksekusi ketika pernyataan itu muncul lagi tetapi dengan nilai yang berbeda.
Greg Biles
95
@RegB. Saya tidak berpikir Anda benar di sini. Saya telah menguji bahwa itu tidak akan memungkinkan saya untuk, misalnya, mengakhiri string literal lebih awal. Selain itu, saya melihat kode sumber dan menemukan bahwa itu menggunakan DbCommand.CreateParameter untuk membungkus semua nilai mentah menjadi parameter. Jadi tidak ada injeksi SQL, dan pemanggilan metode ringkas yang bagus.
Josh Gallagher
7
@ JoshGallagher Ya, Anda benar. Saya sedang memikirkan sebuah string. Skenario kebiasaan menggabungkan ini.
Greg Biles
6
Ini TIDAK berfungsi pada SQL Server 2008 R2. Anda akan memiliki @ p0, @ p2, ..., @pN alih-alih parameter yang Anda berikan. Gunakan SqlParameter("@paramName", value)sebagai gantinya.
Arnthor
Tidak dapat percaya bahwa tidak ada yang menyebutkan dampak kinerja dari kueri ini jika kolom tabel database ditetapkan sebagai ANSI varchar. String .Net adalah unicode artinya paramater akan diteruskan ke server sebagai nvarchar. Ini akan menyebabkan masalah kinerja yang signifikan karena lapisan data harus melakukan terjemahan data. Anda harus tetap menggunakan pendekatan SqlParameter dan menentukan tipe data.
akd
68

Anda dapat:

1) Lewati argumen mentah dan gunakan sintaks {0}. Misalnya:

DbContext.Database.SqlQuery("StoredProcedureName {0}", paramName);

2) Lulus argumen subkelas DbParameter dan gunakan sintaks @ParamName.

DbContext.Database.SqlQuery("StoredProcedureName @ParamName", 
                                   new SqlParameter("@ParamName", paramValue);

Jika Anda menggunakan sintaks pertama, EF sebenarnya akan membungkus argumen Anda dengan kelas DbParamater, menetapkannya, dan mengganti {0} dengan nama parameter yang dihasilkan.

Sintaks pertama jika disukai karena Anda tidak perlu menggunakan pabrik atau tahu apa jenis DbParamaters untuk membuat (SqlParameter, OracleParamter, dll.).

Will Brown
sumber
6
Terpilih untuk menyebutkan bahwa sintaks {0} adalah agnostik basis data. "... kamu tidak perlu menggunakan pabrik atau tahu jenis DbParamaters apa untuk membuat ..."
Makotosan
Skenario 1 tidak digunakan lagi karena versi yang diinterpolasi. Setara sekarang: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB
20

Jawaban lain tidak berfungsi saat menggunakan Oracle. Anda harus menggunakan :bukan @.

var sql = "Update [User] SET FirstName = :FirstName WHERE Id = :Id";

context.Database.ExecuteSqlCommand(
   sql,
   new OracleParameter(":FirstName", firstName), 
   new OracleParameter(":Id", id));
Ryan M
sumber
Terima kasih Tuhan tidak ada yang menggunakan Oracle. Yah tidak sukarela! EDIT: Permintaan maaf untuk lelucon yang terlambat! EDIT: Permintaan maaf untuk lelucon buruk!
Chris Bordeman
18

Coba ini (diedit):

ctx.Database.ExecuteSqlCommand(sql, new SqlParameter("FirstName", firstName), 
                                    new SqlParameter("Id", id));

Gagasan sebelumnya salah.

Ladislav Mrnka
sumber
Ketika saya melakukan itu, saya mendapatkan kesalahan berikut: "Tidak ada pemetaan dari tipe objek System.Data.Objects.ObjectParameter ke tipe asli penyedia terkelola yang dikenal."
jessegavin
Maaf kesalahan saya. Gunakan DbParameter.
Ladislav Mrnka
7
DbParameter adalah abstrak. Anda harus menggunakan SqlParameter atau menggunakan DbFactory untuk membuat DbParameter.
jrummell
12

Untuk entitas Framework Core 2.0 atau lebih baru, cara yang benar untuk melakukan ini adalah:

var firstName = "John";
var id = 12;
ctx.Database.ExecuteSqlCommand($"Update [User] SET FirstName = {firstName} WHERE Id = {id}";

Perhatikan bahwa Entity Framework akan menghasilkan dua parameter untuk Anda, sehingga Anda terlindungi dari Sql Injection.

Perhatikan juga bahwa BUKAN:

var firstName = "John";
var id = 12;
var sql = $"Update [User] SET FirstName = {firstName} WHERE Id = {id}";
ctx.Database.ExecuteSqlCommand(sql);

karena ini TIDAK melindungi Anda dari Sql Injection, dan tidak ada parameter yang dihasilkan.

Lihat ini untuk lebih lanjut.

Greg Gum
sumber
3
Saya sangat menyukai .NET Core 2.0 sehingga memberi saya air mata kegembiraan: ')
Joshua Kemmerer
Saya dapat melihat antusiasme beberapa orang sejak. NET Core 2.0 telah menjadi perjalanan yang lebih mulus bagi saya sejauh ini.
Paul Carlton
Bagaimana yang pertama tidak rentan terhadap injeksi SQL? Keduanya menggunakan interpolasi string C #. Yang pertama tidak akan bisa mencegah ekspansi string, sejauh yang saya tahu. Saya menduga Anda bermaksud demikianctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked
@ CodeNaked, Tidak, saya tidak bermaksud begitu. EF mengetahui masalah ini, dan menciptakan dua parameter aktual untuk melindungi Anda. Jadi bukan hanya string yang dilewati. Lihat tautan di atas untuk detailnya. Jika Anda mencobanya di VS, versi saya tidak akan mengeluarkan peringatan tentang Sql Injection, dan yang lainnya akan.
Greg Gum
1
@GregGum - TIL tentang FormattableString. Anda benar dan itu sangat keren!
CodeNaked
4

Versi sederhana untuk Oracle. Jika Anda tidak ingin membuat OracleParameter

var sql = "Update [User] SET FirstName = :p0 WHERE Id = :p1";
context.Database.ExecuteSqlCommand(sql, firstName, id);
Andreas
sumber
2
Dalam SQL Server saya menggunakan @ p0 bukannya: p0.
Marek Malczewski
3
var firstName = "John";
var id = 12;

ctx.Database.ExecuteSqlCommand(@"Update [User] SET FirstName = {0} WHERE Id = {1}"
, new object[]{ firstName, id });

Ini sangat sederhana !!!

Gambar untuk mengetahui referensi parameter

masukkan deskripsi gambar di sini

zaw
sumber
2

Untuk Metode async ("ExecuteSqlCommandAsync") Anda dapat menggunakannya seperti ini:

var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id";

await ctx.Database.ExecuteSqlCommandAsync(
    sql,
    parameters: new[]{
        new SqlParameter("@FirstName", firstname),
        new SqlParameter("@Id", id)
    });
Magu
sumber
Ini bukan db-agnostik, ini hanya akan berfungsi untuk MS-SQL Server. Ini akan gagal untuk Oracle atau PG.
ANeves
1

Jika tipe data database yang mendasarinya adalah varchar maka Anda harus tetap menggunakan pendekatan di bawah ini. Kalau tidak, permintaan akan memiliki dampak kinerja yang sangat besar.

var firstName = new SqlParameter("@firstName", System.Data.SqlDbType.VarChar, 20)
                            {
                                Value = "whatever"
                            };

var id = new SqlParameter("@id", System.Data.SqlDbType.Int)
                            {
                                Value = 1
                            };
ctx.Database.ExecuteSqlCommand(@"Update [User] SET FirstName = @firstName WHERE Id = @id"
                               , firstName, id);

Anda dapat memeriksa profiler Sql untuk melihat perbedaannya.

akd
sumber
0
public static class DbEx {
    public static IEnumerable<T> SqlQueryPrm<T>(this System.Data.Entity.Database database, string sql, object parameters) {
        using (var tmp_cmd = database.Connection.CreateCommand()) {
            var dict = ToDictionary(parameters);
            int i = 0;
            var arr = new object[dict.Count];
            foreach (var one_kvp in dict) {
                var param = tmp_cmd.CreateParameter();
                param.ParameterName = one_kvp.Key;
                if (one_kvp.Value == null) {
                    param.Value = DBNull.Value;
                } else {
                    param.Value = one_kvp.Value;
                }
                arr[i] = param;
                i++;
            }
            return database.SqlQuery<T>(sql, arr);
        }
    }
    private static IDictionary<string, object> ToDictionary(object data) {
        var attr = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance;
        var dict = new Dictionary<string, object>();
        foreach (var property in data.GetType().GetProperties(attr)) {
            if (property.CanRead) {
                dict.Add(property.Name, property.GetValue(data, null));
            }
        }
        return dict;
    }
}

Pemakaian:

var names = db.Database.SqlQueryPrm<string>("select name from position_category where id_key=@id_key", new { id_key = "mgr" }).ToList();
Neco
sumber
7
Setiap kesempatan Anda dapat menjelaskan kode ini dan mengapa ini merupakan jawaban atas pertanyaan yang diajukan, sehingga mereka yang datang dan menemukan ini nanti dapat memahaminya?
Andrew Barber
1
Masalah dengan sintaks {0} yang Anda kehilangan keterbacaan - saya pribadi tidak terlalu menyukainya. Masalah dengan meneruskan SqlParameters yang Anda butuhkan untuk menentukan implementasi konkret DbParameter (SqlParameter, OracleParameter dll.). Contoh yang diberikan memungkinkan Anda untuk menghindari masalah ini.
Neco
3
Saya kira itu pendapat yang valid, tetapi Anda belum menjawab pertanyaan yang sebenarnya ditanyakan di sini; Cara meneruskan parameter ke ExecuteSqlCommand()Anda harus yakin untuk menjawab pertanyaan spesifik yang ditanyakan ketika Anda memposting jawaban.
Andrew Barber
3
Diturunkan karena tidak perlu rumit (misalnya menggunakan refleksi, menciptakan kembali roda, gagal untuk memperhitungkan penyedia database yang berbeda)
sebuah phu
Terpilih karena menunjukkan salah satu solusi yang mungkin. Saya yakin ExecuteSqlCommand menerima parameter dengan cara yang sama dengan SqlQuery. Saya juga menyukai gaya Dapper.net untuk melewatkan parameter.
Zar Shardan
0

Banyak params dalam prosedur tersimpan yang memiliki banyak params di vb:

Dim result= db.Database.ExecuteSqlCommand("StoredProcedureName @a,@b,@c,@d,@e", a, b, c, d, e)
Dani
sumber
0

Prosedur tersimpan dapat dijalankan seperti di bawah ini

 string cmd = Constants.StoredProcs.usp_AddRoles.ToString() + " @userId, @roleIdList";
                        int result = db.Database
                                       .ExecuteSqlCommand
                                       (
                                          cmd,
                                           new SqlParameter("@userId", userId),
                                           new SqlParameter("@roleIdList", roleId)
                                       );
Manoj Kumar Bisht
sumber
Jangan lupa untuk menggunakan System.Data.SqlClient
FlyingV
0

Untuk .NET Core 2.2, Anda dapat menggunakan FormattableStringuntuk SQL dinamis.

//Assuming this is your dynamic value and this not coming from user input
var tableName = "LogTable"; 
// let's say target date is coming from user input
var targetDate = DateTime.Now.Date.AddDays(-30);
var param = new SqlParameter("@targetDate", targetDate);  
var sql = string.Format("Delete From {0} Where CreatedDate < @targetDate", tableName);
var froamttedSql = FormattableStringFactory.Create(sql, param);
_db.Database.ExecuteSqlCommand(froamttedSql);
Pemutus arus
sumber
Jangan lupa untuk menggunakan System.Data.SqlClient
FlyingV