Odd effect of async / await on loading a datatable - c#

I'm struggling to understand what is going on with this code. I've done async/await for a while now but haven't stumbled on this issue before (unless I'm just not seeing what I've done)
I'm loading a CSV file into a DataTable then I want to pull the list of column names from the DataTable to manipulate later.
In the first method, I load the CSV async then assign the DataGrid to view the data. I wasn't able to use the _ColumnList = ... in the first method because for whatever reason, the "_dtSampleCSV.Columns" did not resolve; it's not seeing that it's a DataTable.
So I moved that line to another method where it pulls the column names and assigns those names to another DataGrid. When it hits this method, there's no data in the _dtSampleCSV DataTable (no rows, no columns), yet the first DataGrid does populate with data.
I've always assumed that using an "await" waits for the thread to complete before proceeding. So either that's not true, or the thread did complete but the DataTable wasn't assigned yet.
I can only assume the data doesn't exist at that point, but the DataGrid's source is set so when the data is populated; it shows. But how do I prevent execution if await isn't completing the dataload into the DataTable?
private DataTable _dtSampleCSV = new DataTable();
private List<string> _ColumnList = new List<string>();
private async void btnAdd_Click(object sender, RoutedEventArgs e)
{
spNew.Visibility = Visibility.Visible;
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
tbFilePath.Text = openFileDialog.FileName;
var _dtSampleCSV = await LoadCSVAsync(openFileDialog.FileName);
dgCSVExample.DataContext = _dtSampleCSV;
dgCSVExample.Visibility = Visibility.Visible;
SetColumnGrid();
}
}
private void SetColumnGrid()
{
_ColumnList = (from DataColumn dc in _dtSampleCSV.Columns.Cast<DataColumn>() select dc.ColumnName).ToList();
dgColumnNames.DataContext = _ColumnList; // breakpoint set here to look at _dtSampleCSV and no data exists at runtime
dgColumnNames.Visibility = Visibility.Visible;
}
// csv method
public static async Task<DataTable> LoadCSVAsync(string filePath)
{
using (var reader = File.OpenText(filePath))
{
var fileText = await reader.ReadToEndAsync();
using (TextReader sr = new StringReader(fileText))
{
var adapter = new GenericParsing.GenericParserAdapter(sr);
adapter.FirstRowHasHeader = true;
adapter.MaxBufferSize = 4096;
adapter.MaxRows = 3;
DataTable dtProcess = adapter.GetDataTable();
return dtProcess;
}
}
}

Your LoadCSVAsync method is static, which explains why it can't access the _dtSampleCSV instance field.
Your btnAdd_Click method creates a local variable called _dtSampleCSV, assigns that to the dgCSVExample.DataContext, and then throws it away. Data from that local variable will not be available in the _dtSampleCSV field.
Change your method so that it assigns the data to the field, rather than creating a local variable:
// Remove this:
// var _dtSampleCSV = await LoadCSVAsync(openFileDialog.FileName);
// Use this:
_dtSampleCSV = await LoadCSVAsync(openFileDialog.FileName);
NB: You should try to avoid async void methods wherever possible.

Related

How to add a progressbar and status to a process that doesnt have a count like copying files does?

So i had posted a question on getting my sample project working and it now works.. Sample Project
And thats great because as in that example i have a production project that requires copying files, so that will work great for that one.
But this question is about displaying a progress bar to a process that im not clear on how to implement.
Im reading a excel file using closedxml, i read that file into a datatable in order to perform some filtering and other things in order to populate some listboxes on my form, how can my sample code in my other post be implemented against the creation of 5 or 6 data tables?
I can provide some of the datatable creation methods, but the over all code is close to 600 lines right now and not finished yet.. so below is a stripped down sample of the current code im working with..
private void sample()
{
string plink = #"C:\Test\Sizes.xlsx";
string[] DistinctDept = { "Dept Code", "Dept Description" };
DataTable ListDept = GetDistinctRecords(LoadExceltoDatatable(plink), DistinctDept);
ListDept.Columns.Add(new DataColumn("DeptCombo", typeof(string), "'('+[Dept Code] +') ' + [Dept Description]"));
if (string.IsNullOrEmpty(ListDept.Rows[0]["Dept Code"].ToString()))
{
ListDept.Rows[0].Delete();
ListDept.AcceptChanges();
}
lbDept.DataSource = ListDept;
lbDept.DisplayMember = "DeptCombo";
lbDept.ClearSelected();
}
public static DataTable GetDistinctRecords(DataTable dt, string[] Columns)
{
DataTable dtUniqRecords = new DataTable();
dtUniqRecords = dt.DefaultView.ToTable(true, Columns);
return dtUniqRecords;
}
public static DataTable LoadExceltoDatatable(string sizeoptcalc)
{
using (var wb = new XLWorkbook(sizeoptcalc, XLEventTracking.Disabled))
{
var ws = wb.Worksheet(1);
var foundMonth = ws.Search("Month", System.Globalization.CompareOptions.OrdinalIgnoreCase);
var monthRow = foundMonth.Last().Address; // A11
var lastcell = ws.LastCellUsed().Address; // BC3950
DataTable dataTable = ws.Range(monthRow, lastcell).RangeUsed().AsTable().AsNativeDataTable();
return dataTable;
}
}
Can this be changed to report the progress? I mean in some cases the excel files are large and do take some time to fill in my listboxes.
Here are more of the datatable creations that i would like to account for the overall progress of them

