วิธีการส่งผ่านพารามิเตอร์ไปยังเมธอด DbContext.Database.ExecuteSqlCommand?


222

สมมติว่าฉันมีความต้องการที่ถูกต้องสำหรับการรันคำสั่ง sql ใน Entity Framework โดยตรง ฉันมีปัญหาในการหาวิธีใช้พารามิเตอร์ในคำสั่ง sql ของฉัน ตัวอย่างต่อไปนี้ (ไม่ใช่ตัวอย่างจริงของฉัน) ไม่ทำงาน

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

กระบวนการ ExecuteSqlCommand วิธีการไม่อนุญาตให้คุณส่งผ่านพารามิเตอร์ที่มีชื่อเช่นใน ADO.Net และเอกสารประกอบสำหรับวิธีนี้ไม่ได้ให้ตัวอย่างใด ๆ เกี่ยวกับวิธีการเรียกใช้แบบสอบถามแบบใช้พารามิเตอร์

ฉันจะระบุพารามิเตอร์อย่างถูกต้องได้อย่างไร

คำตอบ:


295

ลองสิ่งนี้:

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

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

2
นี่ควรเป็นคำตอบที่ถูกต้องจริง ๆ ข้างต้นมีแนวโน้มที่จะถูกโจมตีและไม่ใช่วิธีปฏิบัติที่ดีที่สุด
ต่ำสุด

8
@ นาทีคำตอบที่ยอมรับนั้นไม่มีแนวโน้มที่จะถูกโจมตีมากกว่าคำตอบนี้ บางทีคุณคิดว่ามันกำลังใช้สตริงจัดรูปแบบ - ไม่ใช่
Simon MᶜKenzie

1
ควรทำเครื่องหมายเป็นคำตอบ! นี่เป็นคำตอบเดียวที่ถูกต้องเพราะใช้ได้กับ DateTime
Sven

219

ปรากฎว่ามันใช้งานได้

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

13
สิ่งนี้จะใช้งานได้ แต่กลไกนี้อนุญาตให้ใช้การฉีด SQL และยังป้องกันฐานข้อมูลจากการนำแผนปฏิบัติการกลับมาใช้ใหม่เมื่อคำสั่งแสดงขึ้นอีกครั้ง แต่มีค่าต่างกัน
Greg Biles

95
@GregB ฉันไม่คิดว่าคุณถูกต้องที่นี่ ฉันได้ทดสอบแล้วว่ามันจะไม่ยอมให้ฉันยุติสตริงตัวอักษรก่อน ยิ่งกว่านั้นฉันดูที่ซอร์สโค้ดและพบว่าใช้ DbCommand.CreateParameter เพื่อตัดค่า raw ใด ๆ ลงในพารามิเตอร์ ดังนั้นไม่มีการฉีด SQL และการเรียกใช้เมธอดรวบยอดที่ดี
Josh Gallagher

7
@JoshGallagher ใช่คุณพูดถูก ฉันกำลังคิดถึงสตริงจัดรูปแบบการรวมสิ่งนี้เข้าด้วยกัน
Greg Biles

6
มันไม่ทำงานเหมือน SQL Server 2008 R2 คุณจะมี @ p0, @ p2, ... , @pN แทนที่จะเป็นพารามิเตอร์ที่คุณผ่าน ใช้SqlParameter("@paramName", value)แทน
Arnthor

ไม่อยากเชื่อเลยว่าจะไม่มีใครพูดถึงผลกระทบด้านประสิทธิภาพของการสืบค้นนี้หากระบุคอลัมน์ตารางฐานข้อมูลเป็น ANSI varchar สตริง. Net เป็น Unicode หมายความว่าพารามิเตอร์จะถูกส่งผ่านไปยังเซิร์ฟเวอร์เป็น nvarchar สิ่งนี้จะทำให้เกิดปัญหาประสิทธิภาพที่สำคัญเนื่องจากชั้นข้อมูลต้องทำการแปลข้อมูล คุณควรใช้แนวทางของ SqlParameter และระบุประเภทข้อมูล
AKD

68

