Let me introduce some extended solution.
In this working example as you see there is a stored procedure that has output parameter and async/await C# code that shows how to accept this output single value.
I hope it will help to understand the approach and will be an extended answer on your question.
C# WinForm
private async void Button1_Click(object sender, EventArgs e)
{
await CleanDevicePositions();
}
public async Task CleanDevicePositions()
{
try
{
var endDate = DateTime.Now.AddDays(-120).ToString("yyyyMMdd");
using (var db = new TechServiceEntities())
{
db.Database.CommandTimeout = 18000;
object[] parameters = {
new SqlParameter("@MaxToDeleteInp", 1000),
new SqlParameter("@BatchSizeInp", 100),
new SqlParameter("@ToEndDateInp", endDate),
new SqlParameter
{
ParameterName = "@Records",
SqlDbType = SqlDbType.Int,
Value = -1,
Direction = ParameterDirection.Output
}
};
// For result description have a look at https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/return-data-from-a-stored-procedure?view=sql-server-ver15
var result = await db.Database.
ExecuteSqlCommandAsync($"EXEC dbo.DeleteOldDevicePositions @MaxToDeleteInp, @BatchSizeInp, @ToEndDateInp, @Records OUTPUT", parameters)
.ConfigureAwait(false); ;
var retValue = (parameters[3] as SqlParameter).Value;
BeginInvokeLambda(listBox1, () => { listBox1.Items.Add(retValue); });
BeginInvokeLambda(listBox1, () => { listBox1.Items.Add(result); });
}
}
catch (SqlException ex)
{
Debug.WriteLine(ex);
BeginInvokeLambda(listBox1, () => { listBox1.Items.Add(ex.ToString()); });
BeginInvokeLambda(listBox1, () => { listBox1.Items.Add(ex.Message); });
}
catch (Exception ex)
{
Debug.WriteLine(ex);
BeginInvokeLambda(listBox1, () => { listBox1.Items.Add(ex.ToString()); });
BeginInvokeLambda(listBox1, () => { listBox1.Items.Add(ex.Message); });
}
}
private static IAsyncResult BeginInvokeLambda(Control control, Action action)
{
if (!control.IsDisposed)
{
return control.BeginInvoke(action, null);
}
return null;
}
TSQL
This SP just deletes some records by defined batch size of its amount.
ALTER PROCEDURE [dbo].[DeleteOldDevicePositions]
@MaxToDeleteInp int = 1000, -- Max amount of records to delete
@BatchSizeInp int = 100, -- it is
@ToEndDateInp datetime, -- Delete until this datetime
@Records int OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @MaxToDelete int , @BatchSize int , @ToEndDate datetime;
SET @MaxToDelete = @MaxToDeleteInp;
SET @BatchSize = @BatchSizeInp;
SET @ToEndDate = @ToEndDateInp;
DECLARE @TransactionInterval tinyint = 5, @FactDeleted int = 0;
DECLARE @counter int = 1;
DECLARE @s datetime, @e datetime, @r int = 1;
SELECT @s = MIN(AtTime) FROM dbo.DevicePositions;
BEGIN TRANSACTION;
WHILE (@r > 0)
BEGIN
IF @r % @TransactionInterval = 1
BEGIN
COMMIT TRANSACTION;
BEGIN TRANSACTION;
END
DELETE TOP (@BatchSize) FROM DevicePositions WHERE AtTime >= @s AND AtTime <= @ToEndDate;
SET @FactDeleted = @FactDeleted +@BatchSize;
SET @r = @@ROWCOUNT;
SET @counter = @counter + 1;
IF @FactDeleted >= @MaxToDelete
BREAK;
END
IF @@TRANCOUNT > 0
BEGIN
COMMIT TRANSACTION;
IF @counter % 10 = 0 -- or maybe 100 or 1000
BEGIN
CHECKPOINT;
END
END
SELECT @Records = A.Records FROM (
SELECT OBJECT_NAME(object_id) as ID, SUM(row_count) AS Records FROM sys.dm_db_partition_stats WHERE
object_id = OBJECT_ID('DevicePositions') AND index_id < 2
GROUP BY OBJECT_NAME(object_id) ) A
RETURN @Records;
END
App
Here you can see the number of records after deleting.

@retValis not a return value, is just an Output parameters that happens to be assigned the return value of the SP.