Hal termudah adalah dengan mencetak nomor yang Anda dapatkan kembali untuk ExecuteNonQuery
:
int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
Console.WriteLine("{0} rows affected.", rowsAffected);
}
Ini seharusnya bekerja, tetapi tidak akan menghormati SET NOCOUNT
pengaturan sesi/cakupan saat ini.
Jika tidak, Anda akan melakukannya seperti yang Anda lakukan dengan ADO.NET "polos". Jangan gunakan ServerConnection.ExecuteNonQuery()
metode, tetapi buat SqlCommand
objek dengan mengakses SqlConnection
yang mendasarinya obyek. Pada itu berlangganan StatementCompleted
acara.
using (SqlCommand command = server.ConnectionContext.SqlConnectionObject.CreateCommand())
{
// Set other properties for "command", like StatementText, etc.
command.StatementCompleted += (s, e) => {
Console.WriteLine("{0} row(s) affected.", e.RecordCount);
};
command.ExecuteNonQuery();
}
Menggunakan StatementCompleted
(sebagai gantinya, katakanlah, secara manual mencetak nilai yang ExecuteNonQuery()
dikembalikan) memiliki manfaat bahwa ia bekerja persis seperti SSMS atau SQLCMD.EXE akan:
- Untuk perintah yang tidak memiliki ROWCOUNT tidak akan dipanggil sama sekali (mis. GO, USE).
- Jika
SET NOCOUNT ON
telah disetel, itu tidak akan dipanggil sama sekali. - Jika
SET NOCOUNT OFF
telah disetel, itu akan dipanggil untuk setiap pernyataan di dalam kumpulan.
(Bilah Samping:sepertinya StatementCompleted
persis seperti yang dibicarakan oleh protokol TDS ketika DONE_IN_PROC
peristiwa disebutkan; lihat Keterangan
dari perintah SET NOCOUNT pada MSDN.)
Secara pribadi, saya telah menggunakan pendekatan ini dengan sukses di "klon" saya sendiri dari SQLCMD.EXE.
PERBARUI :Perlu dicatat, bahwa pendekatan ini (tentu saja) mengharuskan Anda untuk membagi skrip/pernyataan input secara manual di GO
pemisah, karena Anda kembali menggunakan SqlCommand.Execute*()
yang tidak dapat menangani banyak batch sekaligus. Untuk ini, ada beberapa opsi:
- Pisahkan input secara manual pada baris yang dimulai dengan
GO
(peringatan:GO
bisa dipanggil sepertiGO 5
, misalnya, untuk mengeksekusi kumpulan sebelumnya 5 kali). - Gunakan ManagedBatchParser class/library untuk membantu Anda membagi input menjadi batch tunggal, terutama mengimplementasikan ICommandExecutor.ProcessBatch dengan kode di atas (atau yang serupa).
Saya memilih opsi yang lebih baru, yang cukup berhasil, mengingat itu tidak didokumentasikan dengan baik dan contoh jarang (google sedikit, Anda akan menemukan beberapa barang, atau menggunakan reflektor untuk melihat bagaimana Majelis SMO menggunakan kelas itu) .
Manfaat (dan mungkin beban) menggunakan ManagedBatchParser
adalah, bahwa itu juga akan mengurai semua konstruksi skrip T-SQL lainnya (ditujukan untuk SQLCMD.EXE
) untuk kamu. Termasuk::setvar
, :connect
, :quit
, dll. Anda tidak perlu mengimplementasikan ICommandExecutor
masing-masing anggota, jika skrip Anda tidak menggunakannya, tentu saja. Namun perlu diingat bahwa Anda mungkin tidak dapat menjalankan skrip "sewenang-wenang".
Nah, apakah itu menempatkan Anda. Dari "pertanyaan sederhana" tentang cara mencetak "... baris terpengaruh" hingga fakta bahwa itu tidak sepele untuk dilakukan dengan cara yang kuat dan umum (mengingat pekerjaan latar belakang diperlukan). YMMV, semoga berhasil.
Pembaruan tentang Penggunaan ManagedBatchParser
Tampaknya tidak ada dokumentasi atau contoh yang baik tentang cara mengimplementasikan IBatchSource
, inilah yang saya gunakan.
internal abstract class BatchSource : IBatchSource
{
private string m_content;
public void Populate()
{
m_content = GetContent();
}
public void Reset()
{
m_content = null;
}
protected abstract string GetContent();
public ParserAction GetMoreData(ref string str)
{
str = null;
if (m_content != null)
{
str = m_content;
m_content = null;
}
return ParserAction.Continue;
}
}
internal class FileBatchSource : BatchSource
{
private readonly string m_fileName;
public FileBatchSource(string fileName)
{
m_fileName = fileName;
}
protected override string GetContent()
{
return File.ReadAllText(m_fileName);
}
}
internal class StatementBatchSource : BatchSource
{
private readonly string m_statement;
public StatementBatchSource(string statement)
{
m_statement = statement;
}
protected override string GetContent()
{
return m_statement;
}
}
Dan beginilah cara Anda menggunakannya:
var source = new StatementBatchSource("SELECT GETUTCDATE()");
source.Populate();
var parser = new Parser();
parser.SetBatchSource(source);
/* other parser.Set*() calls */
parser.Parse();
Perhatikan bahwa kedua implementasi, baik untuk pernyataan langsung (StatementBatchSource
) atau untuk file (FileBatchSource
) memiliki masalah bahwa mereka membaca teks lengkap sekaligus ke dalam memori. Saya punya satu kasus di mana itu meledak, memiliki skrip besar(!) dengan trilyunan INSERT
yang dihasilkan pernyataan. Meskipun menurut saya itu bukan masalah praktis, SQLCMD.EXE
bisa menanganinya. Tetapi untuk kehidupan saya, saya tidak tahu bagaimana tepatnya, Anda perlu membentuk potongan yang dikembalikan untuk IBatchParser.GetContent()
sehingga parser masih dapat bekerja dengan mereka (sepertinya mereka perlu pernyataan lengkap, yang akan mengalahkan tujuan parse sejak awal...).