Làm cách nào để truyền tham số cho phương thức DbContext.Database.ExecuteSqlCommand?


222

Hãy giả sử rằng tôi có một nhu cầu hợp lệ để thực hiện trực tiếp một lệnh sql trong Entity Framework. Tôi gặp khó khăn khi tìm hiểu làm thế nào để sử dụng các tham số trong câu lệnh sql của tôi. Ví dụ sau (không phải ví dụ thực tế của tôi) không hoạt động.

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

Phương thức ExecuteSqlCommand không cho phép bạn truyền các tham số đã đặt tên như trong ADO.Net và tài liệu cho phương thức này không đưa ra bất kỳ ví dụ nào về cách thực hiện truy vấn được tham số hóa.

Làm thế nào để tôi xác định các tham số chính xác?

Câu trả lời:


294

Thử cái này:

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

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

2
Đây thực sự nên là câu trả lời được đánh dấu chính xác, câu trả lời ở trên dễ bị tấn công và không phải là thực tiễn tốt nhất.
Tối thiểu

8
@Min, câu trả lời được chấp nhận không dễ bị tấn công hơn câu trả lời này. Có thể bạn nghĩ rằng nó đang sử dụng chuỗi.Format - không phải vậy.
Simon MᶜKenzie

1
Điều này nên được đánh dấu là câu trả lời! Đây là câu trả lời đúng và duy nhất vì nó hoạt động với DateTime.
Sven

219

Hóa ra là cái này hoạt động.

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

12
Điều này sẽ hoạt động, nhưng cơ chế này cho phép SQL SQL và cũng ngăn cơ sở dữ liệu sử dụng lại một kế hoạch thực hiện khi câu lệnh xuất hiện lại nhưng với các giá trị khác nhau.
Greg Biles

95
@GregB Tôi không nghĩ bạn đúng ở đây. Tôi đã kiểm tra rằng nó sẽ không cho phép tôi, ví dụ, chấm dứt một chuỗi ký tự sớm. Hơn nữa, tôi đã xem mã nguồn và thấy rằng nó đang sử dụng DbCommand.CreateParameter để bọc bất kỳ giá trị thô nào thành tham số. Vì vậy, không có SQL tiêm, và một lời gọi phương thức cô đọng tốt đẹp.
Josh Gallagher

7
@JoshGallagher Vâng, bạn nói đúng. Tôi đã nghĩ về một kịch bản String.Format kết hợp điều này với nhau.
Greg Biles

6
Nó KHÔNG hoạt động như SQL Server 2008 R2. Bạn sẽ có @ p0, @ p2, ..., @pN thay vì các tham số bạn đã truyền. Sử dụng SqlParameter("@paramName", value)thay thế.
Arnthor

Không thể tin rằng không ai đã đề cập đến tác động hiệu năng của truy vấn này nếu các cột trong bảng cơ sở dữ liệu được chỉ định là varar ANSI. Chuỗi .Net là unicode có nghĩa là các tham số sẽ được truyền đến máy chủ dưới dạng nvarchar. Điều này sẽ gây ra vấn đề hiệu suất đáng kể vì lớp dữ liệu phải thực hiện dịch dữ liệu. Bạn nên tuân theo cách tiếp cận của SqlParameter và chỉ định các kiểu dữ liệu.
akd

68

Bạn có thể:

1) Truyền các đối số thô và sử dụng cú pháp {0}. Ví dụ:

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

2) Truyền các đối số lớp con DbParameter và sử dụng cú pháp @ParamName.

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

Nếu bạn sử dụng cú pháp đầu tiên, EF thực sự sẽ bao bọc các đối số của bạn bằng các lớp DbParamater, gán tên cho chúng và thay thế {0} bằng tên tham số được tạo.

Cú pháp đầu tiên nếu được ưa thích vì bạn không cần sử dụng nhà máy hoặc biết loại DbParamaters nào sẽ tạo (SqlParameter, OracleParamter, v.v.).


6
Upvote để đề cập rằng cú pháp {0} là bất khả tri cơ sở dữ liệu. "... bạn không [sic] cần sử dụng nhà máy hoặc biết loại DbParamaters [sic] nào để tạo ..."
Makotosan 17/03/2015

Kịch bản 1 không được ủng hộ cho phiên bản nội suy. Tương đương bây giờ là: DbContext.Database.ExecuteSqlInterpiated ($ "StoredProcedureName {paramName}");
ScottB

20

Các câu trả lời khác không hoạt động khi sử dụng Oracle. Bạn cần sử dụng :thay vì @.

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

context.Database.ExecuteSqlCommand(
   sql,
   new OracleParameter(":FirstName", firstName), 
   new OracleParameter(":Id", id));

Cảm ơn chúa không ai sử dụng Oracle. Cũng không tự nguyện! EDIT: Xin lỗi vì trò đùa muộn! EDIT: Xin lỗi cho trò đùa xấu!
Chris Bordeman

18

Hãy thử điều này (chỉnh sửa):

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

Ý tưởng trước đó đã sai.


Khi tôi làm điều đó, tôi nhận được lỗi sau: "Không có ánh xạ tồn tại từ loại đối tượng System.Data.Objects.ObjectParameter đến một kiểu bản địa của nhà cung cấp được quản lý đã biết."
jessegavin

Xin lỗi, là lỗi của tôi. Sử dụng tham số DbPar.
Ladislav Mrnka

