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) =>
//...
}
Related
public async Task<JobViewModel> Handle(AddJobCommand command, CancellationToken cancellationToken)
{
if (command.JobViewModel == null) throw new InvalidOperationException("Empty request.");
var jobViewModel = command.JobViewModel;
try
{
var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel);
_context.Set<DataAccess.Domain.Lab.Job>().Add(job);
if (job.Notes!= null)
{
var newNote = job.Notes.FirstOrDefault(n => n.IsNew);
if (newNote != null)
{
newNote.JobId = job.Id;
_context.Set<DataAccess.Domain.Lab.JobNote>().Attach(newNote);
_context.Entry(newNote).State = EntityState.Added;
}
}
await OnBeforeAdd(job); // job.Name = await GenerateJobName();
await _context.SaveChangesAsync();
jobViewModel.Id = job.Id;
return jobViewModel;
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
catch (DbUpdateException e)
{
Console.WriteLine(e.Message);
throw;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw;
}
}
protected async Task OnBeforeAdd(DataAccess.Domain.Lab.Job job)
{
if (string.IsNullOrWhiteSpace(job.Name))
{
job.Name = await GenerateJobName();
}
}
private async Task<string> GenerateJobName()
{
Func<int, int, string> composeName = (year, number) => string.Format("S{0:D2}{1:D3}", year, number);
int currentYear = DateTime.UtcNow.Year % 100;
int newJobNumber = 501;
//if (_context.Jobs!= null)
// {
// string maxJobNumber = await _context.Jobs.MaxAsync(j => j.Name);
string maxJobNumber = await _context.Jobs.MaxAsync(j=>j.Name);
if (!string.IsNullOrWhiteSpace(maxJobNumber))
{
var matches = Regex.Matches(maxJobNumber, "S" + currentYear + "(?<num>[0-9]{3})");
if (matches.Count == 1)
{
newJobNumber = int.Parse(matches[0].Groups["num"].Value) + 1;
}
}
// }
string newName = composeName(currentYear, newJobNumber);
while (await _context.Jobs.AnyAsync(j => j.Name == newName))
{
newJobNumber++;
newName = composeName(currentYear, newJobNumber);
}
return newName;
}
}
}
Below line give error:
string maxJobNumber = await _context.Jobs.MaxAsync(j=>j.Name);
On running the code it throws below exception "A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe."
Also the same line "string maxJobNumber = await _context.Jobs.MaxAsync(j=>j.Name);" shows warning message as
converting null literal or possible null value to non-nullable type string error . Can this be an issue?
Some comments about the above code:
There's no need for these lines:
if (job.Notes!= null)
{
var newNote = job.Notes.FirstOrDefault(n => n.IsNew);
if (newNote != null)
{
newNote.JobId = job.Id;
_context.Set<DataAccess.Domain.Lab.JobNote>().Attach(newNote);
_context.Entry(newNote).State = EntityState.Added;
}
}
Once you've added the job to the DbSet, EF will automatically figure out that its JobNotes need to be added as well. And, it will determine there state appropriately.
This is the pattern I would use:
public async Task<JobViewModel> Handle(AddJobCommand command, CancellationToken cancellationToken)
{
var jobViewModel = command.JobViewModel;
var job = _mapper.Map<DataAccess.Domain.Lab.Job>(jobViewModel);
job.Name = await GenerateJobName(job.Name);
await _context.Set<DataAccess.Domain.Lab.Job>().AddAsync(job);
await _context.SaveChangesAsync();
jobViewModel.Id = job.Id;
return jobViewModel;
}
private async Task<string> GenerateJobName(DataAccess.Domain.Lab.Job job)
{
if (!string.IsNullOrWhiteSpace(job.Name)) return job.Name;
...
}
You may be overthinking all the try-catch stuff. It is more than half the code and is obscuring the real logic. I would move that out to a higher location and concentrate on making this do the one thing it's intended to do.
The reason the code performs differently in PROD is due to a race condition. There's no debugger in PROD, so your first calls are finishing with enough time to start the second calls.
Hi I have Controller method as below
[HttpPost]
public JsonResult Post(string vehiclesString, string Entity, int EntityId, ApplicationUser CurrentUser)
{
//https://stackify.com/understanding-asp-net-performance-for-reading-incoming-data/
List<Vehicle> vehicles = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Vehicle>>(vehiclesString);
InputFieldController c = new InputFieldController();
var errors = new List<string>();
try
{
//let's get model for each of the input field
InputFieldController icController = new InputFieldController();
List<InputField> fields = icController.GetNamesValues("VehicleField", -1, "Vehicle", 0);
foreach (Vehicle vehicle in vehicles)
{
//convert array of strings into array of input fields
if (fields.Count != vehicle.ValueStrings.Count)
{
throw new Exception("Vehicle columns mismatch. Expected "
+ fields.Count + " fields, but received " + vehicle.ValueStrings.Count);
}
for (int i = 0; i < fields.Count; i++)
{
InputField field = fields[i];
string cell = vehicle.ValueStrings[i];
if ((cell != null || cell != String.Empty) && (field.Type == "radio" || field.Type == "dropdown"))
{
var f = field.InputDropdowns.Where(x => x.Name == cell).FirstOrDefault();
if (f != null)
{
field.InputValue.InputDropdownId = f.InputDropdownId;
}
else
field.InputValue.InputDropdownId = null;
}
else
{
field.InputValue.Value = cell;
}
vehicle.Values.Add(field);
}
vehicle.Blob = Newtonsoft.Json.JsonConvert.SerializeObject(vehicle.Values);
Vehicle v = new Vehicle();
if (vehicle.VehicleId == 0)
{
v = this.DomainLogicUnitOfWork.VehicleManager.Create(vehicle, Entity, EntityId);
}
}
JsonResult data = Json(new
{
success = true,
});
List<Vehicle> vehiclesList = this.DomainLogicUnitOfWork.VehicleManager.List(Entity, EntityId);
if (vehiclesList != null)
foreach (Vehicle v in vehiclesList)
{
if ((v != null) && (v.Blob != null))
v.Values = Newtonsoft.Json.JsonConvert.DeserializeObject<List<InputField>>(v.Blob);
}
//Task task = Task.Run(async () => await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(Entity, EntityId));
/*
* Here I have to call the this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(string Entity, int EntityId) asynchronously
* but return the data without waiting for the CreateOrUpdate to complete
*/
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(vehiclesList, Entity, EntityId);
});
return data;
}
catch (Exception ex)
{
LogHandler.LogError(9000, "Error updating input fields", ex);
errors.Add("Error 9000:" + ex.Message);
return Json(new
{
error = ex.Message
});
}
}
And I have CreateOrUpdate method defined as below in VehicleInfoManager class
public async Task CreateOrUpdate(string Entity, int EntityId)
{
//do some stuff
var task = Task.Run(() => Test(Entity, EntityId));
//do other stuff
await task;
//some more stuff
}
And Test method is as follows
private void Test(string Entity, int EntityId)
{
List<VehicleInfo> addList; List<VehicleInfo> updateList;
try
{
this.GetAddAndUpdateList(Entity, EntityId, out addList, out updateList);
if ((addList != null) && (addList.Count > 0))
using (var cont = this.UnitOfWork.Context)
{
foreach (var a in addList)
{
cont.VehicleInfos.Add(a);
}
cont.SaveChanges();
}
if ((updateList != null) && (updateList.Count > 0))
using (var cont = this.UnitOfWork.Context)
{
foreach (var a in updateList)
{
var aa = cont.VehicleInfos?.Where(x => x.VehicleInfoId == a.VehicleInfoId)?.FirstOrDefault();
aa.Address_City = a.Address_City;
aa.Address_Country = a.Address_Country;
aa.Address_StateCode = a.Address_StateCode;
aa.Address_Street1 = a.Address_Street1;
aa.Address_Street2 = a.Address_Street2;
aa.Address_Zip = a.Address_Zip;
aa.ChassisYear = a.ChassisYear;
aa.EngineFamilyName = a.EngineFamilyName;
aa.Entity = a.Entity;
aa.EntityId = a.EntityId;
aa.InputFieldEntity = a.InputFieldEntity;
aa.InputFieldEntityId = a.InputFieldEntityId;
aa.InputFieldGroup = a.InputFieldGroup;
aa.LicensePlate = a.LicensePlate;
aa.Manufacturer = a.Manufacturer;
aa.ModelYear = a.ModelYear;
aa.PurchasedDate = a.PurchasedDate;
aa.RegHoldClearBy = a.RegHoldClearBy;
aa.RegHoldClearDate = a.RegHoldClearDate;
aa.RegHoldComment = a.RegHoldComment;
aa.RegHoldSet = a.RegHoldSet;
aa.RegHoldSetBy = a.RegHoldSetBy;
aa.RegHoldSetDate = a.RegHoldSetDate;
aa.TrailerPlate = a.TrailerPlate;
aa.UpdatedBy = a.UpdatedBy;
aa.UpdatedDate = a.UpdatedDate;
aa.VehicleId = a.VehicleId;
aa.VehicleOperator = a.VehicleOperator;
aa.VehicleOwner = a.VehicleOwner;
aa.VIN = a.VIN;
}
cont.SaveChanges();
}
}
catch (Exception ex)
{
ARB.Logging.LogHandler.LogError(9001, "CreateOrUpdate(string Entity, int EntityId) in class VehicleInfoManager", ex);
throw ex;
}
}
What I want is, I want two things here
the Post method to call or start the CreateOrUpdate method as background call but instead of waiting until the CreateOrUpdate method finishes, it should return the result data to UI and continue the big task CreateOrUpdate in the background.
Is there anyway to start the background method CreateOrUpdate after sometime like (10 mins etc) post method returns to UI, if it can't be done, its OK we don't have to worry but just asking if there is anyway to trigger this from within the same application
When I implemented it in the above way, even after using System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem I am still getting the null http context at the following location
user = System.Web.HttpContext.Current.User.Identity.Name; url = System.Web.HttpContext.Current.Request.Url.ToString();
System.Web.HttpContext.Current is coming out as null.
and the application is breaking,
I chaned my async call to the following to use HostingEnvironment.QueueBackgroundWorkItem, still the same htt current context is coming out as null, any help please
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(Entity, EntityId);
});
Thank you
System.Web.HttpContext.Current is maintained by the ASP.NET's SynchronizationContext.
When you start a new Task, the code will be executing on another thread pool thread without a SynchronizationContext and the value of System.Web.HttpContext.Current is not safe to use, whatever its value.
When the execution of the action method (Post) ends, the request will end and the HttpContext instance will be invalid, even if you mange to get a reference to it.
Also, there is no guarantee that that code you posted to the thread pool will run to complete, since it's out of ASP.NET control and ASP.NET won't be aware of it if IIS decides, for some reason, to recycle the application pool or the web application.
If you to post background work, use HostingEnvironment.QueueBackgroundWorkItem. Beware if its constraints.
I am using Microsoft.AnalysisServices.AdomdClient.dll file to connect to Azure Analysis Service for executing DAX queries in Azure function and I need it to spit out in a JSON. Below is how am doing but when there are vast records I see delay in converting the response to json. Analysis service response in 2 secs but masking the response to json is taking more than 40secs. Can someone help suggesting a better way
AdomdCommand cmd = new AdomdCommand(query, _connect);
public List<Dictionary<string, object>> Results { get; } = new List<Dictionary<string, object>>();
var reader = cmd.ExecuteReader();
var schemeTable = reader.GetSchemaTable();
ISet<string> columnSet = new HashSet<string>();
foreach (DataRow row in schemeTable.Rows)
{
String columnName = row[0].ToString();
columnSet.Add(columnName);
}
while (reader.Read())
{
Dictionary<string, object> columns = new Dictionary<string, object>();
foreach (string columnName in columnSet)
{
var value = reader[reader.GetOrdinal(columnName)];
if (value != null)
{
columns.Add(columnName, value);
}
else
{
columns.Add(columnName, null);
}
}
Results.Add(columns);
}
JsonConvert.SerializeObject(Results)
I have a sample for this on GitHub: microsoft/azure-analysis-services-http-sample. It streams the results from an AdomdDataReader to an output stream as JSON. The Stream can be a MemoryStream or (in my case) an HttpResponse stream.
public static async Task WriteResultsToStream(object results, Stream stream, CancellationToken cancel)
{
if (results == null)
{
return;
}
if (results is AdomdDataReader rdr)
{
var encoding = new System.Text.UTF8Encoding(false);
using (var tw = new StreamWriter(stream,encoding,1024*4,true))
using (var w = new Newtonsoft.Json.JsonTextWriter(tw))
{
await w.WriteStartObjectAsync(cancel);
var rn = "rows";
await w.WritePropertyNameAsync(rn);
await w.WriteStartArrayAsync(cancel);
while (rdr.Read())
{
await w.WriteStartObjectAsync(cancel);
for (int i = 0; i < rdr.FieldCount; i++)
{
string name = rdr.GetName(i);
object value = rdr.GetValue(i);
await w.WritePropertyNameAsync(name, cancel);
await w.WriteValueAsync(value, cancel);
}
await w.WriteEndObjectAsync(cancel);
}
await w.WriteEndArrayAsync(cancel);
await w.WriteEndObjectAsync(cancel);
await w.FlushAsync();
await tw.FlushAsync();
await stream.FlushAsync();
}
}
else if (results is CellSet cs)
{
throw new NotSupportedException("CellSet results");
}
else
{
throw new InvalidOperationException("Unexpected result type");
}
}
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;
}
}
}
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
};
}