คุณสามารถ:

1) ส่งผ่านอากิวเมนต์ raw และใช้ไวยากรณ์ {0} เช่น:

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

2) ผ่านอาร์กิวเมนต์ย่อย DbParameter และใช้ไวยากรณ์ @ParamName

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

หากคุณใช้ไวยากรณ์แรก EF จะสรุปอาร์กิวเมนต์ของคุณด้วยคลาส DbParamater กำหนดชื่อและแทนที่ {0} ด้วยชื่อพารามิเตอร์ที่สร้างขึ้น

ไวยากรณ์แรกถ้าต้องการเนื่องจากคุณไม่จำเป็นต้องใช้โรงงานหรือทราบประเภทของ DbParamaters ในการสร้าง (SqlParameter, OracleParamter เป็นต้น)


6
มีการลงทะเบียนเนื่องจากมีการกล่าวถึงว่า {0} ไวยากรณ์เป็นผู้ไม่เชื่อเรื่องพระเจ้าฐานข้อมูล "... คุณไม่จำเป็นต้องใช้โรงงานหรือรู้ว่า DbParamaters ประเภทใดในการสร้าง ... "
Makotosan

สถานการณ์ที่ 1 ถูกคัดค้านรุ่นที่แก้ไขแล้ว เทียบเท่าตอนนี้: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB

20

คำตอบอื่น ๆ ไม่ทำงานเมื่อใช้ Oracle คุณจำเป็นต้องใช้แทน:@

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

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

ขอบคุณพระเจ้าที่ไม่มีใครใช้ Oracle ไม่สมัครใจ! แก้ไข: ขอโทษสำหรับเรื่องตลกปลาย! แก้ไข: ขอโทษสำหรับเรื่องตลกที่ไม่ดี!
Chris Bordeman

18

ลองนี้ (แก้ไข):

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

แนวคิดก่อนหน้านี้ผิด


เมื่อฉันทำเช่นนั้นฉันจะได้รับข้อผิดพลาดต่อไปนี้: "ไม่มีการแมปจากวัตถุชนิด System.Data.Objects.ObjectParameter ไปเป็นชนิดเนทีฟของผู้ให้บริการที่มีการจัดการที่รู้จัก"
jessegavin

ขออภัยความผิดพลาดของฉัน ใช้ DbParameter
Ladislav Mrnka

7
DbParameter เป็นนามธรรม คุณจะต้องใช้ SqlParameter หรือใช้ DbFactory เพื่อสร้าง DbParameter
jrummell

12

สำหรับเอนทิตี Framework Core 2.0 หรือสูงกว่าวิธีที่ถูกต้องคือ:

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

โปรดสังเกตว่า Entity Framework จะสร้างพารามิเตอร์ทั้งสองให้คุณดังนั้นคุณจะได้รับการปกป้องจากการฉีด SQL

โปรดทราบว่าไม่ใช่:

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

เพราะสิ่งนี้ไม่ได้ป้องกันคุณจากการฉีด SQL และไม่มีการผลิตพารามิเตอร์

ดูสิ่งนี้เพิ่มเติม


3
ฉันรัก. NET Core 2.0 มากมันทำให้ฉันมีความสุข: ')
Joshua Kemmerer

ฉันเห็นความกระตือรือร้นของคนบางคนตั้งแต่. NET Core 2.0 เป็นเกมที่ราบรื่นสำหรับฉัน
Paul Carlton

คนแรกไม่เสี่ยงต่อการฉีด SQL อย่างไร ทั้งสองใช้การแก้ไขสตริง C # คนแรกจะไม่สามารถจองส่วนขยายของสตริงได้เท่าที่ฉันรู้ ฉันสงสัยว่าคุณหมายถึงว่าจะเป็นctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked

@CodeNaked, ไม่ฉันไม่ได้หมายความว่าอย่างนั้น EF ตระหนักถึงปัญหาและสร้างพารามิเตอร์จริงสองประการเพื่อปกป้องคุณ ดังนั้นจึงไม่ใช่แค่สตริงที่ส่งผ่าน ดูลิงค์ด้านบนเพื่อดูรายละเอียด หากคุณลองใช้ใน VS เวอร์ชันของฉันจะไม่ออกคำเตือนเกี่ยวกับ SQL Injection และอีกอันจะ
Greg Gum

