2016-08-24 38 views
1

在下面的代碼的BindingSource後,我有產生EnumerableRowCollection(Of DataRow)是最終在一個BindingSource.DataTable的NEWROW()方法將導致LINQ表達的重新評價被用作與AsDataView()

使用LINQ表達代碼第一次循環,沒關係。但是,一旦在IEnumerable上使用AsDataView()設置了第一個ControlBindingSource,則下一次調用DataTable上的NewRow()方法時,它將重新評估LINQ表達式。

Option Explicit On 
Option Infer Off 
Option Strict On 

Public Class Form1 
    Dim ds As New DataSet() 

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 
     ds.Tables.Add("Table1") 

     Dim comparisonInteger As Integer = 12 
     ds.Tables("Table1").Columns.Add("ID", GetType(Integer)) 
     ds.Tables("Table1").Columns.Add("IntegerValue", GetType(Integer)) 
     ds.Tables("Table1").Columns.Add("StringValue", GetType(String)) 

     ds.Tables("Table1").Rows.Add({1, 47, "row 1"}) 
     ds.Tables("Table1").Rows.Add({2, 2, "row 1"}) 
     ds.Tables("Table1").Rows.Add({3, 7, "row 1"}) 
     ds.Tables("Table1").Rows.Add({4, 6, "row 1"}) 

     For i As Integer = 0 To 2 
      Dim iterator As Integer = i 
      Dim tb As New TextBox() 

      CreateNewRowIfMissing(comparisonInteger+i) 

      Dim dataValue As EnumerableRowCollection(Of DataRow) = ds.Tables("Table1").AsEnumerable() _ 
                     .Where(Function(v) DirectCast(v("IntegerValue"), Integer) = comparisonInteger + iterator) 

      Dim bs As New BindingSource(dataValue.AsDataView(), Nothing) 
      Dim b As New Binding("Text", bs, "IntegerValue") 
      b.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged 
      tb.DataBindings.Add(b) 

      tb.Location = New Point(10, 10 * Me.Controls.Count+1) 

      Me.Controls.Add(tb) 
     Next 
    End Sub 

    Private Sub CreateNewRowIfMissing(comparisonInteger As Integer) 

     If ds.Tables("Table1").AsEnumerable() _ 
       .Where(Function(v) DirectCast(v("IntegerValue"), Integer) = comparisonInteger) _ 
       .Count() = 0 

      ds.Tables("Table1").Rows.Add(ds.Tables("Table1").NewRow()) 'Prompts the dataValue IEnumerable to begin evaluating again. 
      ds.Tables("Table1").Rows(ds.Tables("Table1").Rows.Count-1)("ID") = ds.Tables("Table1").Rows.Count 
      ds.Tables("Table1").Rows(ds.Tables("Table1").Rows.Count-1)("IntegerValue") = comparisonInteger 
      ds.Tables("Table1").Rows(ds.Tables("Table1").Rows.Count-1)("StringValue") = "row" & ds.Tables("Table1").Rows.Count.ToString() 
     End If 
    End Sub 
End Class 

爲了澄清,我用一個標準的DataViewRowFilterBindingSource,它的工作:

Dim dv As New DataView(ds.Tables("Table1")) 
dv.RowFilter = "IntegerValue=" & (comparisonInteger+iteratorValue) 

不過,我覺得這可能是更好的利用LINQ來代替。

那麼,這是怎麼回事?爲什麼LINQ表達式DataView是基於在調用DataTableNewRow()方法時重新評估的?有什麼辦法可以防止這種情況發生?

我本來希望AsDataView()會產生一個DataView,其行爲與上述兩行相同。

由於潛在的變通,我發現我可以用這個作爲BindingSourceDataSource

New DataView(dataValue.AsEnumerable().CopyToDataTable())

不過,我很擔心用在這樣的陣容潛在的性能未來,並且如果需要上述行,我不妨使用DataViewRowFilter而不是LINQ。

回答

1

它的DataTableExtensions.AsEnumerable()正常行爲和它的documented

