Cách truyền tham số giá trị bảng cho thủ tục được lưu trữ từ mã .net


171

Tôi có cơ sở dữ liệu SQL Server 2005. Trong một vài thủ tục, tôi có các tham số bảng mà tôi chuyển đến một Proc được lưu trữ dưới dạng nvarchar(được phân tách bằng dấu phẩy) và chia nội bộ thành các giá trị đơn. Tôi thêm nó vào danh sách tham số lệnh SQL như thế này:

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

Tôi phải di chuyển cơ sở dữ liệu sang SQL Server 2008. Tôi biết rằng có các tham số giá trị bảng và tôi biết cách sử dụng chúng trong các thủ tục được lưu trữ. Nhưng tôi không biết làm thế nào để chuyển một danh sách tham số trong lệnh SQL.

Có ai biết cú pháp đúng của Parameters.Addthủ tục? Hoặc có một cách khác để vượt qua tham số này?


Kiểm tra giải pháp này: Quy trình được lưu trữ với tham số có giá trị bảng trong EF. code.msdn.microsoft.com/Stored-Procedure-with-6c194514
Carl Prothman

Trong trường hợp như thế này, tôi thường nối các chuỗi và tách chúng ở phía máy chủ hoặc chuyển ngay cả một xml nếu tôi có nhiều cột. Sql nó rất nhanh khi xử lý xml. Bạn có thể thử tất cả các phương pháp và kiểm tra thời gian xử lý và sau đó chọn phương pháp tốt nhất. Một XML sẽ trông giống như <Item> <Item value = "sdadas" /> <Item value = "sadsad" />...</ Item>. Quá trình trên Sql Server cũng đơn giản. Sử dụng phương pháp này, bạn luôn có thể thêm một thuộc tính mới vào <item> nếu bạn cần thêm thông tin.
Nițu Alexandru

4
@ NițuAlexandru, "Sql rất nhanh khi xử lý xml.". Thậm chí không gần gũi.
nothrow

Câu trả lời:


278

DataTable, DbDataReaderhoặc IEnumerable<SqlDataRecord>các đối tượng có thể được sử dụng để điền tham số có giá trị bảng cho mỗi bài viết MSDN Tham số giá trị bảng trong SQL Server 2008 (ADO.NET) .

Ví dụ sau minh họa bằng cách sử dụng a DataTablehoặc an IEnumerable<SqlDataRecord>:

Mã 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

Mã 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 Ví dụ tuyệt vời. Takeaways là: gửi một DataTablegiá trị tham số, được đặt SqlDbTypethành StructuredTypeNameđến tên UDT cơ sở dữ liệu.
lc.

10
Nếu bạn định sử dụng lại một thể hiện của loại tham chiếu trong một vòng lặp (SqlDataRecord trong ví dụ của bạn), vui lòng thêm một nhận xét về lý do tại sao trong trường hợp cụ thể này lại an toàn.
Søren Boisen

2
Mã này sai: tham số giá trị bảng trống nên đặt giá trị của chúng thành null. CreateSqlDataRecordssẽ không bao giờ quay trở lại nullnếu được cung cấp một idstham số trống .
ta.speot.is

4
@Crono: DataTable(hoặc DataSet) chỉ triển khai nó vì họ phải hỗ trợ các khả năng kéo và thả trong Visual-Studio, vì vậy họ triển IComponentkhai thực hiện IDisposable. Nếu bạn không sử dụng trình thiết kế nhưng tạo thủ công thì không có lý do gì để loại bỏ nó (hoặc sử dụng using-statement). Vì vậy, đây là một trong những trường hợp ngoại lệ của quy tắc vàng "loại bỏ mọi thứ thực hiện IDisposable".
Tim Schmelter

2
@TimSchmelter Theo nguyên tắc thông thường, tôi luôn gọi Disposecác phương thức, ngay cả khi chỉ để Phân tích mã sẽ không cảnh báo tôi nếu tôi không. Nhưng tôi đồng ý rằng trong kịch bản cụ thể này, nơi cơ sở DataSetDataTabletrường hợp được sử dụng, việc gọi Disposesẽ không làm gì cả.
Crono

31

Thêm vào câu trả lời của Ryan bạn cũng sẽ cần phải thiết lập các DataColumn's Ordinalthuộc tính nếu bạn đang đối phó với một table-valued parametervới nhiều cột mà ordinals là không theo thứ tự chữ cái.

Ví dụ: nếu bạn có giá trị bảng sau được sử dụng làm tham số trong SQL:

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

Bạn sẽ cần phải đặt hàng các cột của mình như vậy trong 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

Nếu bạn không làm điều này, bạn sẽ gặp lỗi phân tích cú pháp, không thể chuyển đổi nvarchar thành int.


15

Chung

   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;

    }

Bạn vui lòng cho tôi biết rằng những gì tôi vượt qua như là tham số? Bộ chọn <T, TProperty>? Không thể chỉ đơn giản là tbl.Rows.Add (item) và không cần tham số đó.
GDroid 18/03/2015

bộ chọn. Nhập (mục) chọn thuộc tính trên mục đó hầu hết là trường hợp int, nhưng nó cũng cho phép bạn chọn thuộc tính chuỗi
Martea 20/03/2015

bạn có thể vui lòng cung cấp một ví dụ về cách tôi đặt bộ chọn ở đó không ?? Tôi có một Danh sách <Hướng dẫn> để chuyển đến Proc được lưu trữ ...
GDroid

guideList.ToTablesValuedParameter (x => x), vì x là hướng dẫn trong trường hợp của bạn, trả về sẽ là một DataTable với một cột (id) với một danh sách các hướng dẫn,
Martea

5

Cách sạch nhất để làm việc với nó. Giả sử bảng của bạn là một danh sách các số nguyên được gọi là "dbo.tvp_Int" (Tùy chỉnh cho loại bảng của riêng bạn)

Tạo phương thức mở rộng này ...

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

Bây giờ bạn có thể thêm một tham số có giá trị trong một dòng ở bất cứ đâu chỉ bằng cách thực hiện điều này:

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

1
Nếu paramCollection là NULL thì sao? Làm thế nào để vượt qua loại trống?
Muflix

2
@Muflix Một cách khó hiểu, các phương thức mở rộng thực sự hoạt động chống lại các trường hợp null. Vì vậy, thêm một if(paramCollection != null)kiểm tra đơn giản ở đầu phương thức sẽ ổn
Rhumborl

1
Câu trả lời được cập nhật với -if- check
Shahzad Qureshi

2
Có thể hơi khoa trương, nhưng tôi sử dụng IEnumerablethay vì Listtrong chữ ký, theo cách đó bạn có thể vượt qua bất kỳ thứ gì IEnumerable, không chỉ là danh sách, Vì bạn không sử dụng bất kỳ chức năng cụ thể nào List, tôi thực sự không thấy lý do để không chúng tôiIEnumerable
Đức Phanxicô

Sử dụng Danh sách cho phép bạn sử dụng dữ liệu tốc ký.ForEach (), nếu không, bạn thực sự phải viết một vòng lặp foreach. Điều này cũng có thể làm việc, nhưng tôi thích viết những thứ càng ngắn càng tốt.
Shahzad Qureshi

0

Sử dụng mã này để tạo tham số phù hợp từ loại của bạn:

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
    };
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.