Я делаю LOB-приложение с WPF + Linq to SQL, и проблема коллекций Linq-To-Sql, не правильно реализующих INotifyCollectionChanged, - это то, над чем мне приходилось работать на каждом аспекте системы.
Лучшее решение, которое я нашел, - это выполнить одно из следующих действий:
- Создайте слой модель над вашим классом DataContext, чтобы код GUI взаимодействовал только со слоем модели, а не напрямую с DataContext. В методах бизнес-логики всегда переносите возвращенные коллекции в ObservableCollection
и / или
- Реализуйте вторичные свойства коллекции на ваших классах сущностей, так что там, где у вас изначально было Customer.Products , у вас теперь есть * Customer.Products_Observable ", где это новое свойство только для чтения возвращает ObservableCollection, которая упаковывает все, что возвращает Customer.Products.
и / или
- Создайте новый класс, производный от ObservableCollection, который поддерживает DataContext. Если вы переопределите методы Add / Insert / Remove такого класса, то любые изменения в коллекции могут автоматически распространяться на вызовы DataContext и InsertOnSubmit / DeleteOnSubmit.
Вот пример такого класса:
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Linq
Imports System.Data.Linq
Public Class ObservableEntityCollection(Of T As {Class})
Inherits ObservableCollection(Of T)
Private _Table As Table(Of T)
Public Sub New(ByVal Context As DataContext)
Me._Table = Context.GetTable(Of T)()
End Sub
Public Sub New(ByVal Context As DataContext, ByVal items As IEnumerable(Of T))
MyBase.New(items)
Me._Table = Context.GetTable(Of T)()
End Sub
Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T)
_Table.InsertOnSubmit(item)
MyBase.InsertItem(index, item)
End Sub
Public Shadows Sub Add(ByVal item As T)
_Table.InsertOnSubmit(item)
MyBase.Add(item)
End Sub
Public Shadows Sub Remove(ByVal item As T)
If MyBase.Remove(item) Then
_Table.DeleteOnSubmit(item)
End If
Dim deletable As IDeletableEntity = TryCast(item, IDeletableEntity)
If deletable IsNot Nothing Then deletable.OnDelete()
End Sub
Protected Overrides Sub RemoveItem(ByVal index As Integer)
Dim Item As T = Me(index)
_Table.DeleteOnSubmit(Item)
MyBase.RemoveItem(index)
End Sub
End Class
Public Interface IDeletableEntity
Sub OnDelete()
End Interface
Интерфейс IDeletable позволяет реализовать определенную логику в ваших классах сущностей (например, очистка внешних ключей и удаление дочерних объектов).
Обратите внимание, что классу требуется ссылка DataContext в качестве конструктора, что делает его идеально подходящим для использования в сценарии 1) выше (т. Е. Использование из слоя / класса Model). Если вы хотите реализовать метод 2) [то есть для сущности как свойства], то вы можете дать присоединенным сущностям возможность «найти» их DataContext следующим образом:
[На объекте Class:]
Public Property Context() As DataContext
Get
If _context Is Nothing Then
_context = DataContextHelper.FindContextFor(Me)
Debug.Assert(_context IsNot Nothing, "This object has been disconnected from it's DataContext, and cannot perform the requeted operation.")
End If
Return _context
End Get
Set(ByVal value As DataContext)
_context = value
End Set
End Property
Private _context As DataContext
[Как служебный класс]:
Public NotInheritable Class DataContextHelper
Private Const StandardChangeTrackerName As String = "System.Data.Linq.ChangeTracker+StandardChangeTracker"
Public Shared Function FindContextFor(ByVal this as DataContext, ByVal caller As Object) As JFDataContext
Dim hasContext As Boolean = False
Dim myType As Type = caller.GetType()
Dim propertyChangingField As FieldInfo = myType.GetField("PropertyChangingEvent", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim propertyChangingDelegate As PropertyChangingEventHandler = propertyChangingField.GetValue(caller)
Dim delegateType As Type = Nothing
For Each thisDelegate In propertyChangingDelegate.GetInvocationList()
delegateType = thisDelegate.Target.GetType()
If delegateType.FullName.Equals(StandardChangeTrackerName) Then
propertyChangingDelegate = thisDelegate
Dim targetField = propertyChangingDelegate.Target
Dim servicesField As FieldInfo = targetField.GetType().GetField("services", BindingFlags.NonPublic Or BindingFlags.Instance)
If servicesField IsNot Nothing Then
Dim servicesObject = servicesField.GetValue(targetField)
Dim contextField As FieldInfo = servicesObject.GetType.GetField("context", BindingFlags.NonPublic Or BindingFlags.Instance)
Return contextField.GetValue(servicesObject)
End If
End If
Next
Return Nothing
End Function
Примечание. Сущность может найти свой DataContext, только если она присоединена к DataContext с включенным ChangeTracking. Вышеуказанный хак (да - это хак!) Опирается на него.