.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설정 매개 변수 값으로 SqlDbTypeStructuredTypeName데이터베이스 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가 그렇지 않은 경우 코드 분석에서 경고하지 않도록 항상 메서드를 호출 합니다. 그러나 기본 DataSetDataTable인스턴스가 사용되는 이 특정 시나리오에서는 호출 Dispose이 아무것도하지 않을 것에 동의합니다 .
Crono

31

또한 라이언의 대답에 당신은 또한 설정해야합니다 DataColumnOrdinal당신이 다루고있는 경우 속성을 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.