SqlParameterはすでに別のSqlParameterCollectionに含まれています-using(){}はごまかしますか?


85

using() {}以下に示すように(sic)ブロックを使用し、それcmd1が最初のusing() {}ブロックの範囲を超えて存在しないと仮定しているときに、2番目のブロックがメッセージとともに例外をスローする必要があるのはなぜですか

SqlParameterは、別のSqlParameterCollectionにすでに含まれています

ブロックの最後で破棄されたときに、SqlParameterCollectionアタッチされcmd1ているリソースやハンドル(パラメーター()を含む)が解放されないことを意味しますか?

using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Test;Integrated Security=True"))
{
    var parameters = new SqlParameter[] { new SqlParameter("@ProductId", SqlDbType.Int ) };

    using(var cmd1 = new SqlCommand("SELECT ProductName FROM Products WHERE ProductId = @ProductId"))
    {
        foreach (var parameter in parameters)
        {
            cmd1.Parameters.Add(parameter);                
        }
        // cmd1.Parameters.Clear(); // uncomment to save your skin!
    }

    using (var cmd2 = new SqlCommand("SELECT Review FROM ProductReviews WHERE ProductId = @ProductId"))
    {
        foreach (var parameter in parameters)
        {
            cmd2.Parameters.Add(parameter);
        }
    }
}

注:最初のusing(){}ブロックの最後の括弧の直前にcmd1.Parameters.Clear()を実行すると、例外(および起こりうる恥ずかしさ)からあなたを救うことができます。

再現する必要がある場合は、次のスクリプトを使用してオブジェクトを作成できます。

CREATE TABLE Products
(
    ProductId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    ProductName nvarchar(32) NOT NULL
)
GO

CREATE TABLE ProductReviews
(
    ReviewId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    ProductId int NOT NULL,
    Review nvarchar(128) NOT NULL
)
GO

私もこれを見ていますが、その修正は機能しませんでした。イライラする。そして、私は単一のcmdオブジェクトのみを使用しており、再利用されていません。非同期再試行ループにラップされているため、おそらく同じ根本原因であり、同じ方法で回避されていない可能性があります。
エドウィリアムズ

回答:


109

SqlParameterどのコマンドが含まれているのかを「知っている」のではないかと思います。その情報は、コマンドが破棄されたときにクリアされません、を呼び出すクリアされますcommand.Parameters.Clear()

個人的には、そもそもオブジェクトの再利用は避けたいと思いますが、それはあなた次第です:)


1
ありがとう。そうだと思いました。それはまた、SqlParameterがそれ自体を破棄されたオブジェクトに関連付けていることを意味しますが、それが良いことかどうかは
わかり

@JohnGathogo:ええと、関連付けが形成された後に破棄されるオブジェクトに関連付けられています。確かに理想的ではありません。
Jon Skeet

11
他の人へのメモ。Clear最初のusingブロックを終了する前に実行する必要がありました。2番目のusingブロックに入るときにそれを実行しても、このエラーがスローされました。
snekse 2014年

9

ブロックを使用しても、オブジェクトが「破棄」されることは保証されませんDispose()。単にメソッドが呼び出されるだけです。それが実際に行うことは特定の実装次第であり、この場合、それは明らかにコレクションを空にしません。アイデアは、ガベージコレクターによってクリーンアップされない管理されていないリソースが正しく廃棄されるようにすることです。Parametersコレクションはアンマネージリソースではないため、disposeメソッドによってクリアされないことを完全に驚くわけではありません。


7

cmd.Parameters.Clear();を追加します。実行後は問題ないはずです。


3

usingスコープを定義し、Dispose()それが大好きなの自動呼び出しを行います。

スコープから外れた参照は、別のオブジェクトがその参照を持っている場合、オブジェクト自体を「消滅」させることはありません。この場合はparameters、への参照がありcmd1ます。


2

私も同じ問題を抱えていますありがとう@Jon、私が例を挙げたことに基づいて。

