I have a small app I am writing as an exercise to learn how to work with SQL databases in C# .NET. My app tries to open a connection to a database, and if it fails, it assumes that the database was not there and creates it. This database is used only by my app, and is always local, so it's a lot simpler than many other database situations. Here is the code, based on things I've read on SO and also in "Pro C# 7: With .NET and .NET Core", by Andrew Troelsen:
private static DbConnection connection = null;
private static MyAppModels context = null;
public static void SetupDatabase()
{
string dataProvider = "System.Data.SqlClient";
DbProviderFactory factory = DbProviderFactories.GetFactory(dataProvider);
// get connection object (closes automatically at end of using scope)
connection = factory.CreateConnection();
if (connection == null) { /* throw Exception */ }
string connectionString = @"Data Source=(localdb)\mssqllocaldb;Initial Catalog=MyAppDB;Integrated Security=True";
connection.ConnectionString = connectionString;
try { connection.Open(); }
catch (Exception)
{
// database could not be found; try to create it...
connection.ConnectionString = @"Data Source=(localdb)\mssqllocaldb;Integrated Security=True;database=master";
connection.Open();
string str = "CREATE DATABASE MyAppDB ON PRIMARY " +
"(NAME = MyApp_Data, FILENAME = 'C:\\Users\\Me\\Documents\\MyApp\\MyAppData.mdf', " +
"SIZE = 2MB, MAXSIZE = 50MB, FILEGROWTH = 10%) " +
"LOG ON (NAME = MyApp_Log, FILENAME = 'C:\\Users\\Me\\Documents\\MyApp\\MyAppLog.ldf', " +
"SIZE = 1MB, MAXSIZE = 10MB, FILEGROWTH = 10%)";
DbCommand myCommand = connection.CreateCommand();
myCommand.CommandText = str;
try { myCommand.ExecuteNonQuery(); }
catch (System.Exception ex) { /* throw Exception */ }
}
context = new MyAppModels(connection);
return;
}
This code seems to execute without a problem until I try to add data to a table, at which point I get the following error:
"The model backing the 'MyAppModels' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269)."
If I exit my app and re-open it, it's able to connect to the now-existing database and add records without a problem. But I don't want users of my app to be required to exit and re-open the app after first installing it, just to get around this problem!
I looked as several other posts here, including:
The model backing the 'POSContext' context has changed since the database was created
The model backing the context has changed since the database was created
These both seemed to point to the same solution. Also, the second one had the following quote from a member of the Microsoft EF team:
When a model is first created, we run a DatabaseInitializer to do things like create the database if it's not there or add seed data. The default DatabaseInitializer tries to compare the database schema needed to use the model with a hash of the schema stored in an EdmMetadata table that is created with a database (when Code First is the one creating the database). Existing databases won’t have the EdmMetadata table and so won’t have the hash…and the implementation today will throw if that table is missing. We'll work on changing this behavior before we ship the fial version since it is the default. Until then, existing databases do not generally need any database initializer so it can be turned off for your context type by calling:
Database.SetInitializer<YourDbContext>(null);
I don't really understand all this. But I tried adding that code to my DbContext class anyway:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MyAppModels>(null);
base.OnModelCreating(modelBuilder);
}
Now I get the following error when I try to add the first record to a table:
"An error occurred while updating the entries. See the inner exception for details."
Inner exception: "String or binary data would be truncated. The statement has been terminated."
And now, if I close and re-open the app, the same error occurs every time. So things are now worse. From looking at other SO posts, I see that this error is pointing to a problem where the data being entered into the database exceeds the field size. But I get the error on the first record I try to add and it definitely does NOT exceed any field size in that table.
I suspect that I should not be passing null to SetInitializer(). I noted the comment, "... we run a DatabaseInitializer to do things like create the database if it's not there or add seed data." My code is already trying to handle the database creation. Should my app not be doing that? How would SetInitializer() know the location to put the database or the MAXSIZE? And I don't think I need it to "add seed data", either.
I also noted this comment, "We'll work on changing this behavior before we ship the fial (sic) version since it is the default." That makes it sound like a bug. The comment was written ~9 years ago, so I would have hoped that it was already fixed.
In the meantime, does anyone have any tips for how to get this working right?
--Update--
I was able to get this working with a small addition to the creation code shown above:
myCommand.ExecuteNonQuery();
// close & try again to open the database
connection.Close();
connection.ConnectionString = @"Data Source=(localdb)\mssqllocaldb;Integrated Security=True;Initial Catalog=MyAppDB";
connection.Open();
Seems like it should be unnecessary, but it works...
Then I tried something else. I replaced all the code in SetupDatabase() with one line, as I had seen in several example tutorials:
context = new MyAppModels();
This allows/relies on EF to open the database, creating it, if necessary (in some undefined/default location). This worked when the database already existed, but when the database needed to be created, it failed:
System.Data.SqlClient.SqlException HResult=0x80131904
Message=CREATE FILE encountered operating system error 5(Access is denied.) while attempting to open or create the physical file 'C:\Users\MyAppDB.mdf'. CREATE DATABASE failed. Some file names listed could not be created. Check related errors. Source=.Net SqlClient Data Provider
Note the lack of a backslash between and MyAppDB.mdf. I don't know why this happens or how to fix it. I don't see anything obvious in App.config that would control this...
<connectionStrings>
<add name="MyAppConnection" connectionString="data source=(localdb)\mssqllocaldb;initial catalog=MyAppDB;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
Can someone help me work this out, so I can use "pure" EF, or do I need to go back to my hack that works...?