我正在尝试优化我的过慢的 DataGridView,它使用 SQLite 数据库通过虚拟模式和缓存获取数据,我已经使用了双缓冲的技巧并删除了列和行的自动调整大小。然而,尽管阅读了这篇文章: http://msdn.microsoft.com/en-us/library/ha5xt0d9.aspx 然后 : http://msdn.microsoft.com/en-us/library/ha5xt0d9.aspx
我的网格非常慢,实际上对数据库的查询非常快,但即使数据已经通过缓存加载,DataGridView 的绘制似乎也非常慢......
但也许我的 Cache 类不太好,所以我想知道我是否做错了什么。原理很简单,一个 Cache 分为 3 个部分:Upper、Current(也可以称为“中”和 Lower),每个部分都由索引(开始和结束)分隔,如果数据已经加载,Cache 就会给出根据这些非常简单的规则得出的值:
- 如果值在当前部分没问题,只需加载数据
- 如果需要的值是在上还是下,则该部分成为当前部分没有问题,只需要一个新部分。
即
上部:0 - 100 当前部分:101 - 201 下部:202 - 302
需要的值在Lower Part中,没有问题,Current变成Lower,Upper变成Current,只需要重新加载新的Lower部分。显然,如果所需的值具有缓存中不可用的行索引,则会重新加载该索引。
public class Cache
{
private Dictionary<PagePart, Page> _pages;
public Dictionary<PagePart, Page> Pages
{
get { return this._pages; }
set { this._pages = value; }
}
private String _tableName;
public String TableName
{
get { return this._tableName; }
set { this._tableName = value; }
}
private SQLiteConnection _connection;
public SQLiteConnection Connection
{
get { return this._connection; }
set { this._connection = value; }
}
public Cache(String tableName, SQLiteConnection connection)
{
this.Connection = connection;
this.TableName = tableName;
this.Pages = new Dictionary<PagePart, Page>(PageNumber);
IndexRange indexRangeUpper = new IndexRange(0, PageSize);
IndexRange indexRangeCurrent = new IndexRange(PageSize + 1, 2 * PageSize);
IndexRange indexRangeLower = new IndexRange(2 * PageSize + 1, 3 * PageSize);
DataTable dataTableUpper = this.GetDataTableFromTable(indexRangeUpper);
DataTable dataTableCurrent = this.GetDataTableFromTable(indexRangeCurrent);
DataTable dataTableLower = this.GetDataTableFromTable(indexRangeLower);
Page pageUpper = new Page(indexRangeUpper, dataTableUpper);
Page pageCurrent = new Page(indexRangeCurrent, dataTableCurrent);
Page pageLower = new Page(indexRangeLower, dataTableLower);
Pages.Add(PagePart.Upper, pageUpper);
Pages.Add(PagePart.Current, pageCurrent);
Pages.Add(PagePart.Lower, pageLower);
}
private IndexRange GetTableIndexRange()
{
String commandText = String.Format("SELECT MAX(RowId) FROM {0}", this.TableName);
SQLiteCommand command = new SQLiteCommand(commandText, this.Connection);
this.Connection.Open();
command.CommandText = commandText;
String maxRowIdString = command.ExecuteScalar().ToString();
this.Connection.Close();
Int32 maxRowId = Int32.Parse(maxRowIdString);
return new IndexRange(0, maxRowId);
}
public Object GetCellValue(Int32 rowIndex, Int32 columnIndex)
{
Int32 indexLowerStart = Pages[PagePart.Lower].Range.StartIndex;
Int32 indexLowerEnd = Pages[PagePart.Lower].Range.EndIndex;
Int32 indexCurrentStart = Pages[PagePart.Current].Range.StartIndex;
Int32 indexCurrentEnd = Pages[PagePart.Current].Range.EndIndex;
Int32 indexUpperStart = Pages[PagePart.Upper].Range.StartIndex;
Int32 indexUpperEnd = Pages[PagePart.Upper].Range.EndIndex;
IndexRange indexRangeTable = this.GetTableIndexRange();
Int32 indexTableStart = indexRangeTable.StartIndex;
Int32 indexTableEnd = indexRangeTable.EndIndex;
// Using the cache...
if ((indexUpperStart <= rowIndex) && (rowIndex <= indexLowerEnd))
{
if ((indexLowerStart <= rowIndex) && (rowIndex <= indexLowerEnd))
{
if (indexTableEnd > indexLowerEnd)
{
this.Pages[PagePart.Upper] = this.Pages[PagePart.Current];
this.Pages[PagePart.Current] = this.Pages[PagePart.Lower];
IndexRange indexRangeLower = new IndexRange(this.Pages[PagePart.Current].Range.EndIndex + 1, this.Pages[PagePart.Current].Range.EndIndex + PageSize);
DataTable dataTableLower = this.GetDataTableFromTable(indexRangeLower);
Page pageLower = new Page(indexRangeLower, dataTableLower);
this.Pages[PagePart.Lower] = pageLower;
Int32 pageSize = this.Pages[PagePart.Current].Data.Rows.Count;
return this.Pages[PagePart.Current].Data.Rows[rowIndex % pageSize][columnIndex];
}
else
{
Int32 pageSize = this.Pages[PagePart.Lower].Data.Rows.Count;
return this.Pages[PagePart.Lower].Data.Rows[rowIndex % pageSize][columnIndex];
}
}
if ((indexCurrentStart <= rowIndex) && (rowIndex <= indexCurrentEnd))
{
Int32 pageSize = this.Pages[PagePart.Current].Data.Rows.Count;
return this.Pages[PagePart.Current].Data.Rows[rowIndex % pageSize][columnIndex];
}
if ((indexUpperStart <= rowIndex) && (rowIndex <= indexUpperEnd))
{
if (indexTableStart < indexUpperStart)
{
this.Pages[PagePart.Lower] = this.Pages[PagePart.Current];
this.Pages[PagePart.Current] = this.Pages[PagePart.Upper];
IndexRange indexRangeUpper = new IndexRange(this.Pages[PagePart.Current].Range.StartIndex - 1, this.Pages[PagePart.Current].Range.EndIndex - PageSize);
DataTable dataTableUpper = this.GetDataTableFromTable(indexRangeUpper);
Page pageUpper = new Page(indexRangeUpper, dataTableUpper);
this.Pages[PagePart.Upper] = pageUpper;
Int32 pageSize = this.Pages[PagePart.Current].Data.Rows.Count;
return this.Pages[PagePart.Current].Data.Rows[rowIndex % pageSize][columnIndex];
}
else
{
Int32 pageSize = this.Pages[PagePart.Upper].Data.Rows.Count;
return this.Pages[PagePart.Upper].Data.Rows[rowIndex % pageSize][columnIndex];
}
}
return null;
}
// Need to reload the cache...
else
{
IndexRange indexRangeCurrent = new IndexRange(rowIndex - (PageSize / 2), rowIndex + (PageSize / 2));
IndexRange indexRangeLower = new IndexRange(indexRangeCurrent.EndIndex + 1, indexRangeCurrent.EndIndex + PageSize);
IndexRange indexRangeUpper = new IndexRange(indexRangeCurrent.StartIndex - 1, indexRangeCurrent.StartIndex - PageSize );
DataTable dataTableUpper = this.GetDataTableFromTable(indexRangeUpper);
DataTable dataTableCurrent = this.GetDataTableFromTable(indexRangeCurrent);
DataTable dataTableLower = this.GetDataTableFromTable(indexRangeLower);
Page pageUpper = new Page(indexRangeUpper, dataTableUpper);
Page pageCurrent = new Page(indexRangeCurrent, dataTableCurrent);
Page pageLower = new Page(indexRangeLower, dataTableLower);
Pages[PagePart.Upper] = pageUpper;
Pages[PagePart.Current] = pageCurrent;
Pages[PagePart.Lower] = pageLower;
Int32 pageSize = this.Pages[PagePart.Current].Data.Rows.Count;
return this.Pages[PagePart.Current].Data.Rows[rowIndex % pageSize][columnIndex];
}
}
private DataTable GetDataTableFromTable(IndexRange indexRange)
{
if (this.Connection != null)
{
String commandText = String.Format("SELECT * FROM {0} WHERE RowId BETWEEN {1} AND {2}", this.TableName, indexRange.StartIndex, indexRange.EndIndex);
SQLiteCommand command = new SQLiteCommand(commandText, this.Connection);
SQLiteDataAdapter dataAdapter = new SQLiteDataAdapter(command);
DataTable dataTable = new DataTable(this.TableName, this.TableName);
dataAdapter.Fill(dataTable);
return dataTable;
}
else
{
return null;
}
}
private const Int32 PageNumber = 3;
private const Int32 PageSize = 128;
public class Page
{
public Page(IndexRange range, DataTable data)
{
this.Range = range;
this.Data = data;
}
private IndexRange _range;
public IndexRange Range
{
get { return this._range; }
set { this._range = value; }
}
private DataTable _data;
public DataTable Data
{
get { return this._data; }
set { this._data = value; }
}
}
public enum PagePart
{
Upper,
Current,
Lower,
}
public class IndexRange
{
private Int32 _startIndex;
public Int32 StartIndex
{
get { return this._startIndex; }
set { this._startIndex = value; }
}
private Int32 _endIndex;
public Int32 EndIndex
{
get { return this._endIndex; }
set { this._endIndex = value; }
}
public IndexRange(Int32 startIndex, Int32 stopIndex)
{
this.StartIndex = startIndex;
this.EndIndex = stopIndex;
}
}
}
但是哎呀...画得太慢了...我能做什么...?
最佳答案
这是回答我自己的问题的一种方法,我只是使用主键来避免对偏移量的任何混淆,无论我向上还是向下滚动(是的,懒惰的方式,所以它不是那么......高效,但仍然超过只是根本没有缓存并且非常易于使用[即不使用 3 个缓存,但最终它只用了一个来正确完成工作]):
public class Cache
{
protected SQLiteConnection Connection { get; set; }
protected String TableName { get; set; }
protected const Int32 PageSize = 512;
protected CachePage PageCurrent { get; set; }
protected IndexRange IndexRangeTable { get; set; }
public Cache(SQLiteConnection connection, String tableName)
{
SQLiteConnection.ClearAllPools();
this.Connection = connection;
this.TableName = tableName;
IndexRange indexRangeCurrent = new IndexRange(0, PageSize - 1);
DataTable dataTableCurrent = this.GetDataTableFromTable(indexRangeCurrent);
this.PageCurrent = new CachePage(indexRangeCurrent, dataTableCurrent);
}
public Object GetCellValue(Int32 rowIndex, Int32 columnIndex)
{
DataRow dataRowFound = this.PageCurrent.Data.Rows.Find(rowIndex);
if (dataRowFound != null)
{
return dataRowFound[columnIndex];
}
else
{
this.ShiftPageToIndex(rowIndex);
return GetCellValue(rowIndex, columnIndex);
}
}
private void ShiftPageToIndex(Int32 index)
{
this.PageCurrent.Range.Start = index;
this.PageCurrent.Range.Stop = index + PageSize;
this.PageCurrent.Data = this.GetDataTableFromTable(this.PageCurrent.Range);
this.PageCurrent.Range.Start = index;
this.PageCurrent.Range.Stop = index + this.PageCurrent.Data.Rows.Count;
}
private IndexRange GetTableIndexRange()
{
String commandText = String.Format("SELECT MAX(RowId) FROM {0}", this.TableName);
SQLiteCommand command = new SQLiteCommand(commandText, this.Connection);
this.Connection.Open();
command.CommandText = commandText;
String maxRowIdString = command.ExecuteScalar().ToString();
this.Connection.Close();
Int32 maxRowId = Int32.Parse(maxRowIdString);
return new IndexRange(0, maxRowId);
}
private DataTable GetDataTableFromTable(IndexRange indexRange)
{
if (this.Connection != null)
{
String commandText = String.Format("SELECT * FROM {0} WHERE RowId BETWEEN {1} AND {2}", this.TableName, indexRange.Start, indexRange.Stop);
SQLiteCommand command = new SQLiteCommand(commandText, this.Connection);
SQLiteDataAdapter dataAdapter = new SQLiteDataAdapter(command);
DataTable dataTable = new DataTable(this.TableName, this.TableName);
dataAdapter.Fill(dataTable);
dataTable.Columns.Add("RowId", typeof(Int64));
for (Int32 i = 0; i < dataTable.Rows.Count; i++)
{
dataTable.Rows[i]["RowId"] = i + indexRange.Start;
}
dataTable.PrimaryKey = new DataColumn[] { dataTable.Columns["Rowid"] };
return dataTable;
}
else
{
return null;
}
}
}
public class CachePage
{
public CachePage(IndexRange range, DataTable data)
{
this.Range = range;
this.Data = data;
}
public IndexRange Range {get; set;}
public DataTable Data {get; set;}
}
public class IndexRange
{
public Int32 Start {get; set;}
public Int32 Stop { get; set; }
public IndexRange(Int32 start, Int32 stop)
{
this.Start = start;
this.Stop = stop;
}
}
//如何从虚拟 DataGridView 调用它?
private void dataGridViewMain_CellValueNeeded(Object sender, DataGridViewCellValueEventArgs e)
{
if (this.Cache != null)
{
e.Value = this.Cache.GetCellValue(e.RowIndex, e.ColumnIndex);
}
}
所以它非常酷,并且运行良好,无需大惊小怪,无需考虑太多......
此代码片段可用于使用 RowId 字段而不是主键(实际上并不是真正的字段)来创建 SQLite 数据库表查看器。
另一个更好的解决方案是按升序或降序顺序获取数据,然后使用模运算符查找此类数据,但我太懒了,无法完成该任务。我想 SQLite dll 获取数据顺序仍然比使用预先存在的 DataColumn 作为 PrimaryKey 更快...但正如我所说,我现在很懒...
关于c# - DataGridView 优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15364111/