2回同じsqlparameterが渡された以下の関数を呼び出したとき。最初のデータベース呼び出しでは正しく呼び出されましたが、2回目では上記のエラーが発生しました。

    public Claim GetClaim(long ClaimId)
    {
        string command = "SELECT * FROM tblClaim "
            + " WHERE RecordStatus = 1 and ClaimId = @ClaimId and ClientId =@ClientId";
        List<SqlParameter> objLSP_Proc = new List<SqlParameter>(){
                new SqlParameter("@ClientId", SessionModel.ClientId),
                new SqlParameter("@ClaimId", ClaimId)
            };

        DataTable dt = GetDataTable(command, objLSP_Proc);
        if (dt.Rows.Count == 0)
        {
            return null;
        }

        List<Claim> list = TableToList(dt);

        command = "SELECT * FROM tblClaimAttachment WHERE RecordStatus = 1 and ClaimId = @ClaimId and ClientId =@ClientId";

        DataTable dt = GetDataTable(command, objLSP_Proc); //gives error here, after add `sqlComm.Parameters.Clear();` in GetDataTable (below) function, the error resolved.


        retClaim.Attachments = new ClaimAttachs().SelectMany(command, objLSP_Proc);
        return retClaim;
    }

これは一般的なDAL機能です

       public DataTable GetDataTable(string strSql, List<SqlParameter> parameters)
        {
            DataTable dt = new DataTable();
            try
            {
                using (SqlConnection connection = this.GetConnection())
                {
                    SqlCommand sqlComm = new SqlCommand(strSql, connection);

                    if (parameters != null && parameters.Count > 0)
                    {
                        sqlComm.Parameters.AddRange(parameters.ToArray());
                    }

                    using (SqlDataAdapter da = new SqlDataAdapter())
                    {
                        da.SelectCommand = sqlComm;
                        da.Fill(dt);
                    }
                    sqlComm.Parameters.Clear(); //this added and error resolved
                }
            }
            catch (Exception ex)
            {                   
                throw;
            }
            return dt;
        }

2

プロシージャを複数回呼び出すためにSqlParameterコレクションの一部として同じSqlParameterオブジェクトを使用していたため、この特定のエラーに直面しました。このエラーIMHOの理由は、SqlParameterオブジェクトが特定のSqlParameterコレクションに関連付けられており、同じSqlParameterオブジェクトを使用して新しいSqlParameterコレクションを作成できないためです。

したがって、これの代わりに:

var param1 = new SqlParameter{ DbType = DbType.String, ParameterName = param1,Direction = ParameterDirection.Input , Value = "" };
var param2 = new SqlParameter{ DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input , Value = 100};

SqlParameter[] sqlParameter1 = new[] { param1, param2 };

ExecuteProc(sp_name, sqlParameter1);

/*ERROR : 
SqlParameter[] sqlParameter2 = new[] { param1, param2 };
ExecuteProc(sp_name, sqlParameter2);
*/ 

これを行う:

var param3 = new SqlParameter{ DbType = DbType.String, ParameterName = param1, Direction = ParameterDirection.Input , Value = param1.Value };
var param4 = new SqlParameter{ DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input , Value = param2.Value};

SqlParameter[] sqlParameter3 = new[] { param3, param4 };

ExecuteProc(sp_name, sqlParameter3);

0

パラメータオブジェクトのインスタンス化に失敗したため、この例外が発生しました。同じ名前のパラメータを持つ2つのプロシージャについて不平を言っていると思いました。同じパラメータが2回追加されていることに不満がありました。

            Dim aParm As New SqlParameter()
            aParm.ParameterName = "NAR_ID" : aParm.Value = hfCurrentNAR_ID.Value
            m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
            aParm = New SqlParameter
            Dim tbxDriveFile As TextBox = gvNetworkFileAccess.Rows(index).FindControl("tbxDriveFolderFile")
            aParm.ParameterName = "DriveFolderFile" : aParm.Value = tbxDriveFile.Text
            m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
            **aParm = New SqlParameter()**  <--This line was missing.
            Dim aDDL As DropDownList = gvNetworkFileAccess.Rows(index).FindControl("ddlFileAccess")
            aParm.ParameterName = "AccessGranted" : aParm.Value = aDDL.Text
            **m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)** <-- The error occurred here.

