1

I am experiencing database connection errors with an ASP.NET application written in VB, running on three IIS servers. The underlying database is MS Access, which is on a shared network device. It uses Entity Framework, code first implementation and JetEntityFrameworkProvider.

The application is running stable. But, approximately 1 out of 1000 attempts to open the database connection fails with either one of the following two errors:

06:33:50   DbContext  "Failed to open connection at 2/12/2020 6:33:50 AM +00:00 with error: 
Cannot open database ''.  It may not be a database that your application recognizes, or the file may be corrupt.

Or

14:04:39   DbContext  "Failed to open connection at 2/13/2020 2:04:39 PM +00:00 with error: 
Could not use ''; file already in use.

One second later, with refreshing (F5), the error is gone and it works again.

Details about the environment and used code.

Connection String

<add name="DbContext" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=x:\thedatabase.mdb;Jet OLEDB:Database Password=xx;OLE DB Services=-4;" providerName="JetEntityFrameworkProvider" />

DbContext management

The application uses public property to access DbContext. DbContext is kept in the HttpContext.Current.Items collection for the lifetime of the request, and is disposed at it’s end.

Public Shared ReadOnly Property Instance() As DbContext
    Get
        SyncLock obj
            If Not HttpContext.Current.Items.Contains("DbContext") Then
                HttpContext.Current.Items.Item("DbContext") = New DbContext()
            End If

            Return HttpContext.Current.Items.Item("DbContext")
        End SyncLock
    End Get
End Property

BasePage inits and disposes the DbContext.

Protected Overrides Sub OnInit(e As EventArgs)
    MyBase.OnInit(e)
    DbContext = Data.DbContext.Instance
    ...
End Sub

Protected Overrides Sub OnUnload(e As EventArgs)
    MyBase.OnUnload(e)
    If DbContext IsNot Nothing Then DbContext.Dispose()
End Sub

What I have tried

Many of the questions on SO which address above error messages, deal with generally not being able to establish a connection to the database – they can’t connect at all. That’s different with this case. Connection works 99,99% of the time.

Besides that, I have checked:

  • Permissions: Full access is granted for share where .mdb (database) and .ldb (locking file) resides.
  • Network connection: there are no connection issues to the shared device; it’s a Gigabit LAN connection
  • Maximum number of 255 concurrent connections is not reached
  • Maximum size of database not exceeded (db has only 5 MB)
  • Changed the compile option from “Any CPU” to “x86” as suggested in this MS Dev-Net post

Quote: I was getting the same "Cannot open database ''" error, but completely randomly (it seemed). The MDB file was less than 1Mb, so no issue with a 2Gb limit as mentioned a lot with this error. It worked 100% on 32 bit versions of windows, but I discovered that the issues were on 64 bit installations. The app was being compiled as "Any CPU". I changed the compile option from "Any CPU" to "x86" and the problem has disappeared.

Nothing helped so far.

To gather more information, I attached an Nlog logger to the DbContext which writes all database actions and queries to a log file.

Shared Log As Logger = LogManager.GetLogger("DbContext")
Me.Database.Log = Sub(s) Log.Debug(s)

Investigating the logs I figured out that when one of the above errors occured on one server, another one of the servers (3 in total) has closed the db connection at exactly the same time. Here two examples which correspond to the above errors:

06:33:50   DbContext  "Closed connection at 2/12/2020 6:33:50 AM +00:00
14:04:39   DbContext  "Closed connection at 2/13/2020 2:04:39 PM +00:00

Assumption

When all connections of a DbContext have been closed, the according record is removed from the .ldb lock file. When a connection to the db is being opened, a record will be added to the lock file. When these two events occur at the exact same time, from two different servers, there is a write conflict to the .ldb lock file, which results in on of the errors from above.

Question

Can anyone confirm or prove this wrong? Has anyone experienced this behaviour? Maybe I am missing something else. I’d appreciate your input and experience on this.

