.net 코드에서 테이블 값 매개 변수를 스토어드 프로 시저로 전달하는 방법


171

SQL Server 2005 데이터베이스가 있습니다. 몇 가지 절차에는 저장된 proc에 nvarchar(쉼표로 구분) 전달하고 내부적으로 단일 값으로 나누는 테이블 매개 변수가 있습니다. 다음과 같이 SQL 명령 매개 변수 목록에 추가합니다.

cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";

데이터베이스를 SQL Server 2008로 마이그레이션해야합니다. 테이블 값 매개 변수가 있으며이를 저장 프로 시저에서 사용하는 방법을 알고 있습니다. 그러나 SQL 명령에서 매개 변수 목록으로 전달하는 방법을 모르겠습니다.

누구나 Parameters.Add절차 의 올바른 구문을 알고 있습니까? 아니면이 매개 변수를 전달하는 다른 방법이 있습니까?


이 솔루션을 확인하십시오. EF에 테이블 반환 매개 변수가있는 저장 프로 시저. code.msdn.microsoft.com/Stored-Procedure-with-6c194514
— Carl Prothman

이와 같은 경우에는 일반적으로 문자열을 연결하여 서버 측에서 분할하거나 여러 열이있는 경우 XML을 전달합니다. XML XML을 처리 할 때 매우 빠릅니다. 모든 방법을 시도하고 처리 시간을 확인한 후 최상의 방법을 선택할 수 있습니다. XML은 <Items> <Item value = "sdadas"/> <Item value = "sadsad"/>...</ Items>와 같습니다. Sql Server의 프로세스도 간단합니다. 이 방법을 사용하면 추가 정보가 필요한 경우 언제든지 <item>에 새 속성을 추가 할 수 있습니다.
— Nițu Alexandru

4
@ NițuAlexandru, "XML은 XML을 처리 할 때 매우 빠릅니다.". 근처에도 안.
— nothrow

답변:


279

DataTable, DbDataReader또는 IEnumerable<SqlDataRecord>개체를 사용하여 MSDN 문서 SQL Server 2008의 테이블 반환 매개 변수 (ADO.NET)에 따라 테이블 반환 매개 변수를 채울 수 있습니다 .

다음 예제는 a DataTable또는 an 사용을 보여줍니다 IEnumerable<SqlDataRecord>.

SQL 코드 :

CREATE TABLE dbo.PageView
(
    PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
    PageViewCount BIGINT NOT NULL
);
CREATE TYPE dbo.PageViewTableType AS TABLE
(
    PageViewID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.procMergePageView
    @Display dbo.PageViewTableType READONLY
AS
BEGIN
    MERGE INTO dbo.PageView AS T
    USING @Display AS S
    ON T.PageViewID = S.PageViewID
    WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
    WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END

C # 코드 :

private static void ExecuteProcedure(bool useDataTable, 
                                     string connectionString, 
                                     IEnumerable<long> ids) 
{
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    {
        connection.Open();
        using (SqlCommand command = connection.CreateCommand()) 
        {
            command.CommandText = "dbo.procMergePageView";
            command.CommandType = CommandType.StoredProcedure;

            SqlParameter parameter;
            if (useDataTable) {
                parameter = command.Parameters
                              .AddWithValue("@Display", CreateDataTable(ids));
            }
            else 
            {
                parameter = command.Parameters
                              .AddWithValue("@Display", CreateSqlDataRecords(ids));
            }
            parameter.SqlDbType = SqlDbType.Structured;
            parameter.TypeName = "dbo.PageViewTableType";

            command.ExecuteNonQuery();
        }
    }
}

private static DataTable CreateDataTable(IEnumerable<long> ids) 
{
    DataTable table = new DataTable();
    table.Columns.Add("ID", typeof(long));
    foreach (long id in ids) 
    {
        table.Rows.Add(id);
    }
    return table;
}

private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids) 
{
    SqlMetaData[] metaData = new SqlMetaData[1];
    metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt);
    SqlDataRecord record = new SqlDataRecord(metaData);
    foreach (long id in ids) 
    {
        record.SetInt64(0, id);
        yield return record;
    }
}

24
+1 훌륭한 예입니다. 테이크 아웃은 다음과 같습니다을 보내 DataTable설정 매개 변수 값으로 SqlDbType에 Structured와 TypeName데이터베이스 UDT 이름.
— lc.

10
루프에서 참조 유형의 인스턴스를 재사용하려는 경우 (이 예제에서는 SQLDataRecord)이 특정 인스턴스에서 안전한 이유에 대한 설명을 추가하십시오.
— Søren Boisen

2
이 코드는 잘못되었습니다. 빈 테이블 값 매개 변수의 값은으로 설정해야 null합니다. 빈 매개 변수 가 주어지면 CreateSqlDataRecords반환되지 않습니다 . nullids
— ta.speot.is는