0

問題
この問題が発生したときに、C#からSQLServerストアドプロシージャを実行していました。

例外メッセージ[SqlParameterはすでに別のSqlParameterCollectionに含まれています。]

原因
ストアドプロシージャに3つのパラメータを渡していた。追加しました

param = command.CreateParameter();

全部で一度だけ。パラメータごとにこの行を追加する必要がありました。これは、合計で3回を意味します。

DbCommand command = CreateCommand(ct.SourceServer, ct.SourceInstance, ct.SourceDatabase);
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[ETL].[pGenerateScriptToCreateIndex]";

DbParameter param = command.CreateParameter();
param.ParameterName = "@IndexTypeID";
param.DbType = DbType.Int16;
param.Value = 1;
command.Parameters.Add(param);

param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "@SchemaName";
param.DbType = DbType.String;
param.Value = ct.SourceSchema;
command.Parameters.Add(param);

param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "@TableName";
param.DbType = DbType.String;
param.Value = ct.SourceDataObjectName;
command.Parameters.Add(param);

dt = ExecuteSelectCommand(command);

解決策
各パラメーターに次のコード行を追加します

param = command.CreateParameter();

0

これが私がやった方法です!

        ILease lease = (ILease)_SqlParameterCollection.InitializeLifetimeService();
        if (lease.CurrentState == LeaseState.Initial)
        {
            lease.InitialLeaseTime = TimeSpan.FromMinutes(5);
            lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
            lease.RenewOnCallTime = TimeSpan.FromMinutes(2);
            lease.Renew(new TimeSpan(0, 5, 0));
        }

0

EntityFrameworkを使用している場合

私もこれと同じ例外がありました。私の場合、EntityFrameworkDBContextを介してSQLを呼び出していました。以下は私のコードとそれを修正する方法です。

壊れたコード

string sql = "UserReport @userID, @startDate, @endDate";

var sqlParams = new Object[]
{
    new SqlParameter { ParameterName= "@userID", Value = p.UserID, SqlDbType = SqlDbType.Int, IsNullable = true }
    ,new SqlParameter { ParameterName= "@startDate", Value = p.StartDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
    ,new SqlParameter { ParameterName= "@endDate", Value = p.EndDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
};

IEnumerable<T> rows = ctx.Database.SqlQuery<T>(sql,parameters);

foreach(var row in rows) {
    // do something
}

// the following call to .Count() is what triggers the exception
if (rows.Count() == 0) {
    // tell user there are no rows
}

注:上記の呼び出しがためにSqlQuery<T>()実際に返されDbRawSqlQuery<T>、その実装IEnumerable

.Count()を呼び出すと例外がスローされるのはなぜですか?

確認のためにSQLプロファイラーを起動していませんが.Count()、SQL Serverへの別の呼び出しがトリガーされていると思われ、内部的に同じSQLCommandオブジェクトを再利用して、重複するパラメーターを再度追加しようとしています。

ソリューション/作業コード

内にカウンターを追加したforeachので、電話をかけなくても行数を維持できます.Count()

int rowCount = 0;

foreach(var row in rows) {
    rowCount++
    // do something
}

if (rowCount == 0) {
    // tell user there are no rows
}

あとは

私のプロジェクトはおそらく古いバージョンのEFを使用しています。新しいバージョンでは、パラメータをクリアするか、SqlCommandオブジェクトを破棄することで、この内部バグが修正されている可能性があります。

あるいは、.Count()を繰り返した後に呼び出さないように開発者に指示する明示的な指示がDbRawSqlQueryあり、私はそれを間違ってコーディングしています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.