7
DbParameter là trừu tượng. Bạn sẽ phải sử dụng SqlParameter hoặc sử dụng DbFactory để tạo DbParameter.
jrummell

12

Đối với thực thể Framework Core 2.0 trở lên, cách chính xác để thực hiện việc này là:

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

Lưu ý rằng Entity Framework sẽ tạo ra hai tham số cho bạn, vì vậy bạn được bảo vệ khỏi Sql Injection.

Cũng lưu ý rằng nó KHÔNG:

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

bởi vì điều này KHÔNG bảo vệ bạn khỏi Sql Injection và không có tham số nào được tạo ra.

Xem điều này để biết thêm.


3
Tôi yêu .NET Core 2.0 rất nhiều, nó mang lại cho tôi những giọt nước mắt của niềm vui: ')
Joshua Kemmerer

Tôi có thể thấy sự nhiệt tình của một số người vì .NET Core 2.0 là một chuyến đi mượt mà hơn đối với tôi cho đến nay.
Paul Carlton

Làm thế nào là người đầu tiên không dễ bị SQL tiêm? Cả hai đều sử dụng phép nội suy chuỗi C #. Người đầu tiên sẽ không thể ưu tiên mở rộng chuỗi, theo như tôi biết. Tôi nghi ngờ bạn có nghĩa làctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaken 20/8/19

@CodeNaken, Không, tôi không có ý đó. EF nhận thức được vấn đề và tạo hai tham số thực tế để bảo vệ bạn. Vì vậy, nó không chỉ là một chuỗi được truyền qua. Xem liên kết ở trên để biết chi tiết. Nếu bạn thử nó trong VS, phiên bản của tôi sẽ không đưa ra cảnh báo về Sql Injection và phiên bản khác sẽ.
Greg Gum

1
@GregGum - TIL về FormattableString. Bạn nói đúng và điều đó thật tuyệt!
CodeNakes

4

Phiên bản đơn giản hóa cho Oracle. Nếu bạn không muốn tạo OracleParameter

var sql = "Update [User] SET FirstName = :p0 WHERE Id = :p1";
context.Database.ExecuteSqlCommand(sql, firstName, id);

2
Trong SQL Server, tôi sử dụng @ p0 thay vì: 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 });

Điều này thật đơn giản !!!

Hình ảnh để biết tham chiếu tham số

nhập mô tả hình ảnh ở đây


2

Đối với Phương thức async ("ExecuteSqlCommandAsync"), bạn có thể sử dụng phương thức này như sau:

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)
    });

Đây không phải là bất khả tri, nó sẽ chỉ hoạt động cho MS-SQL Server. Nó sẽ thất bại cho Oracle hoặc PG.
Ngày

1

Nếu các kiểu dữ liệu cơ sở dữ liệu cơ bản của bạn là varchar thì bạn nên tuân theo cách tiếp cận bên dưới. Nếu không, truy vấn sẽ có tác động hiệu suất rất lớn.

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);

Bạn có thể kiểm tra hồ sơ Sql để thấy sự khác biệt.


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;
    }
}

Sử dụng:

var names = db.Database.SqlQueryPrm<string>("select name from position_category where id_key=@id_key", new { id_key = "mgr" }).ToList();

7
Bất kỳ cơ hội nào bạn có thể giải thích mã này và tại sao nó là một câu trả lời cho câu hỏi được đặt ra, để những người đến và tìm thấy điều này sau đó có thể hiểu nó?
Andrew Barber

1
Vấn đề với cú pháp {0} khiến bạn mất khả năng đọc - cá nhân tôi không thực sự thích nó. Vấn đề với việc chuyển SqlParameter mà bạn cần chỉ định triển khai cụ thể DbParameter (SqlParameter, OracleParameter, v.v.). Ví dụ được cung cấp cho phép bạn tránh những vấn đề này.
Neco

3
Tôi đoán đó là một ý kiến ​​hợp lệ, nhưng bạn chưa trả lời câu hỏi thực sự được hỏi ở đây; Làm thế nào để truyền tham số cho ExecuteSqlCommand()Bạn nên chắc chắn trả lời câu hỏi cụ thể được hỏi khi bạn đăng câu trả lời.
Andrew Barber

3
Bị từ chối vì nó phức tạp không cần thiết (ví dụ: sử dụng sự phản chiếu, phát minh lại bánh xe, không tính đến các nhà cung cấp cơ sở dữ liệu khác nhau)
một phu phu

Được bình chọn vì nó cho thấy một trong những giải pháp khả thi. Tôi chắc chắn ExecuteSqlCommand chấp nhận các tham số giống như cách SqlQuery làm. Tôi cũng thích kiểu truyền các tham số của Dapper.net.
Zar Shardan

0

Nhiều tham số trong một thủ tục được lưu trữ có nhiều tham số trong vb:

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

0

Thủ tục lưu trữ có thể được thực hiện như dưới đây

 string cmd = Constants.StoredProcs.usp_AddRoles.ToString() + " @userId, @roleIdList";
                        int result = db.Database
                                       .ExecuteSqlCommand
                                       (
                                          cmd,
                                           new SqlParameter("@userId", userId),
                                           new SqlParameter("@roleIdList", roleId)
                                       );

Đừng quên sử dụng System.Data.SqlClient
FlyingV

0

Đối với .NET Core 2.2, bạn có thể sử dụng FormattableStringcho SQL động.

//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);

Đừng quên sử dụng System.Data.SqlClient
FlyingV
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.