Najłatwiej jest po prostu wydrukować numer, który otrzymasz z powrotem dla ExecuteNonQuery
:
int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
Console.WriteLine("{0} rows affected.", rowsAffected);
}
To powinno działać, ale nie będzie honorować SET NOCOUNT
ustawienie bieżącej sesji/zakresu.
W przeciwnym razie zrobiłbyś to tak, jak robiłbyś z „zwykłym” ADO.NET. Nie używaj ServerConnection.ExecuteNonQuery()
metody, ale utwórz SqlCommand
obiekt, uzyskując dostęp do bazowego SqlConnection
obiekt. W tym celu zasubskrybuj StatementCompleted
wydarzenie.
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();
}
Korzystanie z StatementCompleted
(zamiast tego, powiedzmy, ręcznie wypisz wartość, którą ExecuteNonQuery()
zwracane) ma tę zaletę, że działa dokładnie tak, jak SSMS lub SQLCMD.EXE:
- W przypadku poleceń, które nie mają ROWCOUNT, nie zostanie ono w ogóle wywołane (np. GO, USE).
- Jeśli
SET NOCOUNT ON
został ustawiony, w ogóle nie zostanie wywołany. - Jeżeli
SET NOCOUNT OFF
została ustawiona, będzie wywoływana dla każdej instrukcji w paczce.
(Pasek boczny:wygląda jak StatementCompleted
jest dokładnie tym, o czym mówi protokół TDS, gdy DONE_IN_PROC
wydarzenie jest wymienione; zobacz Uwagi
polecenia SET NOCOUNT w witrynie MSDN.)
Osobiście z powodzeniem zastosowałem to podejście w moim własnym „klonie” SQLCMD.EXE.
AKTUALIZUJ :Należy zauważyć, że to podejście (oczywiście) wymaga ręcznego podzielenia skryptu wejściowego/instrukcji w GO
separatora, ponieważ wróciłeś do używania SqlCommand.Execute*()
które nie mogą obsługiwać wielu partii jednocześnie. W tym celu istnieje wiele opcji:
- Ręcznie podziel dane wejściowe na linie zaczynające się od
GO
(zastrzeżenie:GO
można nazwać jakGO 5
, na przykład, aby wykonać poprzednią partię 5 razy). - Użyj ManagedBatchParser class/library, które pomogą Ci podzielić dane wejściowe na pojedyncze partie, w szczególności zaimplementuj ICommandExecutor.ProcessBatch z powyższym kodem (lub czymś przypominającym go).
Wybrałem późniejszą opcję, która była dość pracochłonna, biorąc pod uwagę, że nie jest dobrze udokumentowana, a przykłady są rzadkie (wygoogluj trochę, znajdziesz trochę rzeczy lub użyj reflektora, aby zobaczyć, jak SMO-Assembly używają tej klasy) .
Korzyść (i być może obciążenie) z używania ManagedBatchParser
jest to, że przeanalizuje również wszystkie inne konstrukcje skryptów T-SQL (przeznaczonych dla SQLCMD.EXE
) dla Was. W tym::setvar
, :connect
, :quit
itp. Nie musisz implementować odpowiedniego ICommandExecutor
członków, jeśli twoje skrypty ich nie używają, oczywiście. Pamiętaj jednak, że możesz nie być w stanie wykonać „dowolnych” skryptów.
Cóż, czy to cię postawiło. Od "prostego pytania" jak wydrukować "... wiersze dotknięte" do faktu, że nie jest to trywialne wykonanie w solidny i ogólny sposób (biorąc pod uwagę wymaganą pracę w tle). YMMV, powodzenia.
Aktualizacja dotycząca użycia ManagedBatchParser
Wydaje się, że nie ma dobrej dokumentacji ani przykładu na temat implementacji IBatchSource
, oto, z czym poszedłem.
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;
}
}
A tak byś tego używał:
var source = new StatementBatchSource("SELECT GETUTCDATE()");
source.Populate();
var parser = new Parser();
parser.SetBatchSource(source);
/* other parser.Set*() calls */
parser.Parse();
Zauważ, że obie implementacje, zarówno dla instrukcji bezpośrednich (StatementBatchSource
) lub dla pliku (FileBatchSource
) mają problem, że od razu wczytują cały tekst do pamięci. Miałem jeden przypadek, w którym to wybuchło, mając ogromny (!) skrypt z miliardami wygenerowanych INSERT
sprawozdania. Chociaż nie sądzę, że jest to praktyczny problem, SQLCMD.EXE
może sobie z tym poradzić. Ale na całe życie nie mogłem zrozumieć, jak dokładnie trzeba by utworzyć fragmenty zwrócone dla IBatchParser.GetContent()
aby parser mógł nadal z nimi pracować (wygląda na to, że musiałyby to być kompletne instrukcje, które w pewnym sensie pokonałyby cel parsowania w pierwszej kolejności...).