I'm doing an LOB app with WPF + Linq to SQL, and the problem of the Linq-To-Sql collections not correctly implementing INotifyCollectionChanged is something that I 've had to work around on every facet of the system.
The best solution I've found so far is to do any one of the following:
- Create a model layer over your DataContext class, so that your GUI code only interacts with the model layer, not directly with the DataContext. In your business logic methods, always wrap returned collections in an ObservableCollection
 
and/or
- Implement secondary collection properties on your entity classes, such that where you originally had Customer.Products, you now have *Customer.Products_Observable", where this new readonly property simply returns an ObservableCollection that wraps whatever Customer.Products returns.
 
and/or
- Create a new class derived from ObservableCollection, which is DataContext aware. If you override the Add/Insert/Remove methods of such a class, then any alterations to the collection can automatically propogate to the DataContext and InsertOnSubmit / DeleteOnSubmit calls.
 
Here is an example of such a class:
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
The IDeletable interface allows you to implement specific logic on your entity classes (like cleaning up foreign-keys and deleting child objects).
Notice that the class requires a DataContext reference as a constructor, which makes it ideally suited for use with scenario 1) above (i.e. use from a Model layer/class). If you want to implement it method 2) [ie on the entity as a property], then you can give attached entities the ability to "find" their DataContext as follows:
[On the entity 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
[As a utility class]:
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
Note: An entity can only find it's DataContext if it is attached to a DataContext with ChangeTracking switched on. The above hack (yes - it is a hack!) relies on it.