วิธีการส่งผ่านพารามิเตอร์ค่าของตารางไปยังกระบวนงานที่เก็บไว้จากรหัส. 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 ถ้าฉันมีหลายคอลัมน์ SQL เร็วมากเมื่อประมวลผล xml คุณสามารถลองวิธีการทั้งหมดและตรวจสอบเวลาการประมวลผลและหลังจากนั้นเลือกวิธีที่ดีที่สุด XML จะมีลักษณะเหมือน <Items> <Item value = "sdadas" /> <Item value = "sadsad" /> ... </Items> กระบวนการบน SQL Server ก็ง่าย เมื่อใช้วิธีนี้คุณสามารถเพิ่มแอตทริบิวต์ใหม่ลงใน <item> หากคุณต้องการข้อมูลเพิ่มเติม
Nițu Alexandru

4
@ NițuAlexandru "Sql มันเร็วมากเมื่อประมวลผล xml" ไม่ได้ใกล้เคียง.
nothrow

คำตอบ:


279

DataTable, DbDataReaderหรือIEnumerable<SqlDataRecord>วัตถุที่สามารถนำมาใช้ในการเติมพารามิเตอร์ตารางมูลค่าต่อบทความ MSDN ตารางมูลค่าพารามิเตอร์ใน SQL Server 2008 (ADO.NET)

ตัวอย่างต่อไปนี้แสดงให้เห็นถึงการใช้หนึ่งDataTableหรือ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จะไม่ส่งคืนnullหากกำหนดidsพารามิเตอร์ว่าง
ta.speot.is

4
@Crono: DataTable(หรือDataSet) เพียงใช้มันเพราะพวกเขาต้อง suppiort ลากและวางความสามารถใน Visual-Studio เพื่อให้พวกเขาดำเนินการซึ่งการดำเนินการIComponent IDisposableหากคุณไม่ได้ใช้นักออกแบบ แต่สร้างด้วยตนเองก็ไม่มีเหตุผลที่จะกำจัดมัน (หรือใช้using-statement) ดังนั้นนี่คือข้อยกเว้นประการหนึ่งของกฎทอง "กำจัดทุกสิ่งที่นำไปใช้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 (รายการ) และไม่จำเป็นต้องใช้พารามิเตอร์นั้น
GDroid

selector.Invoke (รายการ) เลือกคุณสมบัติในรายการส่วนใหญ่เป็น int แต่มันยังช่วยให้คุณสามารถเลือกคุณสมบัติสตริง
Martea

คุณช่วยยกตัวอย่างให้ฉันเลือกตัวเลือกได้อย่างไร? ฉันมีลิสต์ <Guid> เพื่อส่งไปยัง proc ที่เก็บไว้ ...
GDroid

guidList.ToTabledValuedParameter (x => x) เนื่องจาก x เป็น guid ในกรณีของคุณการส่งคืนจะเป็น DataTable ที่มีหนึ่งคอลัมน์ (id) พร้อมรายการ guids
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 ชัดเจนวิธีการขยายใช้งานได้จริงกับอินสแตนซ์ที่เป็นโมฆะ ดังนั้นการเพิ่มการif(paramCollection != null)ตรวจสอบง่าย ๆที่ด้านบนของวิธีการจะเป็นไปด้วยดี
Rhumborl

1
อัปเดตคำตอบพร้อมเริ่มต้น -if- ตรวจสอบ
Shahzad Qureshi

2
อาจจะค่อนข้างคล่องแคล่ว แต่ฉันจะใช้IEnumerableแทนListลายเซ็นในแบบที่คุณสามารถส่งผ่านสิ่งที่IEnumerableไม่ใช่แค่รายการเนื่องจากคุณไม่ได้ใช้ฟังก์ชั่นใด ๆ ที่เฉพาะเจาะจงListฉันไม่เห็นเหตุผลที่จะไม่ เราIEnumerable
ฟรานซิสลอร์ด

การใช้รายการช่วยให้คุณใช้ข้อมูลชวเลขได้ ForEach () มิฉะนั้นคุณจะต้องเขียน foreach loop ซึ่งสามารถใช้งานได้ แต่ฉันชอบเขียนสิ่งที่สั้นที่สุด
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.