4
@Crono : DataTable(나 DataSet)가 비주얼 스튜디오에서 suppiort 드래그 & 드롭 기능을 가지고 있기 때문에 그들이 구현할 수 있도록 단지, 그것을 구현 IComponent하는 구현을 IDisposable. 디자이너를 사용하지 않고 수동으로 작성하는 경우 디자이너를 폐기하거나-문을 사용할 이유가 없습니다 using. "이것은 모든 것을 처리하는"황금 규칙의 예외 중 하나입니다 IDisposable.
— Tim Schmelter

2
@TimSchmelter 경험상 필자 Dispose가 그렇지 않은 경우 코드 분석에서 경고하지 않도록 항상 메서드를 호출 합니다. 그러나 기본 DataSet및 DataTable인스턴스가 사용되는 이 특정 시나리오에서는 호출 Dispose이 아무것도하지 않을 것에 동의합니다 .
— Crono

31

또한 라이언의 대답에 당신은 또한 설정해야합니다 DataColumn의 Ordinal당신이 다루고있는 경우 속성을 table-valued parameter가진 여러 누구의 서수입니다 열 수 없습니다 알파벳 순서.

예를 들어, SQL에서 매개 변수로 사용되는 다음 테이블 값이있는 경우 :

CREATE TYPE NodeFilter AS TABLE (
  ID int not null
  Code nvarchar(10) not null,
);

C #에서와 같이 열을 주문해야합니다.

table.Columns["ID"].SetOrdinal(0);
// this also bumps Code to ordinal of 1
// if you have more than 2 cols then you would need to set more ordinals

이를 수행하지 않으면 구문 분석 오류가 발생하고 nvarchar를 int로 변환하지 못했습니다.


15

일반적인

   public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector)
    {
        var tbl = new DataTable();
        tbl.Columns.Add("Id", typeof(T));

        foreach (var item in list)
        {
            tbl.Rows.Add(selector.Invoke(item));

        }

        return tbl;

    }

매개 변수로 무엇을 전달해야하는지 알려주시겠습니까? Func <T, TProperty> 선택기? 단순히 tbl.Rows.Add (item) 일 수 없으며 해당 매개 변수가 필요하지 않습니다.
— GDroid

selector.Invoke (item)은 대부분의 경우 해당 항목의 속성을 선택하지만 문자열 속성을 선택할 수도 있습니다
— Martea

선택기를 어떻게 배치합니까? 저장 프로 시저에 전달할 List <Guid>가 있습니다.
— GDroid

guidList.ToTabledValuedParameter (x => x), x는 사용자의 guid이므로 반환은 guid 목록이있는 하나의 열 (id)을 가진 DataTable이됩니다.
— Martea

5

가장 깨끗한 방법으로 작업하십시오. 테이블이 "dbo.tvp_Int"라는 정수 목록이라고 가정하십시오 (자신의 테이블 유형에 맞게 사용자 정의)

이 확장 방법을 만듭니다 ...

public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data)
{
   if(paramCollection != null)
   {
       var p = paramCollection.Add(parameterName, SqlDbType.Structured);
       p.TypeName = "dbo.tvp_Int";
       DataTable _dt = new DataTable() {Columns = {"Value"}};
       data.ForEach(value => _dt.Rows.Add(value));
       p.Value = _dt;
   }
}

이제 간단히 다음을 수행하여 테이블 값 매개 변수를 한 줄에 추가 할 수 있습니다.

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds);

1
paramCollection이 NULL이면 어떻게 되나요? 빈 타입을 전달하는 방법?
— Muflix

2
@Muflix 애매하게도 확장 메서드는 실제로 null 인스턴스에 대해 작동합니다. 따라서 if(paramCollection != null)방법의 상단에 간단한 확인을 추가하는 것이
— 좋습니다

1
초기 체크 기능으로 업데이트 된 답변
— Shahzad Qureshi

2
어쩌면 약간 pedantic하지만 서명 IEnumerable대신에 사용하려고 합니다. 그러면 목록뿐만 아니라, List무엇이든 전달할 수 있습니다. IEnumerable특정 기능을 사용 List하지 않기 때문에 실제로는 우리IEnumerable
— 프랜시스로드

List를 사용하면 속기 데이터를 사용할 수 있습니다 .ForEach (), 그렇지 않으면 실제로 foreach 루프를 작성해야합니다. 어느 것도 효과가 있지만 가능한 한 짧게 쓰는 것을 좋아합니다.
— Shahzad Qureshi

0

이 코드를 사용하여 유형에서 적합한 매개 변수를 작성하십시오.

private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
    DataTable dt = new DataTable();

    var properties = typedParameter.GetType().GetProperties().ToList();
    properties.ForEach(p =>
    {
        dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
    });
    var row = dt.NewRow();
    properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
    dt.Rows.Add(row);

    return new SqlParameter
    {
        Direction = ParameterDirection.Input,
        ParameterName = name,
        Value = dt,
        SqlDbType = SqlDbType.Structured
    };
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.