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
これは、正解としてマークされた答えであるはずです。上記のものは攻撃を受けやすく、ベストプラクティスではありません。
ミン

8
@Min、受け入れられた回答は、この回答よりも攻撃を受けにくい傾向があります。たぶん、あなたはそれがstring.Formatを使用していると思ったでしょう-そうではありません。
SimonMᶜKenzie2014年

1
これは回答としてマークする必要があります!これは、DateTimeで動作するため、唯一正しい答えです。
2015年

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、2011

6
SQL Server 2008 R2以降は機能しません。渡したパラメータの代わりに、@ p0、@ p2、...、@ pNを使用します。SqlParameter("@paramName", value)代わりに使用してください。
Arnthor 2014年

データベーステーブルの列がANSI varcharとして指定されている場合、このクエリのパフォーマンスへの影響について誰も言及していないとは信じられません。.Net文字列はUnicodeであり、パラメーターはnvarcharとしてサーバーに渡されます。データレイヤーはデータ変換を実行する必要があるため、これは重大なパフォーマンスの問題を引き起こします。SqlParameterのアプローチに固執し、データ型を指定する必要があります。
akd 2017年

68

次のいずれかを行うことができます。

1)生の引数を渡して、{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 [sic]のタイプを知ったりする必要はありません...」
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を使用していない神に感謝します。まあ自発的ではありません!編集:後半のジョークの謝罪!編集:悪い冗談をお詫びします!
クリスボーデマン

18

これを試してください(編集):

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

以前の考えは間違っていました。


これを行うと、「オブジェクトタイプSystem.Data.Objects.ObjectParameterから既知のマネージプロバイダーネイティブタイプへのマッピングが存在しません。」というエラーが発生します。
jessegavin

すみません、私の間違いです。DbParameterを使用します。
Ladislav Mrnka、2011年

7
DbParameterは抽象です。DbParameterを作成するには、SqlParameterを使用するか、DbFactoryを使用する必要があります。
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は2つのパラメーターを生成するため、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はこれまで私にとってスムーズな乗り心地だったので、一部の人々の熱意を見ることができます。
ポールカールトン

最初のものはSQLインジェクションに対して脆弱ではないのですか?どちらもC#文字列補間を使用します。最初の方法では、私の知る限り、文字列の展開を先取りすることはできません。私はあなたがすることを意味疑いctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked

@CodeNaked、いや、そういう意味ではなかった。EFはこの問題を認識しており、ユーザーを保護するために2つの実際のパラメーターを作成します。したがって、渡されるのは単なる文字列ではありません。詳細については、上記のリンクを参照してください。VSで試してみると、私のバージョンではSQLインジェクションに関する警告が表示されず、他のバージョンでは警告が表示されます。
グレッグガム

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に依存せず、MS-SQL Serverでのみ機能します。OracleまたはPGの場合は失敗します。
ANeves 2018年

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
このコードを説明し、それが提示された質問への回答である理由を説明して、後でこれを見つけた人が理解できるようにする可能性はありますか?
アンドリューバーバー

1
{0}構文に問題があるため、読みにくくなります-個人的にはあまり好きではありません。DbParameter(SqlParameter、OracleParameterなど)の具体的な実装を指定する必要があるSqlParametersを渡す際の問題。提供されている例では、これらの問題を回避できます。
Neco 2013年

3
それは妥当な意見だと思いますが、実際にここで尋ねられている質問は答えていません。パラメータを渡す方法ExecuteSqlCommand()回答を投稿するときに尋ねられる特定の質問に必ず答える必要があります。
Andrew Barber

3
不必要に複雑である(たとえば、リフレクションを使用する、ホイールを再発明する、さまざまなデータベースプロバイダーを考慮に入れない)ため
、反対票を投じた

可能な解決策の1つを示しているため、賛成票が投じられました。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.