recover "DbContext" lost out after a method

I need your help.
I begin to ASP.net and I fail to retrieve a "dbcontext" to display my request in a "datagrid". Here is my code:
public IQueryable<DiagTab> Clooper(string m_ValEnvoi)
{
string Ladatatable = m_ValEnvoi;
using (var db = new DiagEntities())
{
var secki = db.DiagTabs.Where(Ladatatable); // Ladatatabase = Dynamic LinQ
return secki;
}
I call this way (no error)
TheLoop pilou = new TheLoop();
pilou.Clooper(Valtest);
var olami = pilou.Clooper(Valtest);
but if i try this:
var selection_click = olami;
GridView1.DataSource = selection_click.ToList();
GridView1.DataBind();
the code is interrupted and displays "Could not complete the operation because the DbContext has been deleted".
Is it possible to get the paste has Dbcontext for this request?
thanks for your help
You should call ToList() inside the method, before disposing the DbContext.
Don't use using which dispose the DiagEntities and instead of it, if you want to use it in several methods just declare a property for your DiagEntities in the class constructor.
public IQueryable<DiagTab> Clooper(string m_ValEnvoi)
{
string Ladatatable = m_ValEnvoi;
var secki = db.DiagTabs.Where(Ladatatable); // Ladatatabase = Dynamic LinQ
return secki;
}

Make changes to referenced datatable in Parallel.ForEach loop

I'm trying to create a method which when passed a datatable reference with pingable host names, tries to ping each of the hosts and then change the value of corresponding column and row depending on ping success.
However i cannot use references in Parallel.ForEach method. Is there any way i could make this work?
Here's my code:
public void checkON(ref DataTable PCS)
{
Parallel.ForEach(PCS.AsEnumerable(), pc =>
{
string loopIp = pc["Name"].ToString();
if (PingIP(loopIp))
{
DataRow[] currentpc = PCS.Select("Name = '{0}'", loopIp);
currentpc[0]["Online"] = "ON";
}
else
{
DataRow[] currentpc = PCS.Select("Name = '{0}'", loopIp);
currentpc[0]["Online"] = "OFF";
}
}
);}
Unless code explicitly says that it is thread-safe, you should assume it is not - and therefore access must be synchronized. The ref in your code serves no purpose. Each pc is a DataRow, so you can access that directly:
string loopIp;
lock(someLockObject) {
loopIp = (string)pc["Name"];
}
string online = PingIP(loopIp) ? "ON" : "OFF";
lock(someLockObject) {
pc["Online"] = online;
}
where someLockObject is shared between all of the callers, because you can't make assumptions about the threading model:
object someLockObject = new object();
Parallel.ForEach(PCS.AsEnumerable(), pc =>
{ ... });
In particular, you can't just lock the row because DataTable doesn't store data in rows (it stores it in columns; no, really).

Trying to iterate through ListBox, Error : Specified cast is not valid

I have a method in another class i'm using to send data to a database. That method is here as well.
public Int32 AddOrder(clsStock NewItem)
{
//this function takes the data passed via NewItem
//and passes it to the parameters for the stored procedure
//
//create an instance of the data dictionary
clsDataDictionary DD = new clsDataDictionary();
//create an instance of the object class
Int32 ReturnValue;
//create an instance of the data conduit
clsDataConduit Items = new clsDataConduit();
//pass the data for this address
Items.AddParameter(DD.sproc_tblOrders_Add_AuthId, NewItem.AuthId);
Items.AddParameter(DD.sproc_tblOrders_Add_ItemId, NewItem.ItemId);
Items.AddParameter(DD.sproc_tblOrders_Add_DateOrdered, NewItem.DateOrdered);
Items.AddParameter(DD.sproc_tblOrders_Add_Cancel, NewItem.Cancel);
//execute the stored procedure
ReturnValue = Items.Execute(DD.sproc_tblOrders_Add);
//return the primary key value
return ReturnValue;
}
The method on my aspx page which i'm using to iterate through my listbox and execute that method for each item in the listbox is here as well.
protected void btnSubmit_Click1(object sender, EventArgs e)
{
//create an instance of the collection class
clsStockCollection Items = new clsStockCollection();
foreach(int id in lstAdded.Items)
{
TheItem.AuthId = 5;
TheItem.ItemId = Convert.ToInt32(lstAdded.Items[id].Value);
TheItem.Cancel = "false";
Items.AddOrder(TheItem);
}
Response.Redirect("Order.aspx");
}
When I run my website and hit the btnSubmit it's giving the following error :
"Specified cast is not valid" that is on the method on the aspx page (the 2nd pastebin file)
Any idea why this is?
It should be like this
foreach(ListItem item in lstAdded.Items)
{
TheItem = new clsStock();
TheItem.AuthId = 5;
TheItem.ItemId = Convert.ToInt32(item.Value);
TheItem.Cancel = "false";
Items.AddOrder(TheItem);
}
You are iterating the ListBox.Items through an int type field. ListBox.Items is a ListItemCollection, what you can do is use implicitly typed variable using var keyword, like:
foreach(var id in lstAdded.Items)
{
TheItem.AuthId = 5;
TheItem.ItemId = Convert.ToInt32(id.Text); //Change here
TheItem.Cancel = "false";
Items.AddOrder(TheItem);
}
Currently it appears you are considering it as an index in foreach loop, instead its a single item from the lstAdded

ListView returns null on selected items that should contain data

I have code which takes in data from Flickrs Rest service and populates a ListView. This code works fine and when I run my app I can search for photos and be displayed a list of them. However I want to then get a single photos data but when I try to access this data from the ListView it's completely empty (Iv debugged it and it just contains null entries). I don't have a lot of experience with C# so could anyway advise me as to why I would be getting null results?
private async void ParseFlickrResponse(HttpResponseMessage response)
{
XDocument xml = XDocument.Parse(await response.Content.ReadAsStringAsync());
var photos = from results in xml.Descendants("photo")
select new FlickrImage
{
ImageId = results.Attribute("id").Value.ToString(),
FarmId = results.Attribute("farm").Value.ToString(),
ServerId = results.Attribute("server").Value.ToString(),
Secret = results.Attribute("secret").Value.ToString(),
Title = results.Attribute("title").Value.ToString()
};
FlickrListView.ItemsSource = photos;
}
EDITED
Current code:
enter code here:
private async void ParseFlickrResponse(HttpResponseMessage response)
{
XDocument xml = XDocument.Parse(await response.Content.ReadAsStringAsync());
var photos = from results in xml.Descendants("photo").ToList()
select new FlickrImage
{
ImageId = results.Attribute("id").Value.ToString(),
FarmId = results.Attribute("farm").Value.ToString(),
ServerId = results.Attribute("server").Value.ToString(),
Secret = results.Attribute("secret").Value.ToString(),
Title = results.Attribute("title").Value.ToString()
};
FlickrListView.ItemsSource = new ObservableCollection<FlickrImage>(photos);
}
private void GetPhotoSource(object sender, ItemClickEventArgs e)
{
int inx = FlickrListView.SelectedIndex;
// FlickrImage t = lst.First();
FlickrImage t = lst.ElementAt(inx);
MyImage.Source = new BitmapImage(new Uri(t.ImageUrl.ToString(), UriKind.Absolute));
}
Try adding a .ToList() at the end of your LINQ.
*EDIT (comments summary)
You are handling ItemClick event which seems to be raised before the selection properties on the ListViewBase change. Since these are not updated at this point - you can check e.ClickedItem and cast it to FlickrImage to get your clicked item.
If you do want to work with selection properties - you should be handling the SelectionChanged event.

Categories

Resources