I want to create an extension method that update the changed data in async method using SqlDataAdapter
I've created simple extension but it's not functioning fast.
public static Task<int> UpdateAsync (this SqlDataAdapter adapter,DataTable changedData)
{
return Task.Factory.StartNew(() =>
{
return adapter.Update(changedData);
},CancellationToken.None,TaskCreationOptions.LongRunning,TaskScheduler.Default);
}
I want to update every row in the changed rows in a parallel
I tried to do something like that :
public static async Task<int> UpdateAsync(this SqlDataAdapter adapter, DataTable changedData)
{
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
if (changedData == null) throw new ArgumentNullException(nameof(changedData));
if (changedData.Rows.Count == 0) return default;
SqlConnection connection = adapter.SelectCommand.Connection;
if (!connection.State.Equals(ConnectionState.Open))
await connection.OpenAsync().ConfigureAwait(false);
// deleted rows
bool isThereDeletedRows = changedData.Rows.OfType<DataRow>().Any(row => row.RowState.Equals(DataRowState.Deleted));
var tasks = new List<Task<int>>();
if (isThereDeletedRows)
{
Task<int> task= adapter.DeleteCommand.ExecuteNonQueryAsync();
tasks.Add(task);
}
// updated rows
bool isThereModifiedRows = changedData.Rows.OfType<DataRow>().Any(row => row.RowState.Equals(DataRowState.Modified));
if (isThereModifiedRows)
{
Task<int> task= adapter.UpdateCommand.ExecuteNonQueryAsync();
tasks.Add(task);
}
// new added rows
bool isThereAddedRows = changedData.Rows.OfType<DataRow>().Any(row => row.RowState.Equals(DataRowState.Added));
if (isThereAddedRows)
{
Task<int> task= adapter.InsertCommand.ExecuteNonQueryAsync();
tasks.Add(task);
}
int[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
if (connection.State.Equals(ConnectionState.Open) ) connection.Close();
return results.Sum();
}
but I don't know who to assign the commands parameters dynamically like the normal Update method
Now
I did something like this
public static async Task<int> UpdateAsync(this SqlDataAdapter adapter,
DataTable changedData)
{
if (adapter == null) throw new
ArgumentNullException(nameof(adapter));
if (changedData == null) throw new
ArgumentNullException(nameof(changedData));
if (changedData.Rows.Count == 0) return default;
SqlConnection connection = adapter.SelectCommand.Connection;
if (!connection.State.Equals(ConnectionState.Open))
await connection.OpenAsync().ConfigureAwait(false);
var tasks = new List<Task<int>>();
// deleted rows
Task<int> deleteTask= DeleteRowsTask(adapter, changedData);
tasks.Add(deleteTask);
// updated rows
Task<int> modifyTask= ModifyRowsTask(adapter, changedData);
tasks.Add(modifyTask);
// new added rows
Task<int> addTask= AddRowsTask(adapter, changedData);
tasks.Add(addTask);
int[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
if (connection.State.Equals(ConnectionState.Open))
connection.Close();
return results.Sum();
}
private static async Task<int> AddRowsTask(SqlDataAdapter adapter, DataTable changedData)
{
var result = 0;
List<DataRow> addedRows = changedData.Rows.OfType<DataRow>().Where(row => row.RowState.Equals(DataRowState.Added)).ToList();
if (addedRows.Count == 0) return result;
foreach (DataRow row in addedRows)
{
AssignCommandParameters(adapter.InsertCommand, row);
Task<int> task = adapter.InsertCommand.ExecuteNonQueryAsync();
result += await task.ConfigureAwait(false);
}
return result;
}
private static void AssignCommandParameters(SqlCommand command, DataRow row)
{
foreach (SqlParameter parameter in command.Parameters)
parameter.Value = row[parameter.SourceColumn];
}
private static async Task<int> DeleteRowsTask(SqlDataAdapter adapter, DataTable changedData)
{
List<DataRow> deletedRows = changedData.Rows.OfType<DataRow>().Where(row => row.RowState.Equals(DataRowState.Deleted)).ToList();
var result = 0;
if (deletedRows.Count == 0) return result;
foreach (DataRow row in deletedRows)
{
AssignCommandParameters(adapter.DeleteCommand, row);
Task<int> task = adapter.DeleteCommand.ExecuteNonQueryAsync();
result += await task.ConfigureAwait(false);
}
return result;
}
private static async Task<int> ModifyRowsTask(SqlDataAdapter adapter, DataTable changedData)
{
var result = 0;
List<DataRow> modifiedRows = changedData.Rows.OfType<DataRow>().Where(row => row.RowState.Equals(DataRowState.Modified)).ToList();
if (modifiedRows.Count == 0) return result;
foreach (DataRow row in modifiedRows)
{
AssignCommandParameters(adapter.UpdateCommand, row);
Task<int> task = adapter.UpdateCommand.ExecuteNonQueryAsync();
result += await task.ConfigureAwait(false);
}
return result;
}
I did this for output parameters :
private static void AssignOutputParameters(SqlCommand command, DataRow row)
{
foreach (SqlParameter parameter in command.Parameters)
{
if (parameter.Direction == ParameterDirection.Output || parameter.Direction == ParameterDirection.InputOutput)
{
row[parameter.SourceColumn] = parameter.Value;
}
}
}
Related
I have an async operation that takes in an List of Airline as parameter and returns some data, and I have a list of Airlines for which I want to get data for.
However, if I can't get the data for all those Airlines after some predefined amount of time, I want to stop waiting and return something else to the user.
public async Task Run()
{
var watch = System.Diagnostics.Stopwatch.StartNew();
await RunAirline();
watch.Stop();
Console.WriteLine($"Total Execution Time: {watch.ElapsedMilliseconds + Environment.NewLine}");
//return $"Total Execution Time: {watch.ElapsedMilliseconds + Environment.NewLine}";
//Console.ReadLine();
}
private static async Task RunAirline()
{
try
{
List<string> AirlineList = GetAirLineCodes();
List<Task<WebsiteDataModel.WebsiteDataModel>> taskList = new List<Task<WebsiteDataModel.WebsiteDataModel>>();
foreach (string AirlineCode in AirlineList)
{
taskList.Add(Task.Run(() => CallindividualAirline(AirlineCode)));
}
var result = await Task.WhenAll(taskList);
foreach (WebsiteDataModel.WebsiteDataModel model in result)
{
Display(model);
}
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message.ToString());
}
}
private static List<string> GetAirLineCodes()
{
return new List<string>()
{
"A",
"B",
"C"
};
}
private static void Display(WebsiteDataModel.WebsiteDataModel result)
{
Console.WriteLine($"Website Content as {result.DataContent} , Website Name as : {result.WebsiteName} Status as : {result.Status} , Content length as : {result.WebsiteData.Length} ----- Error as : {result.error.FaultException.ToString()}." + Environment.NewLine);
}
private static WebsiteDataModel.WebsiteDataModel CallindividualAirline(string AirlineCode)
{
WebsiteDataModel.WebsiteDataModel LobjWebsiteDataModel = new WebsiteDataModel.WebsiteDataModel();
WebsiteDataModel.ErrorData LobjErrorData = new WebsiteDataModel.ErrorData();
try
{
switch (AirlineCode)
{
// calling Airline API...........
case "A":
ClsAirOne LobjAirOne = new ClsAirOne();
LobjWebsiteDataModel = LobjAirOne.GetAirDataData("https://book.xxxxx.com");
return LobjWebsiteDataModel;
case "B":
ClsAirTwo LobjAirTwo = new ClsAirTwo();
LobjWebsiteDataModel = LobjAirTwo.GetAirData("https://book.xxxxx.in");
return LobjWebsiteDataModel;
case "C":
ClsAirThree LobjAirThree = new ClsAirThree();
LobjWebsiteDataModel = LobjAirThree.GetAirData("https://xxxxx.in/");
return LobjWebsiteDataModel;
default:
return LobjWebsiteDataModel;
}
}
catch (Exception Ex)
{
LobjWebsiteDataModel.Status = "0";
LobjWebsiteDataModel.WebsiteData = "";
LobjErrorData.FaultException = "ERR-01" + Ex.Message.ToString();
LobjWebsiteDataModel.error = LobjErrorData;
return LobjWebsiteDataModel;
}
}
The best way to do this is to cancel each operation passed to the Task.WhenAll. You can create a cancellation token source with a timeout, and then pass its CancellationToken down to the methods that actually do the I/O.
E.g.:
public async Task Run()
{
...
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await RunAirline(cts.Token);
...
}
private static async Task RunAirline(CancellationToken cancellationToken)
{
...
foreach (string AirlineCode in AirlineList)
taskList.Add(Task.Run(() => CallindividualAirline(AirlineCode, cancellationToken)));
...
}
private static WebsiteDataModel.WebsiteDataModel CallindividualAirline(string AirlineCode, CancellationToken cancellationToken)
{
...
ClsAirOne LobjAirOne = new ClsAirOne();
LobjWebsiteDataModel = LobjAirOne.GetAirDataData("https://book.xxxxx.com", cancellationToken);
...
ClsAirTwo LobjAirTwo = new ClsAirTwo();
LobjWebsiteDataModel = LobjAirTwo.GetAirData("https://book.xxxxx.in", cancellationToken);
...
}
SqlBulkCopy.WriteToServerAsync does not respect the await keyword. Why?
Here is my code:
public async Task UpdateDBWithXML(Action<Func<DataTable, Task>> readXmlInBatches, string hashKey, string hash)
{
using (var transaction = this.Context.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
using (var bulk = new SqlBulkCopy((SqlConnection)this.Connection, SqlBulkCopyOptions.Default, (SqlTransaction)transaction.UnderlyingTransaction))
{
//this.Context.Database.ExecuteSqlCommand("DELETE FROM [dbo].[LegalContractorTemps]");
bulk.DestinationTableName = "LegalContractorTemps";
readXmlInBatches(async (DataTable table) =>
{
if (bulk.ColumnMappings.Count == 0)
{
foreach (DataColumn column in table.Columns)
{
bulk.ColumnMappings.Add(new SqlBulkCopyColumnMapping(column.ColumnName, column.ColumnName));
}
}
await bulk.WriteToServerAsync(table);
});
await this.Context.Database.ExecuteSqlCommandAsync(
"EXECUTE dbo.LegalContractorsDataSynchronize #hashKey, #hash",
new SqlParameter("#hashKey", hashKey),
new SqlParameter("#hash", hash)
);
transaction.Commit();
}
}
In the readXmlInBatches parameter I pass the following function as an argument:
public void ReadXMLInBatches(Func<DataTable, Task> processBatch)
{
int batchSize = 10000;
var table = new DataTable();
foreach (var col in columnNames)
{
table.Columns.Add(col);
}
using (var reader = new StreamReader(pathToXml, Encoding.GetEncoding(encoding)))
using (var xmlReader = XmlReader.Create(reader))
{
string lastElement = null;
DataRow lastRow = null;
while (xmlReader.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Element:
if (xmlReader.Name == "RECORD")
{
if (table.Rows.Count >= batchSize)
{
processBatch(table);
table.Rows.Clear();
}
lastRow = table.Rows.Add();
}
lastElement = xmlReader.Name;
break;
case XmlNodeType.Text:
ReadMember(lastRow, lastElement, xmlReader.Value);
break;
}
}
if (table.Rows.Count > 0)
{
processBatch(table);
table.Rows.Clear();
}
}
}
I have in the XML something about 1.7 million records. After my program have read a few batches I am getting the error:
System.Data.RowNotInTableException: 'This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.'
I researched the source code of the SqlBulkCopy. And found the method which throws an error:
public Task WriteToServerAsync(DataTable table, DataRowState rowState, CancellationToken cancellationToken) {
Task resultTask = null;
SqlConnection.ExecutePermission.Demand();
if (table == null) {
throw new ArgumentNullException("table");
}
if (_isBulkCopyingInProgress){
throw SQL.BulkLoadPendingOperation();
}
SqlStatistics statistics = Statistics;
try {
statistics = SqlStatistics.StartTimer(Statistics);
_rowStateToSkip = ((rowState == 0) || (rowState == DataRowState.Deleted)) ? DataRowState.Deleted : ~rowState | DataRowState.Deleted;
_rowSource = table;
_SqlDataReaderRowSource = null;
_dataTableSource = table;
_rowSourceType = ValueSourceType.DataTable;
_rowEnumerator = table.Rows.GetEnumerator();
_isAsyncBulkCopy = true;
resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); //It returns Task since _isAsyncBulkCopy = true;
}
finally {
SqlStatistics.StopTimer(statistics);
}
return resultTask;
}
I noticed the field _isBulkCopyingInProgress and decided to check it while debugging. And I found out that when the error is thrown the field is true. How is that possible? I would expect the bulk insert to happen first (before the execution continues and the WriteToServerAsync will be called a second time) since I add the await here: await bulk.WriteToServerAsync(table);.
What could I be missing?
You are passing an asynchronous function to ReadXMLInBatches, but it's execution isn't being awaited inside your method, therefore ReadXMLInBatches may terminate before all the calls to WriteToServerAsync have completed.
Try the following changes:
public async Task ReadXMLInBatchesAsync(Func<DataTable, Task> processBatch)
{
//...
await processBatch(table);
//...
}
public async Task UpdateDBWithXML(Func<Func<DataTable, Task>, Task> readXmlInBatches, string hashKey, string hash)
{
//...
await readXmlInBatches(async (DataTable table) =>
//...
}
This is my code that works. I wouldn't have to do this if it weren't task based async, but using Monitor.Enter/Exit results in this problem Object synchronization method was called from an unsynchronized block of code. Exception on Mutex.Release()
People have mentioned using AutoResetEvent, and SemaphoreSlim, but I'm not quite sure which pattern fits.
private bool fakelock;
internal async Task<byte[][]> ExchangeCore(byte[][] apdus)
{
if (apdus == null || apdus.Length == 0)
return null;
List<byte[]> resultList = new List<byte[]>();
var lastAPDU = apdus.Last();
while (fakelock)
{
await Task.Delay(100);
}
fakelock = true;
foreach (var apdu in apdus)
{
await WriteAsync(apdu);
var result = await ReadAsync();
resultList.Add(result);
}
fakelock = false;
return resultList.ToArray();
}
You could maybe use a SemaphoreSlim which supports async.
private static SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
internal async Task<byte[][]> ExchangeCore(byte[][] apdus)
{
if (apdus == null || apdus.Length == 0)
return null;
await Semaphore.WaitAsync();
try
{
List<byte[]> resultList = new List<byte[]>();
foreach (var apdu in apdus)
{
await WriteAsync(apdu);
var result = await ReadAsync();
resultList.Add(result);
}
return resultList.ToArray();
}
finally
{
Semaphore.Release();
}
}
My requirement is, need to render all framework elements which are bound with data using async and await.
Tried below possibilities:
If i am using asyncmethod().result it will block the UI and it
will be waiting for a long time to completion.
If I am using await keyword the controlstoconfigure foreach(2nd
one) will be hit for before completion of parallel.foreach()
So can you please suggest me for rendering elements, call the 2nd for each after completion of parallel foreach or without blocking UI?
Code snippet:
1)Renderingwidget - this method is used to get framework elements in the collection which is bound with data.
2)FetchData - this method is used to get the data from the server for particular framework element.
3)GetTablefromserver - this method is used to get the data based on queries.
public async void RenderingWidget()
{
ConcurrentDictionary<string, EngineChangedEventArgs> controlsToConfigure = new ConcurrentDictionary<string, EngineChangedEventArgs>();
foreach (var engines in MainWindow.ViewModel.RelationalDataManagerList.Values)
{
Dictionary<string, FrameworkElement> controlcollection = CurrentDashboardReport.WidgetCollection;
Parallel.ForEach(controlcollection, async item =>
{
try
{
try
{
controlsToConfigure.TryAdd(item.Key, await FetchDataInParallel(MainWindow.ViewModel.RelationalDashboardReportList[engines.DataSourceName].Reports, item.Key,));
}
catch (Exception ex)
{
ExceptionLog.WriteExceptionLog(ex, null);
throw new ParallelException(ex, item.Key);
}
}
catch (ParallelException ex)
{
exceptions.Enqueue(ex);
ExceptionLog.WriteExceptionLog(ex, GeneratedQueryText);
}
});
if (exceptions.Count > 0)
{
foreach (var nonRenderedControls in exceptions)
{
controlsToConfigure.TryAdd(nonRenderedControls.ReportName, await FetchDataInParallel(CurrentDashboardReport.Reports, nonRenderedControls.ReportName));
}
}
}
foreach (var control in controlsToConfigure)
{
(CurrentDashboardReport.WidgetCollection[control.Key] as DashboardDataControl).CurrentDashboardReport.OnActiveColumnsChanged(control.Value);
}
}
public async Task<EngineChangedEventArgs> FetchDataInParallel(RelationalReportCollection reports, string myReport)
{
var dataTable = await GetTableFromServer(reports.Query);
eventArgs = new EngineChangedEventArgs
{
ItemsSource = dataTable
};
return eventArgs;
}
public async Task<DataTable> GetTableFromServer(string query)
{
var resultTable = new DataTable
{
Locale = CultureInfo.InvariantCulture
};
SqlConnection sqlConnection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand(query, sqlConnection)
SqlDataReader dataReader = null;
try
{
if (sqlConnection.State != ConnectionState.Open)
{
sqlConnection.Open();
}
dataReader =await command.ExecuteReaderAsync();
resultTable.Load(dataReader);
return resultTable;
}
finally
{
if (dataReader != null)
{
dataReader.Dispose();
}
command.Dispose();
return resultTable;
}
}
Parallel.ForEach doesn't play well with async/await as you release the thread for the duration of the async call. Please refer to the following question for more information about this: Nesting await in Parallel.ForEach
You may just call Parallel.ForEach on a thread pool thread and then use the Task.WhenAll method to wait until all tasks have completed Before you continue:
public async void RenderingWidget()
{
ConcurrentDictionary<string, EngineChangedEventArgs> controlsToConfigure = new ConcurrentDictionary<string, EngineChangedEventArgs>();
List<Task> tasks = new List<Task>();
foreach (var engines in MainWindow.ViewModel.RelationalDataManagerList.Values)
{
Dictionary<string, FrameworkElement> controlcollection = CurrentDashboardReport.WidgetCollection;
tasks.Add(Task.Run(() =>
{
Parallel.ForEach(controlcollection, item =>
{
try
{
try
{
controlsToConfigure.TryAdd(item.Key, FetchDataInParallel(MainWindow.ViewModel.RelationalDashboardReportList[engines.DataSourceName].Reports, item.Key).Result);
}
catch (Exception ex)
{
ExceptionLog.WriteExceptionLog(ex, null);
throw new ParallelException(ex, item.Key);
}
}
catch (ParallelException ex)
{
exceptions.Enqueue(ex);
ExceptionLog.WriteExceptionLog(ex, GeneratedQueryText);
}
});
if (exceptions.Count > 0)
{
foreach (var nonRenderedControls in exceptions)
{
controlsToConfigure.TryAdd(nonRenderedControls.ReportName, FetchDataInParallel(CurrentDashboardReport.Reports, nonRenderedControls.ReportName).Result);
}
}
}));
}
await Task.WhenAll(tasks);
foreach (var control in controlsToConfigure)
{
(CurrentDashboardReport.WidgetCollection[control.Key] as DashboardDataControl).CurrentDashboardReport.OnActiveColumnsChanged(control.Value);
}
}
public async Task<EngineChangedEventArgs> FetchDataInParallel(RelationalReportCollection reports, string myReport)
{
var dataTable = await GetTableFromServer(reports.Query).ConfigureAwait(false);
return new EngineChangedEventArgs
{
ItemsSource = dataTable
};
}
I Created several Task in the way below. But it seems WaitAll is not working. It is sending response without wait. Anything goes wrong here?
private void GetItemsPrice(IEnumerable<Item> items, int customerNumber)
{
try
{
var tasks = new List<Task>();
for (var i = 0; i < items.Count(); i += 50)
{
var newTask = DoGetItemsPrice(items.Skip(i).Take(50), customerNumber);
tasks.Add(newTask);
}
Task.WaitAll(tasks.ToArray());
}
catch (Exception ex)
{
ErrorLog.WriteLog(GetType().Name, "GetItemsPrice", string.Format("customerNumber={0}", customerNumber), ex.Message);
}
}
private static Task DoGetItemsPrice(IEnumerable<Item> items, int customerNumber)
{
return Task.Factory.StartNew(() =>
{
var sxApiObj = new SxApiService();
var request = new OEPricingMultipleRequest();
request.customerNumber = customerNumber;
request.arrayProduct =
items.Select(
itemCode =>
new OEPricingMultipleinputProduct
{
productCode = itemCode.ItmNum,
quantity = itemCode.Quantity,
warehouse = ConfigurationVariables.DefaultWareHouse
}).ToArray();
var response = sxApiObj.OEPricingMultiple(ConfigurationVariables.SfAppServer,
ConfigurationVariables.SfUserId,
ConfigurationVariables.SfPassword,
request);
if (response.arrayPrice != null)
{
foreach (var priceData in response.arrayPrice)
{
var productCode = priceData.productCode;
var item = items.FirstOrDefault(itm => itm.ItmNum == productCode);
if (item == null) continue;
item.ItmListPrice1 = priceData.price.ToString("c", ConfigurationVariables.UsCulture);
item.ItmListPrice2 = priceData.discountAmount.ToString("c", ConfigurationVariables.UsCulture);
item.ItmListPrice3 = priceData.extendedAmount.ToString("c", ConfigurationVariables.UsCulture);
item.Quantity = priceData.netAvailable;
}
}
});
}
There is nothing wrong with my question. WaitAll works fine and the code also correct.