AsEnumerable方法返回的枚舉對象是 永久地綁定到產生它的數據表。多次調用 AsEnumerable方法將返回多個獨立可查詢的 對象,這些對象都綁定到源DataTable

另外的DataView正常行爲,這也是documented

DataView不存儲數據,而是代表一個連接 視圖及其對應的數據表的。

當你調用DataTable.AsEnumemarble()方法,它通過該方法將返回EnumerableRowCollection<DatRow>保持原來的DataTable的引用,然後用它每次調用EnumerableRowCollection(Of T).AsDataView()方法,並返回其用於創建收集的原始DataTable行。當ListChanged事件引發

所以在你的榜樣dataValue.AsDataView()你作爲BindingSourceDataSource總是返回DataTable當前行。

但如果分配new DataView(data.AsEnumerable().CopyToDataTable())BindingSourceDataSource,因爲你使用的是不同的DataTable通過調用CopyToDataTable,改變原有DataTable不具有完全不知道原來DataTable的數據源上的任何影響。事實上它也足夠使用下面的語句爲BindingSourceDataSource

bs.DataSource = dataValue.CopyToDataTable() 

穿上Form一個DataGridView和3個按鈕,然後處理Click事件按鈕的像下面。

DataTable dt; 
EnumerableRowCollection<DataRow> data; 
BindingSource bs; 
private void button1_Click(object sender, EventArgs e) 
{ 
    dt = new DataTable(); 
    dt.Columns.Add("A", typeof(int)); 
    for (int i = 0; i < 5; i++) 
     dt.Rows.Add(i); 
    data = dt.AsEnumerable().Where(x => x.Field<int>(0) >= 3); 
    bs = new BindingSource(); 
    bs.DataSource = data.AsDataView(); 
    bs.RaiseListChangedEvents = true; 
    this.dataGridView1.DataSource = bs; 
} 
private void button2_Click(object sender, EventArgs e) 
{ 
    dt.Rows.Add(dt.AsEnumerable().Max(x => x.Field<int>(0)) + 1); 
} 
private void button3_Click(object sender, EventArgs e) 
{ 
    var row = data.Where(x => x.Field<int>(0) == 3).First(); 
    row[0] = 333; 
    MessageBox.Show(dt.Rows[3][0].ToString()); 
} 

測試1

  1. 點擊Button1,該DataGridView將顯示3,4 A列。
  2. 點擊Button2,新行5作爲A列的值將被添加並立即顯示在網格中。它還顯示一個MessageBox,其中顯示3data中的項目數。
  3. 點擊Button3,3將更改爲333並立即顯示在網格中。它也顯示333作爲單元格在數據表中的值。

結果→通過打開ListChanged並使用AsDataView您將立即看到網格中的更改。

測試2

  1. 關閉使用bs.RaiseListChangedEvents = false;
  2. 點擊Button1會像測試1
  3. 點擊Button2也不再立即顯示新的紀錄提高ListChanged。雖然它顯示3data中的行數。
  4. 點擊Button3將不再顯示編輯值立即。雖然消息框顯示數據已在DataTable中更改。

結果→如果您關閉ListChanged事件,更改將不會立即顯示在列表中。

測試3

  1. 使用bs.DataSource = data.CopyToDataTable();
  2. 設置bs.RaiseListChangedEvents = true;
  3. 點擊Button1填補電網。
  4. 點擊Button2對網格沒有任何影響,但顯示3爲行數。
  5. 點擊Button3對網格沒有任何影響,但將333顯示爲數據表的值。

結果→AsDataTable創建一個新的DataTable這是不相關的原DataTable

+0

啊,所以'CopyToDataTable'絕對會打破我的DataBinding。在考慮將其作爲替代品之前,可能應該已經進行了測試。無論如何,我確實知道它在IEnumerable下引用同一個表(這是我想要的)。你是說當'DataTable'增加了一個新行時,這會引發'DataView'中的'ListChanged'事件,導致對'DataView'創建的linq表達式進行重新評估?我的理解是否正確? – Interminable

