DataGrid Excessive Databinding Property Get Calls
We have a data grid on a window that is bound to a collection of obje开发者_C百科cts. We are having very bad performance on this window; it can take up to 20 seconds to load, and then 5-7 seconds each time you 'scroll' the data grid. This is with a total of 12 items so far. When we investigated, it seemed that the primary cause of the slowdown was property getters; some of our getters were being called upwards of 20,000 times (that's 1666.667 times per object)! Instrumentation revealed that our getters were not particularly slow; the slowest one took 0.002 seconds. Unfortunately, when you multiply 0.0002 * 20k, you easily come up with the delay we're experiencing.
We sat down and created a proof-of-problem sample project. In this project, we created a dead-simple class, a dead-simple View Model, and a very basic window. We added some code to attempt to observe the problem. While we're not seeing anything near the magnitude of problems we saw earlier, this is a much simpler screen; more disturbingly for us, we did see evidence of what we would consider to be 'excessive' use of the property getters. In this example, when you run the screen, they start out at 1; when you scroll down and then back up, some of them are now up to 5 or 6; scrolling back down and you'll see some of the bottom ones up to 9 or so.
In the case of this proof of concept, that's not a problem; but our actual objects are much more complicated, and we cannot afford 40 second delays to access a property 20,000 times! Does anyone know what's going on here? How can I get the grid to poll my objects less aggressively? We are using the WPF-Toolkit DataGrid, and .NET version 3.5. Sample code for the PoC is below:
*****WINDOW CODE*****
<Window x:Class="PocApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="200" Width="200">
<Grid>
<WpfToolkit:DataGrid ItemsSource="{Binding Path=MyEntries}">
</WpfToolkit:DataGrid>
</Grid>
</Window>
*****WINDOW CODE-BEHIND*****
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PocApp
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new EntryViewModel();
}
}
}
*****ENTRY CLASS*****
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PocApp
{
public class Entry
{
public Entry(string first, string last)
{
FirstName = first;
LastName = last;
}
public string FirstName
{
get
{
firstCall++;
System.Console.WriteLine("FirstName Call:" + firstCall);
return _firstname;
}
set
{
_firstname = value;
}
}
public int FirstNameCallCount
{
get
{
return firstCall;
}
}
public string LastName
{
get
{
lastCall++;
System.Console.WriteLine("LastName Call:" + lastCall);
return _lastname;
}
set
{
_lastname = value;
}
}
public int LastNameCallCount
{
get
{
return lastCall;
}
}
private string _firstname,_lastname;
private int firstCall,lastCall = 0;
}
}
*****ENTRY VIEW MODEL CLASS*****
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
namespace PocApp
{
public class EntryViewModel
{
public EntryViewModel()
{
List<Entry> myCoolEntries = new List<Entry>()
{
new Entry("A","A1"),
new Entry("B","B1"),
new Entry("C","C1"),
new Entry("D","D1"),
new Entry("E","E1"),
new Entry("F","F1"),
new Entry("G","G1"),
new Entry("H","H1"),
new Entry("I","I1"),
new Entry("J","J1"),
new Entry("K","K1"),
new Entry("L","L1"),
new Entry("M","M1"),
new Entry("N","N1"),
new Entry("O","O1"),
new Entry("P","P1"),
new Entry("Q","Q1"),
};
MyEntries = (CollectionView)CollectionViewSource.GetDefaultView(myCoolEntries);
}
public CollectionView MyEntries
{
get;
private set;
}
}
}
The numbers you're seeing in your proof of concept are actually normal. They are the result of virtualization of rows inside the datagrid; when an item is scrolled out of view, the container row is re-used to display a newly entered item, thus keeping memory usage low with respect to the actual visual controls created and manipulated. Displaying another item means requerying the bound properties.
Normally I don't imagine this would be a problem; I've worked with pretty complicated business objects and displayed quite a lot of data in the datagrid without the kind of problems you're mentioning. I would suggest taking a look at any styles applied to the datagrid, as they may interfere with the virtualization of rows; also, property getters that take 2 milliseconds to execute aren't what I'd call fast :)
Edit: actually, avoiding the additional calls to the property getters would only be possible by disabling virtualization, which would create huge performance problems - both tons of wasted memory and abismal scroll performance with even a 'small' (thousands) amount of rows in the datagrid. However, these are about the only things I can say without more information. If at all possible, try to step through the code when the getters are called and determine more precisely the source and timing of the majority of calls. If the problems occur when scrolling, then your entities are indeed incredibly complex and I question the value of displaying them in a datagrid in this way.
Another edit: I re-read the question more carefully and I noticed you said 12 items?! I'm sorry but now I'm quite adamant in saying that the datagrid shouldn't be blamed for this, unless your objects have thousands of properties, all bound to columns in the datagrid, which I doubt is at all possible. Please inspect the rest of the code and styles and try to determine any potential problem areas we could advise you about... not sure what else to say. Also check that the entities don't raise NotifyPropertyChanged events unnecessarily, as this will cause a property re-query by the bound controls.
精彩评论