1
@GregGum - TIL FormattableStringเกี่ยวกับ คุณพูดถูกและเยี่ยมมาก!
CodeNaked

4

รุ่นประยุกต์สำหรับ Oracle หากคุณไม่ต้องการสร้าง OracleParameter

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

2
ใน SQL Server ฉันใช้ @ p0 แทน: 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 });

มันง่ายมาก !!!

รูปภาพสำหรับรู้การอ้างอิงพารามิเตอร์

ป้อนคำอธิบายรูปภาพที่นี่


2

สำหรับวิธีการ async ("ExecuteSqlCommandAsync") คุณสามารถใช้มันได้เช่นนี้:

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

นี่ไม่ใช่ db-agnostic แต่จะใช้ได้กับ MS-SQL Server เท่านั้น มันจะล้มเหลวสำหรับ Oracle หรือ PG
Aneves

1

หากประเภทข้อมูลฐานข้อมูลพื้นฐานของคุณเป็น varchar คุณควรปฏิบัติตามแนวทางด้านล่าง มิฉะนั้นข้อความค้นหาจะมีผลกระทบต่อประสิทธิภาพอย่างมาก

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

คุณสามารถตรวจสอบ SQL Profiler เพื่อดูความแตกต่าง


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

การใช้งาน:

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

7
โอกาสใดที่คุณสามารถอธิบายรหัสนี้ได้และทำไมจึงเป็นคำตอบสำหรับคำถามที่โพสต์ไว้เพื่อให้ผู้ที่มาพบในภายหลังสามารถเข้าใจได้
Andrew Barber

1
ปัญหาเกี่ยวกับไวยากรณ์ {0} ที่คุณอ่านไม่ออก - ฉันเองไม่ชอบมันจริงๆ ปัญหาเกี่ยวกับการส่ง SqlParameters ที่คุณต้องการระบุการใช้งาน DbParameter (SqlParameter, OracleParameter ฯลฯ ) อย่างเป็นรูปธรรม ตัวอย่างที่ให้ไว้ช่วยให้คุณสามารถหลีกเลี่ยงปัญหาเหล่านี้ได้
Neco

3
ฉันเดาว่ามันเป็นความเห็นที่ถูกต้อง แต่คุณยังไม่ได้ตอบคำถามที่ถูกถามจริง ๆที่นี่; วิธีส่งพารามิเตอร์ให้ExecuteSqlCommand()คุณควรแน่ใจว่าได้ตอบคำถามเฉพาะที่ถูกถามเมื่อคุณโพสต์คำตอบ
Andrew Barber

3
ลงเนื่องจากมีความซับซ้อนโดยไม่จำเป็น (เช่นใช้การไตร่ตรอง, การสร้างวงล้อใหม่, ล้มเหลวในการบัญชีสำหรับผู้ให้บริการฐานข้อมูลที่แตกต่างกัน)
phu

โหวตขึ้นเพราะมันแสดงวิธีแก้ปัญหาอย่างใดอย่างหนึ่งที่เป็นไปได้ ฉันแน่ใจว่า ExecuteSqlCommand ยอมรับพารามิเตอร์แบบเดียวกับที่ SqlQuery ทำฉันชอบสไตล์ Dapper.net ในการส่งผ่านพารามิเตอร์
Zar Shardan

0

หลายพารามิเตอร์ในกระบวนงานที่เก็บไว้ซึ่งมีหลายพารามิเตอร์ใน vb:

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

0

ขั้นตอนการจัดเก็บสามารถดำเนินการดังต่อไปนี้

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

อย่าลืมใช้ System.Data.SqlClient
FlyingV

0

สำหรับ. NET Core 2.2 คุณสามารถใช้FormattableStringสำหรับ SQL แบบไดนามิก

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

อย่าลืมใช้ System.Data.SqlClient
FlyingV
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.