Cara meneruskan parameter nilai tabel ke prosedur tersimpan dari .net code

171

Saya memiliki database SQL Server 2005. Dalam beberapa prosedur, saya memiliki parameter tabel yang saya berikan ke proc tersimpan sebagai nvarchar(dipisahkan oleh koma) dan secara internal dibagi menjadi nilai-nilai tunggal. Saya menambahkannya ke daftar parameter perintah SQL seperti ini:

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

Saya harus memigrasi database ke SQL Server 2008. Saya tahu bahwa ada parameter nilai tabel, dan saya tahu cara menggunakannya dalam prosedur tersimpan. Tapi saya tidak tahu bagaimana cara mengirimkannya ke daftar parameter dalam perintah SQL.

Adakah yang tahu sintaks Parameters.Addprosedur yang benar? Atau adakah cara lain untuk melewatkan parameter ini?

Marek Kwiendacz
sumber
Lihatlah solusi ini: Prosedur Tersimpan dengan Parameter Table-Valued di EF. code.msdn.microsoft.com/Stored-Procedure-with-6c194514
Carl Prothman
Dalam kasus seperti ini, saya biasanya merangkai string dan membaginya di sisi server atau lulus bahkan xml jika saya memiliki beberapa kolom. Sql sangat cepat saat memproses xml. Anda dapat mencoba semua metode dan memeriksa waktu pemrosesan dan setelah itu pilih metode terbaik. XML akan terlihat seperti <Items> <Item value = "sdadas" /> <Item value = "sadsad" /> ... </Items>. Proses pada Sql Server juga sederhana. Dengan menggunakan metode ini, Anda selalu dapat menambahkan atribut baru ke <item> jika Anda membutuhkan informasi lebih lanjut.
Nițu Alexandru
4
@ NițuAlexandru, "Sql sangat cepat saat memproses xml.". Bahkan tidak dekat.
nothrow

Jawaban:

279

DataTable,, DbDataReaderatau IEnumerable<SqlDataRecord>objek dapat digunakan untuk mengisi parameter tabel-nilai per artikel MSDN Parameter Tabel-Nilai di SQL Server 2008 (ADO.NET) .

Contoh berikut menggambarkan penggunaan salah satu DataTableatau IEnumerable<SqlDataRecord>:

Kode 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

Kode 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;
    }
}
Ryan Prechel
sumber
24
+1 Contoh luar biasa. Takeaways adalah: kirim a DataTablesebagai nilai parameter, atur SqlDbTypeke Structureddan TypeNameke database UDT name.
lc.
10
Jika Anda akan menggunakan kembali contoh tipe referensi dalam satu lingkaran (SqlDataRecord dalam contoh Anda), silakan tambahkan komentar mengapa aman untuk melakukannya dalam contoh khusus ini.
Søren Boisen
2
Kode ini salah: parameter bernilai tabel kosong harus ditetapkan nilainya null. CreateSqlDataRecordstidak akan pernah kembali nulljika diberi idsparameter kosong .
ta.speot.is
4
@Crono: DataTable(atau DataSet) hanya mengimplementasikannya karena mereka harus mendukung kemampuan drag & drop di Visual-Studio, sehingga mereka mengimplementasikan IComponentimplementasinya IDisposable. Jika Anda tidak menggunakan perancang tetapi membuatnya secara manual, tidak ada alasan untuk membuangnya (atau menggunakan usingpernyataan). Jadi ini adalah salah satu pengecualian dari aturan emas "buang semua yang mengimplementasikan IDisposable".
Tim Schmelter
2
@TimSchmelter Sebagai aturan praktis saya selalu memanggil Disposemetode, bahkan jika hanya supaya Analisis Kode tidak akan memperingatkan saya jika saya tidak melakukannya. Tetapi saya setuju bahwa dalam skenario khusus ini di mana basis DataSetdan DataTableinstance digunakan, panggilan Disposetidak akan melakukan apa-apa.
Crono
31

Lebih lanjut untuk jawaban Ryan Anda juga perlu mengatur DataColumn's Ordinalproperti jika Anda berurusan dengan table-valued parameterdengan beberapa kolom yang ordinals yang tidak dalam urutan abjad.

Sebagai contoh, jika Anda memiliki nilai tabel berikut yang digunakan sebagai parameter dalam SQL:

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

Anda perlu memesan kolom seperti itu di 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

Jika Anda gagal melakukan ini, Anda akan mendapatkan kesalahan parse, gagal mengubah nvarchar ke int.

Scotty.NET
sumber
15

Umum

   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;

    }
Martea
sumber
Tolong beri tahu saya apa yang saya sampaikan sebagai parameter? Fungsi pemilih <T, TProperty>? Tidak bisakah hanya tbl.Rows.Add (item) dan tidak perlu parameter itu.
GDroid
selector.Invoke (item) memilih properti pada item yang sebagian besar merupakan int, tetapi juga memungkinkan Anda untuk memilih properti string
Martea
dapatkah Anda memberikan contoh bagaimana cara menempatkan pemilih di sana ?? Saya punya Daftar <Guid> untuk dilewatkan ke proc tersimpan ...
GDroid
guidList.ToTabledValuedParameter (x => x), karena x adalah panduan dalam kasus Anda, kembalinya akan menjadi DataTable dengan satu kolom (id) dengan daftar panduan,
Martea
5

Cara terbersih untuk bekerja dengannya. Dengan asumsi tabel Anda adalah daftar bilangan bulat yang disebut "dbo.tvp_Int" (Kustomisasi untuk jenis tabel Anda sendiri)

Buat metode ekstensi ini ...

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

Sekarang Anda dapat menambahkan parameter nilai tabel dalam satu baris di mana saja hanya dengan melakukan ini:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds);
Shahzad Qureshi
sumber
1
bagaimana jika paramCollection adalah NULL? Bagaimana cara melewati tipe kosong?
Muflix
2
@ Muflix Jelas, metode ekstensi benar-benar bekerja melawan instance nol. Jadi menambahkan if(paramCollection != null)cek sederhana di bagian atas metode ini akan baik
Rhumborl
1
Jawaban yang diperbarui dengan cek -jika- awal
Shahzad Qureshi
2
Mungkin agak sombong, tapi saya akan menggunakan IEnumerablealih-alih Listdi tanda tangan, dengan begitu Anda bisa melewatkan apa pun yang ada IEnumerable, bukan hanya daftar, Karena Anda tidak menggunakan fungsi spesifik apa pun List, saya tidak benar-benar melihat alasan untuk tidak kamiIEnumerable
Francis Lord
Menggunakan Daftar memungkinkan Anda untuk menggunakan data steno. ForEach (), jika tidak, Anda harus benar-benar menulis loop foreach. Yang bisa bekerja juga, tapi saya suka menulis sesingkat mungkin.
Shahzad Qureshi
0

Gunakan kode ini untuk membuat parameter yang cocok dari tipe Anda:

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