5

I'm building a .NET 4 WPF application using Entity Framework code first and SQL Server Compact 4.0. I'm trying to call DbContext.SaveChanges() on a background thread to avoid blocking the UI, but I'm occasionally getting the following exception:

System.AccessViolationException occurred
  Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  Source=System.Data.SqlServerCe
  StackTrace:
       at System.Data.SqlServerCe.NativeMethodsHelper.OpenStore(IntPtr pOpenInfo, IntPtr pfnOnFlushFailure, IntPtr& pStoreService, IntPtr& pStoreServer, IntPtr& pQpServices, IntPtr& pSeStore, IntPtr& pTx, IntPtr& pQpDatabase, IntPtr& pQpSession, IntPtr& pStoreEvents, IntPtr& pError)
       at System.Data.SqlServerCe.NativeMethods.OpenStore(IntPtr pOpenInfo, IntPtr pfnOnFlushFailure, IntPtr& pStoreService, IntPtr& pStoreServer, IntPtr& pQpServices, IntPtr& pSeStore, IntPtr& pTx, IntPtr& pQpDatabase, IntPtr& pQpSession, IntPtr& pStoreEvents, IntPtr& pError)
       at System.Data.SqlServerCe.SqlCeConnection.Open(Boolean silent)
       at System.Data.SqlServerCe.SqlCeConnection.Open()
       at System.Data.EntityClient.EntityConnection.OpenStoreConnectionIf(Boolean openCondition, DbConnection storeConnectionToOpen, DbConnection originalConnection, String exceptionCode, String attemptedOperation, Boolean& closeStoreConnectionOnFailure)
       at System.Data.EntityClient.EntityConnection.Open()
       at System.Data.Objects.ObjectContext.EnsureConnection()
       at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
       at System.Data.Entity.Internal.InternalContext.SaveChanges()
       at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
       at System.Data.Entity.DbContext.SaveChanges()
       at SourceLog.Model.LogSubscriptionManager.<SaveChanges>b__2() in C:\github.com\tomhunter-gh\SourceLog\SourceLog.Model\LogSubscriptionManager.cs:line 51
  InnerException: (null)

Here's the code that calls SaveChanges():

internal static readonly object DbSaveLockObject = new object();
public static void SaveChanges()
{
    Task.Factory.StartNew(() =>
    {
        lock (DbSaveLockObject)
        {
            Debug.WriteLine(DateTime.Now + ": SaveChanges in lock");
            Db.SaveChanges();
        }
    });
}
Tom Hunter
  • 5,714
  • 10
  • 51
  • 76

2 Answers2

3

The issue here is not serializing access to the DbContext object, it's avoiding access to the same object from different threads. So the solution is to ensure you create a new DbContext object every time you need to interact with the database.

using (var db = new SourceLogContext())
{
    db.LogSubscriptions.First(s => s.LogSubscriptionId == LogSubscriptionId)
        .Log.Add((LogEntry)e.LogEntry);
    db.SaveChanges();
}

What I'm not quite sure about is how you deal with updating the UI. If the code above is running in a background thread and the UI has previously been bound to the LogSubscription.Log collection, then the UI thread is referencing a different instance of the collection and you have to add the new entry to this collection as well.

_uiThread.Post(entry => Log.Add((LogEntry)entry), e.LogEntry);

A further complication is lazy loading where entities might not be loaded from the database until the user has access them through the UI. To handle this it seems you have to maintain at least one reference to the DbContext for the life of the UI thread..

private static readonly SourceLogContext DbUILazyLoadContext = new SourceLogContext();

I'd welcome comments on these points..

Tom Hunter
  • 5,714
  • 10
  • 51
  • 76
  • I didn't really get a better understanding than what I have in my answer. You can see the item being added to two collections in the [AddNewLogEntry](https://github.com/tomhunter-gh/SourceLog/blob/aed3718af18fcff471f04c83f83a0160b97b6829/SourceLog.Model/LogSubscription.cs#L90) method, once to the context collection and once to the "UI collection". – Tom Hunter Apr 02 '13 at 22:43
  • I had the same problem, it ended up being a backgroundworker trying to access the context while other processes were using it. I moved that call after the other processes finished and that solved it. I guess that queing them would also work. As far as I know EF should manage the threading, I never had such an issue until I created the background call. – Hannish Nov 23 '16 at 11:02
0

An AccessViolationException occurs only when verifiable managed code interacts with unmanaged code or with unsafe managed code.

You should go through this blog my MS on how to troubleshoot access voilation error: http://blogs.msdn.com/b/sqlservercompact/archive/2009/05/06/troubleshooting-access-violation-exception-while-using-sql-server-compact-database-with-ado-net-provider.aspx

Bull
  • 701
  • 1
  • 6
  • 13
  • Thanks, I've read that article. I'm using SQL CE 4.0 and as the article states "the application should serialize access to these objects" I've used `lock` to serialize the calls to `SaveChanges()`.. – Tom Hunter Aug 06 '12 at 10:04