DbContext.Database.ExecuteSqlCommand 메서드에 매개 변수를 전달하는 방법은 무엇입니까?


222

Entity Framework에서 sql 명령을 직접 실행할 필요가 있다고 가정 해 봅시다. 내 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에서와 같이 명명 된 매개 변수를 전달할 수 없으며이 메서드에 대한 설명서 에는 매개 변수가있는 쿼리를 실행하는 방법에 대한 예가 없습니다.

매개 변수를 올바르게 지정하려면 어떻게합니까?

답변:


294

이 시도:

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

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

2
이것은 실제로 정답으로 표시되어야하며, 위의 답변은 공격을 받기 쉽고 모범 사례가 아닙니다.
Min

8
@Min, 허용 된 답변은이 답변보다 더 공격에 취약하지 않습니다. 어쩌면 문자열을 사용하고 있다고 생각했을 수도 있습니다.
Simon Mᶜ 켄지

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

12
이것은 작동하지만이 메커니즘은 SQL 삽입을 허용하며 명령문이 다시 나타나지만 다른 값을 갖는 경우 데이터베이스가 실행 계획을 재사용하지 못하게합니다.
Greg Biles

95
@ GregB 나는 당신이 여기에 있다고 생각하지 않습니다. 예를 들어 문자열 리터럴을 일찍 종료 할 수 없다는 것을 테스트했습니다. 또한 소스 코드를 살펴본 후 DbCommand.CreateParameter를 사용하여 모든 원시 값을 매개 변수로 마무리한다는 것을 알았습니다. 따라서 SQL 주입이 없으며 간결한 메소드 호출이 좋습니다.
Josh Gallagher

7
@JoshGallagher 그렇습니다. 나는 문자열을 생각하고 있었다.
Greg Biles

6
SQL Server 2008 R2에서는 작동하지 않습니다. 전달한 매개 변수 대신 @ p0, @ p2, ..., @pN이 있습니다. SqlParameter("@paramName", value)대신 사용하십시오 .
Arnthor

데이터베이스 테이블 열이 ANSI varchar로 지정된 경우 아무도이 쿼리의 성능 영향을 언급하지 않았다고 믿을 수 없습니다. .Net 문자열은 유니 코드이므로 매개 변수가 nvarchar로 서버에 전달됩니다. 데이터 계층이 데이터 변환을 수행해야하므로 성능에 심각한 문제가 발생할 수 있습니다. SqlParameter의 접근 방식을 고수하고 데이터 유형을 지정해야합니다.
akd

68

다음 중 하나를 수행 할 수 있습니다.

1) 원시 인수를 전달하고 {0} 구문을 사용하십시오. 예 :

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

2) DbParameter 서브 클래스 인수를 전달하고 @ParamName 구문을 사용하십시오.

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

첫 번째 구문을 사용하는 경우 EF는 실제로 인수를 DbParamater 클래스로 랩핑하고 이름을 지정하고 {0}을 생성 된 매개 변수 이름으로 바꿉니다.

팩토리를 사용하거나 작성할 DbParamater 유형 (SqlParameter, OracleParamter 등)을 알 필요가 없기 때문에 선호하는 경우 첫 번째 구문입니다.


6
{0} 구문이 데이터베이스에 구애받지 않는다고 언급했습니다. "... 당신은 공장을 사용하거나 어떤 종류의 DbParamaters [sic]를 만들지
알아야합니다

시나리오 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

entity Framework Core 2.0 이상에서이를 수행하는 올바른 방법은 다음과 같습니다.

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

Entity Framework는 두 개의 매개 변수를 생성하므로 Sql Injection으로부터 보호됩니다.

또한 다음이 아닙니다.

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

Sql Injection으로부터 사용자를 보호하지 않으며 매개 변수가 생성되지 않기 때문입니다.

자세한 내용은 이것을 참조하십시오 .


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

비동기 메소드 ( "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 프로파일 러를 확인할 수 있습니다.


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} 구문의 문제-나는 개인적으로 그것을 좋아하지 않습니다. DbParameter (SqlParameter, OracleParameter 등)의 구체적인 구현을 지정해야하는 SqlParameters 전달 문제 제공된 예제를 사용하면 이러한 문제를 피할 수 있습니다.
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.