If my assumption is true, a solution could be to use a helper class for accessing db, which catches and handles this error, waiting for a minimal time period and trying again.

But this feels kind of wrong. So I am also open to suggestions for a “proper” solution.

EDIT: The "proper" solution would be using a DBMS Server (as stated in the comments below). I'm aware of this. For now, I have to deal with this design mistake without being responsible for it. Also, I can't change it in the short run.

Maddin
  • 298
  • 3
  • 11

2 Answers2

1

I write this as an aswer because of space but this is not really an answer.

It's for sure an OleDb provider issue.
I think that is a sharing issue. You could do some tries:

  • use a newer OleDb provider instead of Microsoft.Jet.OLEDB.4.0. (if you have try 64 bits you could already have try another provider because Jet.OLEDB.4.0 is 32 bits only)
  • Implement a retry mechanism on the new DbContext()
  • Reading your tests this is probaly not your case. I THINK that Dispose does not always work properly on Jet.OLEDB.4.0 connections. I noted it on tests and I solved it using a different testing engine. Before giving up I used this piece of code
    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); GC.WaitForPendingFinalizers();
    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
    As you can understand reading this code, they are tries and the latest solution was changing the testing engine.

  • If your app is not too busy you could try to lock the db using a different mechanism (for example using a lock file). This is not really different from new DbContext() retries.

  • In late '90s I remember I had an issue related to disk sharing OS (I were using Novel Netware). Actually I have not experience in using mdb files on a network share. You could try to move the mdb on a folder shared with Windows
  • Actually I use Access databases only for tests. If you really need to use a single file database you could try other solutions: SQL Lite (you need a library, also this written by me, to apply code first https://www.nuget.org/packages/System.Data.SQLite.EF6.Migrations/ ) or SQL Server CE
  • Use a DBMS Server. This is for sure the best solution. As the writer of JetEntityFrameworkProvider I think that single file databases are great for single user apps (for this apps I suggest SQL Lite), for tests (I think that for tests JetEntityFrameworkProvider is great), for transfering data or, also, for readonly applications. In other cases use a DBMS Server. As you know, with EF, you can change from JetEntityFrameworkProvider to SQL Server or to MySql without effort.
bubi
  • 6,414
  • 3
  • 28
  • 45
  • Regarding point 2) "Implement a retry mechanism on the new DBContext()": The error occurs on the `open` event, not on the `new`. It's when the new DbContext is used for the forst time to query the db. You can see it in log. I forced a pause between the two `08:58:19 DbContext New instance created` ... paused ... `08:58:38 DbContext "Opened connection at 19.02.2020 08:58:38 +01:00` – Maddin Feb 19 '20 at 08:06
  • You can open the connection then pass the open connection to the context (it's one of the overloads). I mean `new DbContext(openConnection)`. – bubi Feb 19 '20 at 08:27
0

You went wrong at the design stage: The MS Access database engine is unfit for ASP.Net sites, and this is explicitly stated on multiple places, e.g. the official download page under details.

The Access Database Engine 2016 Redistributable is not intended .... To be used by ... a program called from server-side web application such as ASP.NET

If you really have to work with an Access database, you can run a helper class that retries in case of common errors. But I don't recommend it.

The proper solution here is using a different RDBMS which exhibits stateless behavior. I recommend SQL Server Express, which has limitations, but if you exceed those you will be far beyond what Access supports, and wont cause errors like this.

Erik A
  • 31,639
  • 12
  • 42
  • 67
  • You are right. I know of this. It was not designed by me and I can't change it in the short run. – Maddin Feb 19 '20 at 08:55
  • The point is: there's no proper solution when working with ASP.NET and Access, because an improper choice has been made to use Access in an unsupported way. You're bound to hack away at the limitations imposed by that decision, the only way to _make it proper_ is by switching to a different RDBMS. If you can't, don't feel bad about hacky solutions, because they may very well be the best ones for your needs. – Erik A Feb 19 '20 at 09:03