+0

* 1)*您使用'CopyToDataTable'接收的數據包含相同的值,但它是另一個參考。這是一個不同的'DataTable'。 * 2)*當您添加記錄或對其底層DataTable應用任何更改時,將會引發'DataView'的'ListChanged'事件。 * 3)* DataView的'ListChanged'事件導致綁定控件詢問數據,因此包含新行的整個數據將返回到'DataGridView'。例如,如果您將'bs.RaiseListChangedEvents = false'設置爲'DataGridView',您將看不到更改。 –

+0

不要擔心性能。顯示添加到「DataTable」的新記錄不會對性能產生任何負面影響。考慮一下你的需求和你需要的東西。然後根據您的要求選擇解決方案。例如,如果你需要數據綁定到原始記錄,你應該使用'AsDataView'。另外如果你不想顯示新的記錄(我不知道爲什麼),然後應用過濾器到'DataView.RowFilter'。 –

0

它看起來像只要底層DataTable被更新,DataView的LINQ表達式就會自動重新評估。這意味着在將新行添加到DataTable而不是NewRow()的調用中時,正在重新評估LINQ。這是不是直接對我明顯的,因爲我是在一行做兩個動作:ds.Tables("Table1").Rows.Add(ds.Tables("Table1").NewRow())

@RezaAghaei提到,如果底層DataTable被更新ListChanged事件將引起人們的關注。通過測試,似乎如果DataView是從LINQ表達式創建的,則在事件發生之前重新評估LINQ ,前提是底層DataTable已更新。

由於這樣做的結果,設定BindingSourceRaiseListChangedEvents屬性不會停止LINQ的評價,和之前提出的ListChanged事件評價發生,似乎是可以處理任何其他事件取消行爲,所以我認爲不可能阻止這種情況發生。

但是,現在我明白它在做什麼了,當行本身被添加到DataTable時,重新評估底層LINQ表達式是有意義的,因此,停止它可能不是一個好主意無論如何。

這對我造成了一些麻煩,雖然我添加的新行沒有完全設置,導致InvalidCastException。然而,在我上面的例子中,我只需要確保新行在將其添加到DataTable之前已完全設置好。因此,當重新評估LINQ時,該行包含有效數據,並且表達式運行時沒有錯誤。

所以,在上面的例子中,CreateNewRowIfMissing()方法僅需要進行調整,以執行以下操作:

Private Sub CreateNewRowIfMissing(comparisonInteger As Integer) 

    If ds.Tables("Table1").AsEnumerable() _ 
      .Where(Function(v) DirectCast(v("IntegerValue"), Integer) = comparisonInteger) _ 
      .Count() = 0 

     Dim newRow As DataRow = ds.Tables("Table1").NewRow() 

     newRow("ID") = ds.Tables("Table1").Rows.Count 
     newRow("IntegerValue") = comparisonInteger 
     newRow("StringValue") = "row" & ds.Tables("Table1").Rows.Count.ToString() 

     ds.Tables("Table1").Rows.Add(newRow) 'Prompts the dataValue IEnumerable to begin evaluating again. 
    End If 
End Sub 

這確保了新行僅加入到DataTable後它已準備好,幷包含有效數據。結果,當它最終被添加時,LINQ表達式重新評估並且不拋出InvalidCastException

+0

**•**如果您將記錄添加到「數據表」中,您認爲「ListChanged」事件不會引發什麼樣的測試?→錯!它會被提升。 **•**我不確定重新評估linq是什麼意思,但您需要仔細閱讀第一個引用:* AsEnumerable方法返回的可枚舉對象永久綁定到產生它的DataTable 。* –

+0

@RezaAghaei測試時,我正在重新評估LINQ。我並沒有預料到在LINQ得到重新評估之後'ListChanged'事件將會上升。我已經相應地更新了答案。 – Interminable

+0

沒問題,我沒有更多的信息分享的話題。我發佈的答案是你所需要的,它完全有用IMO。 –