We have a web service that picks up a report from a server and returns it to the Silverlight GUI. In the GUI we have an event handler that calls the following function:
private void PopulateDataGrids()
{
try
{
reportTitle = reportDataUtil.ReportHeader.Rows.First(r => r.Items[ReportTools.FIELDNAME_STRING].ToString() == ReportTools.FIELDVALUE_TITLE).Items[ReportTools.FIELDVALUE_STRING].ToString();
dg_Report_Header.DataSource = reportDataUtil.ReportHeader;
dg_Report_Header.CanUserSortColumns = false;
dg_Report_Header.CanUserReorderColumns = false;
dg_Report_Header.CanUserResizeColumns = true;
dg_Report_Header.DataBind();
dg_Report_Header.UpdateLayout();
dg_Report_Detail.DataSource = reportDataUtil.ReportDetails;
dg_Report_Detail.CanUserSortColumns = true;
dg_Report_Detail.CanUserReorderColumns = true;
dg_Report_Detail.CanUserResizeColumns = true;
dg_Report_Detail.DataBind();
dg_Report_Detail.UpdateLayout();
//Hide the ElementID colmun
if (dg_Report_Detail.Columns.Count > 0)
{
var col = dg_Report_Detail.Columns.FirstOrDefault(dc => dc.Header.ToString() == NetVisSolution.Reports.ReportTools.COLUMNNAME_ELEMENTID);
if (col != null)
{
col.Visibility = System.Windows.Visibility.Collapsed;
}
}
}
catch (Exception)
{
ClearResources();
closeReportDataViewer();
ErrorWindow.CreateNew("Unable to open selected report data, the file maybe corrupted or not \nin the server specified location.");
//throw new Exception(ex.Message, ex.InnerException);
}
}
Trouble is, the detail table can be 14000 rows long, taking up more than a megabyte. When such a report is loaded, the dg_Report_Detail.DataBind(); call takes several minutes and locks up the GUI.
Is there a simple way to do this asynchronously or in such a manner that the application remains responsive?
Thanks in advance,
--- Alistair.
Although DataGrid implements Row Virtualization, it can still spend a lot of time calculating the height of each row separately. Assigning a RowHeight will skip all those calculations.
<sdk:DataGrid AutoGenerateColumns="False" RowHeight="20">
</sdk:DataGrid>
Related
I am evaluating the performance of the XamDataGrid provided by Infragistics. In particular, i'm focusing on the programmatic selection of 500k record in a table of 1 million records. To do that i'm currently doing the following:
Stopwatch s = new Stopwatch();
var recordsToSelect = new List<DataRecord>(500000);
s.Start();
foreach (DataRecord rec in ODataGrid.Records)
{
if (rec.Index % 2 == 0)
recordsToSelect.Add(rec);
}
s.Stop();
Console.WriteLine(s.Elapsed);
s.Restart();
ODataGrid.SelectedItems.Records.AddRange(recordsToSelect.ToArray(), false, true);
s.Stop();
Console.WriteLine(s.Elapsed);
the results are the following:
00:00:14.3122651 for the loop
00:00:00.8765741 to apply the selection
The problem is clearly the loop. I wonder, since i'm binding a DataTable as item source, and since the loop on a DataTable is 10x faster the the loop on the XamDataGrid.Recordsif there is a way to convert the DataRow in a DataRecord, or if there is a faster way to reach the goal of the function i exposed before
EDIT :
I was able to imporove performance doing:
Stopwatch s = new Stopwatch();
var recordsToSelect = new List<DataRow>(500000);
s.Start();
int i = 0;
foreach (DataRow rec in d.Rows)
{
if (i % 2 == 0)
recordsToSelect.Add(rec);
i++;
}
s.Stop();
Console.WriteLine(s.Elapsed);
s.Restart();
DataRecord[] dr = ODataGrid.GetRecordsFromDataItems(recordsToSelect.ToArray(), false);
s.Stop();
Console.WriteLine(s.Elapsed);
s.Restart();
ODataGrid.SelectedItems.Records.AddRange(dr, false, true);
s.Stop();
Console.WriteLine(s.Elapsed);
with these times elapsed
00:00:00.0812110
00:00:08.2830562
00:00:01.1774925
the problem is that the creation of the DataRecord array is the slowest part
Why do you reach your item source over a datagrid? You can use MVVM approach to get your item source as ObservableCollection or List. From your view model, you can reach the data source and as well as your selected items.
I am trying to have my asp.net WebAPI web service read a .csv and update a database using Entity Framework. The .csv file is about 20,000-30,000 rows.
As of now I am using a TextfieldParser to read the .csv, each row of the .csv file I create a new object, then add object to the EF context.
Once it's done adding all rows to the context, then I call db.SaveChanges();
Watching the console I noticed it calls an update statement for each row... which takes a long time. Is there a better more efficient way to accomplish this?
if (filetype == "xxx")
{
using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
int rowCount = 1;
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
//skip header row
if (rowCount != 1)
{
var t = new GMI_adatpos
{
PACCT = fieldData[3]
};
db.GMI_adatpos.Add(t);
}
rowCount++;
}
}
}
db.SaveChanges();
This issue is very common,
In your case, we can split it into two category:
Add vs AddRange Performance
Write & database round-trip
Add vs AddRange Performance
The Add method will try to detect change every time you add a new record while the AddRange only does it once. Detecting changes every time can take several minutes.
This issue is very easy to fix, simply create a list, add the entity to this list instead and use AddRange with the list at the end.
List<GMI_adatpo> list = new List<GMI_adatpo>();
if (filetype == "xxx")
{
using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
int rowCount = 1;
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
//skip header row
if (rowCount != 1)
{
var t = new GMI_adatpos
{
PACCT = fieldData[3]
};
list.Add(t);
}
rowCount++;
}
}
}
db.GMI_adatpos.AddRange(list)
db.SaveChanges();
Write & Database round-trip
Everytime you save a record, you perform a database round-trip. So if you insert average 30,000 record, you perform 30,000 database round-trip which is insane!
Disclaimer: I'm the owner of the project Entity Framework Extensions
This library allows to perform:
BulkSaveChanges
BulkInsert
BulkUpdate
BulkDelete
BulkMerge
You can either call BulkSaveChanges instead of SaveChanges or create a list to insert and use directly BulkInsert instead for even more performance.
BulkSaveChanges Solution (Way faster than SaveChanges)
db.GMI_adatpos.AddRange(list)
db.SaveChanges();
BulkInsert Solution (Fastest than BulkSaveChanges but do not save related entities)
db.BulkInsert(list);
Because the number of items added to the DbContext is very high, ram space is gradually filled, and operation is very slow. Therefore is better that after a few records (ex 100), calling SaveChanges Methods and renew DbContext.
if (filetype == "xxx")
{
using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
int rowCount = 1;
while (!csvReader.EndOfData)
{
if(rowCount%100 == 0)
{
db.Dispose();
db.SaveChanges();
db = new AppDbContext();//Your DbContext
}
string[] fieldData = csvReader.ReadFields();
//skip header row
if (rowCount != 1)
{
var t = new GMI_adatpos
{
PACCT = fieldData[3]
};
db.GMI_adatpos.Add(t);
}
rowCount++;
}
}
}
I am programmatically updating my WinForm DataGridView
Problem, DataGridViewCheckBoxCell doesn't get updated !!!
I was google, it seams like knowing case but whatever I've tried did not help yet.
private void InitializeFunctionsDataGrid()
{
System.Data.DataSet ds = func.GetFunctions();
this.FunctionsDataGrid.DataSource = ds.Tables[0];
this.FunctionsDataGrid.Columns["FunctionId"].Visible = false;
this.FunctionsDataGrid.Columns["DESCRIPTION"].Width = 370;
DataGridViewCheckBoxColumn column = new DataGridViewCheckBoxColumn();
column.Name = "enable";
column.HeaderText = "enable";
column.FalseValue = 0;
column.TrueValue = 1;
FunctionsDataGrid.Columns.Add(column);
foreach(DataGridViewRow row in FunctionsDataGrid.Rows)
{
(( DataGridViewCheckBoxCell)row.Cells["enable"]).Value = 1;
}
FunctionsDataGrid.CurrentCell = null;
}
enable is an unbound column. This means that you need to provide cell value yourself.
You can set the VirtualMode property to true and handle the CellValueNeeded event.
If you want to enable the user to check a cell then you need to handle the CellValuePushed event.
DataGridView samples that are part of the DataGridView FAQ has a specific example of an unbound checkbox column along with databound columns.
OK basically easiest way for me was to work with datasource.
I've add the column to the DataTable and fill it with data.
And then last thing this.FunctionsDataGrid.DataSource = ds.Tables[0];
I have Datagrid and Text Box in my Form. Datagrid is showing me existing items in my stock. I use Text Box to search and set focus to that row which is matching with my Text Box. Now it is working fine when VirtualizingStackPanel.IsVirtualizing="false" but it is very slow and getting a lot RAM resource.
Here is my code for this.
public IEnumerable<Microsoft.Windows.Controls.DataGridRow> GetDataGridRows(Microsoft.Windows.Controls.DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
if (null == itemsSource) yield return null;
foreach (var item in itemsSource)
{
var row = grid.ItemContainerGenerator.ContainerFromItem(item) as Microsoft.Windows.Controls.DataGridRow;
if (null != row) yield return row;
}
}
private void SearchBoxDataGrid_TextChanged(object sender, TextChangedEventArgs e)
{
var row = GetDataGridRows(AssortDataGrid);
/// go through each row in the datagrid
foreach (Microsoft.Windows.Controls.DataGridRow r in row)
{
DataRowView rv = (DataRowView)r.Item;
// Get the state of what's in column 1 of the current row (in my case a string)
string t = rv.Row["Ассортимент"].ToString().ToLower();
if (t.StartsWith(SearchBoxDataGrid.Text.ToLower()))
{
AssortDataGrid.SelectedIndex = r.GetIndex();
AssortDataGrid.ScrollIntoView(AssortDataGrid.SelectedItem);
break;
}
}
}
What I want is to make it VirtualizingStackPanel.IsVirtualizing="true" but in this case my method is not working. I know why it is not working, my code will work only for showing part of Datagrid.
What do you recommend? How to fix this issue? Any idea will be appreciated. If you give any working code it will be fantastic. I hope I could explain my problem.
Virtualization means that WPF will reuse the UI components, and simply replace the DataContext behind the components.
For example, if your Grid has 1000 items and only 10 are visible, it will only render around 14 UI items (extra items for scroll buffer), and scrolling simply replaces the DataContext behind these UI items instead of creating new UI elements for every item. If you didn't use Virtualization, it would create all 1000 UI items.
For your Search to work with Virutalization, you need to loop through the DataContext (DataGrid.Items) instead of through the UI components. This can either be done in the code-behind, or if you're using MVVM you can handle the SeachCommand in your ViewModel.
I did a little coding and make it work. If anyone needs it in future please, use it.
Firstly I am creating List of Products
List<string> ProductList;
Then on Load Method I list all my products to my Product List.
SqlCommand commProc2 = new SqlCommand("SELECT dbo.fGetProductNameFromId(ProductID) as ProductName from Assortment order by ProductName desc", MainWindow.conn);
string str2;
SqlDataReader dr2 = commProc2.ExecuteReader();
ProductList = new List<string>();
try
{
if (dr2.HasRows)
{
while (dr2.Read())
{
str2 = (string)dr2["ProductName"];
ProductList.Insert(0, str2.ToLower ());
}
}
}
catch (Exception ex)
{
MessageBox.Show("An error occured while trying to fetch data\n" + ex.Message);
}
dr2.Close();
dr2.Dispose();
After that I did some changes in SearchBoxDataGrid_TextChanged
private void SearchBoxDataGrid_TextChanged(object sender, TextChangedEventArgs e)
{
int pos = 0;
string typedString = SearchBoxDataGrid.Text.ToLower();
foreach (string item in ProductList)
{
if (!string.IsNullOrEmpty(SearchBoxDataGrid.Text))
{
if (item.StartsWith(typedString))
{
pos = ProductList.IndexOf(item);
AssortDataGrid.SelectedIndex = pos;
AssortDataGrid.ScrollIntoView(AssortDataGrid.SelectedItem);
break;
}
}
}
}
Now it works when VirtualizingStackPanel.IsVirtualizing="true".
That is all.
I am trying to implement a Search as you type functionality ( like the one in the search in the default email app ) - I have a listbox with say 50 items - each item bound to a class object which has string fields ... I wish to search and display items which have the text in the search box in one of their string fields - this as the user keys in to the textbox ... tried a couple of approaches -->
1 >> Using a CollectionViewSource
- Bound a CollectionViewSource to All the items from the DB
- Bound the List Box to a CollectionViewSource
- Setting the filter property of the CollectionViewSource - to whose function which searchs for the text in the Search box in the items and set the e.Accepted - on every keyup event
- Filtering works fine but its slow with 50 items :( - guess cos of filter taking each item and checking if to set e.Accepted property to true
.... One DB call on load but seems to be a lot of processing to decide which element to disply in the filer by the CollectionViewSource
2 >> Filter # DB level
- on keyup - sending the text in the search box to the ViewModel where a function returns an ObservableCollection of objects which has the search string
- ObservableCollection is bound to the listbox
.... Not much processing # the top layer but multiple DB calls on each Keypress - still slow but just a tad faster than Approach One
Is there any other approch you would recommend ? or any suggestions to further optimize the above mentioned approaches? - any tweak to give smooth functioning for the search?
First time into mobile dev :) ... Thanx in advance :)
You can optimize the search process further by delaying the search action by x milliseconds to allow the user to continue the writing of the search criteria. This way, each new typed char will delay the search action by xxx milliseconds till the last char where the action is triggered, you'll get a better performance using just one call instead of say 10 calls. Technically speaking, you can achieve this by using a Timer class.
Here you'll find a sample source code example that shows u how to implement this.
private void txtSearchCriteria_TextChanged(object sender, TextChangedEventArgs e)
{
if (txtSearchCriteria.Text == "Search ...")
return;
if (this.deferredAction == null)
{
this.deferredAction = DeferredAction.Create(() => ApplySearchCriteria());
}
// Defer applying search criteria until time has elapsed.
this.deferredAction.Defer(TimeSpan.FromMilliseconds(250));
}
private DeferredAction deferredAction;
private class DeferredAction
{
public static DeferredAction Create(Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
return new DeferredAction(action);
}
private Timer timer;
private DeferredAction(Action action)
{
this.timer = new Timer(new TimerCallback(delegate
{
Deployment.Current.Dispatcher.BeginInvoke(action);
}));
}
public void Defer(TimeSpan delay)
{
// Fire action when time elapses (with no subsequent calls).
this.timer.Change(delay, TimeSpan.FromMilliseconds(-1));
}
}
public void ApplySearchCriteria()
{
ICollectionView dataView = this.ViewSource.View;
string criteria = this.txtSearchCriteria.Text;
string lowerCriteria = criteria.ToLower();
using (dataView.DeferRefresh())
{
Func<object, bool> filter = word =>
{
bool result = word.ToString().ToLower().StartsWith(lowerCriteria);
return result;
};
dataView.Filter = l => { return filter.Invoke(l); };
}
this.lstWords.ItemsSource = dataView;
if (this.lstWords.Items.Count != 0)
{
this.lblError.Visibility = Visibility.Collapsed;
}
else
{
this.lblError.Visibility = Visibility.Visible;
}
}