We have two models, a Shared model and an Individual model, each with their own context. The Shared model includes a listing of all individual items along with various metadata. Each individual item has a foreign key back to this listing.
When running migrations to the Individual model we connect back to the Shared context during the "Seed" method to grab some of the listing's metadata for that item. There may be multiple instances of each type of context active at a time, so we sneak pieces of our desired Shared context's connection string into the Individual string by way of the "Application Name" attribute and dynamically generate the Shared one in code. It's a bit of a cheat, but it's worked fine for a very long time while using Entity Framework 6.0.1, however a recent upgrade to 6.1.3 seems to have broken this and I don't know why.
Here's some of our code:
public class IndividualConfiguration : DbMigrationsConfiguration<IndividualContext>
{
protected override void Seed(IndividualContext context)
{
var store = context.Stores.FirstOrDefault();
var storeListingId = store.StoreListingId;
var sharedContext = GetSharedContextFromIndividualContext(context);
var storeListing = sharedContext.StoreListings.FirstOrDefault(p => p.Id == storeListingId);
/* Use listing metadata for things... */
}
public SharedContext GetSharedContextFromIndividualContext(IndividualContext context)
{
var connString = context.Database.Connection.ConnectionString;
var builder = new SqlConnectionStringBuilder(connString);
/* Derive shared context info from the Application Name... */
builder.InitialCatalog = derivedCatalog;
builder.DataSource = derivedDataSource;
builder.ApplicationName = string.Empty;
return new SharedContext(builder.ConnectionString);
}
}
The migration command I enter into the Nuget Package Manager Console is:
update-database -configuration "IndividualConfiguration"
-ConnectionString "Data Source=localhost;Initial Catalog=IndividualDatabase; Integrated Security=True;
MultipleActiveResultSets=True; Application Name=SharedDatabase|localhost"
-ConnectionProviderName "System.Data.SqlClient" -force –verbose
Notice how "SharedDatabase|localhost" is used in the Application name. Those are the catalog and data source of the matching shared context. Looking at the results via the debugger, I can confirm that "builder.ConnectionString" has built the correct connection string, which would be:
"Data Source=localhost;Initial Catalog=SharedDatabase;Integrated Security=True;MultipleActiveResultSets=True;Application Name="
...and yet, when looking up the "StoredListings" DbSet from the returned sharedContext, the code errors out with "Invalid object name 'dbo.StoreListings'."
While troubleshooting this, I tried modifying the code that gets the SharedContext to see when things go wrong:
var ctx = new SharedContext(builder.ConnectionString);
var connBefore = ctx.Database.Connection.ConnectionString;
string latestMigration = ctx.Database.SqlQuery<string>("SELECT TOP 1 MigrationId FROM [__MigrationHistory] ORDER BY MigrationId DESC").FirstOrDefault();
var connAfter = ctx.Database.Connection.ConnectionString;
throw new Exception("CONN BEFORE: " + connBefore + Environment.NewLine + Environment.NewLine + "CONN AFTER : " + connAfter + Environment.NewLine + Environment.NewLine + "LAST MIGRATION: " + latestMigration);
...and ended up with:
CONN BEFORE: Data Source=localhost;Initial Catalog=SharedDatabase;Integrated Security=True;MultipleActiveResultSets=True;Application Name=
CONN AFTER : Data Source=localhost;Initial Catalog=IndividualDatabase; Integrated Security=True; MultipleActiveResultSets=True; Application Name=SharedDatabase|localhost
LAST MIGRATION: 201607081517538_LatestIndividualMigration
So for some reason, despite the shared context's connection string being correct, upon USING the connection it reverts back to the same individual string I used in the migration command.
Does anyone know why that second context is getting ignored? I can confirm that if I revert back to Entity Framework 6.0.1 everything starts working again, so I want to say that something changed from 6.0.1 to 6.1.3 that invalidates our code but I have no idea what that could be.
Related
I need to run a raw sql query, but I'm getting an error when I try to open the connection to the database. "The connection was not closed. The connection's current state is open."
_loginValidator and _contactService are passed into the controller through DI:
services.AddScoped<ILoginValidator, LoginValidator>();
services.AddScoped<IContactService, ContactService>();
The two lines below are in an action function of the controller. If I switch the two lines, the error goes away...:
var validationErrors = _loginValidator.Validate(id, "");
var user = _contactService.GetContact(id);
Here is _loginValidator.Validate. If I comment out the second line, the error goes away...:
public LoginValidationResult Validate(int userId, string encryptedPassword)
{
var vr = new LoginValidationResult();
var user = _context.Users.Include(u => u.LoginUserQuestionAnswers).FirstOrDefault(u => u.Id == userId);
//...
}
Here is _contactService.GetContact. This is where I get the error:
public ContactDto GetContact(int id)
{
var conn = _context.Database.GetDbConnection();
//ERROR HERE!!!
conn.Open();
//work on conn, for example: ExecuteReader
conn.Close();
}
Notes:
If I comment out the _context line in the Validate(...) function, I do not get the error.
If I switch the two lines I listed in the action function, I do not get the error.
I think the problem is that EntityCore is not closing the connection after I finish using it in _loginValidator.Validate(...)
Anyone know how I can deal with this problem?
DB Connection is an unmanaged resource and you need to close it yourself. The best practice is to use a using statement for your DB connections.
See these links:
http://stackoverflow.com/questions/35077000/entity-framework-7-get-database-time
https://msdn.microsoft.com/en-us/data/dn456849.aspx
The connection being left open after the FirstOrDefault query is a bug. I filed https://github.com/aspnet/EntityFramework/issues/6581 for it and we just triaged it for the 1.0.2 release.
To workaround the bug for now I think you can check if the connection is already open and, if so, don't try to open it again.
I'm trying to save few entities with this code:
this.UserService.Users.Add(eUser);
if (SelectedRewindItems != null && SelectedRewindItems.Count > 0)
{
foreach (var ug in SelectedRewindItems)
{
HpmModel.Usergroup nUg = new HpmModel.Usergroup();
decimal numId;
var a = Decimal.TryParse(ug.Key.ToString(), out numId);
nUg.Groupid = numId;
nUg.Userid = eUser.Userid;
// eUser.Usergroups.Add(nUg);
this.UserService.Usergroups.Add(nUg);
}
}
var submitOp = this.UserService.SubmitChanges();
IsSuccess = true;
ActionMessageOnButtonSuccess = User.Fname + " " + User.Lname + " Added Successfully !!";
string message = null;
if (submitOp.EntitiesInError.Any())
{
message = string.Empty;
Entity entityInError = submitOp.EntitiesInError.First();
if (entityInError.EntityConflict != null)
{
EntityConflict conflict = entityInError.EntityConflict;
foreach (var cm in conflict.PropertyNames)
{
message += string.Format("{0}", cm);
}
}
else if (entityInError.ValidationErrors.Any())
{
message += "\r\n" + entityInError.ValidationErrors.First().ErrorMessage;
}
MessageBox.Show(message);
}
else
{
MessageBox.Show("Submit Done");
}
But I'm getting this error:
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state.
Inner exception message: Saving or accepting changes failed because more than one entity of type 'HpmModel.Usergroup' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
Source=EntityFramework
StackTrace:
at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass2a.b__27()
at System.Data.Entity.Infrastructure.DefaultExecutionStrategy.Execute[TResult](Func1 operation)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges()
at OpenRiaServices.DomainServices.EntityFramework.LinqToEntitiesDomainService1.InvokeSaveChanges(Boolean retryOnConflict) in c:\Code\Repos\openriaservices\OpenRiaServices.DomainServices.EntityFramework\Framework\LinqToEntitiesDomainService.cs:line 145
at OpenRiaServices.DomainServices.EntityFramework.LinqToEntitiesDomainService`1.PersistChangeSet() in c:\Code\Repos\openriaservices\OpenRiaServices.DomainServices.EntityFramework\Framework\LinqToEntitiesDomainService.cs:line 138
at OpenRiaServices.DomainServices.Server.DomainService.PersistChangeSetInternal()
at OpenRiaServices.DomainServices.Server.DomainService.Submit(ChangeSet changeSet)
InnerException: System.InvalidOperationException
HResult=-2146233079
Message=Saving or accepting changes failed because more than one entity of type 'HpmModel.Usergroup' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
Source=EntityFramework
StackTrace:
at System.Data.Entity.Core.Objects.ObjectStateManager.FixupKey(EntityEntry entry)
at System.Data.Entity.Core.Objects.EntityEntry.AcceptChanges()
at System.Data.Entity.Core.Objects.ObjectContext.AcceptAllChanges()
at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
InnerException:
When I checked the Database Entities got saved but still it is giving me this issues.
Is this because I'm trying save them after saving User & Then UserGroup entities separatly. or Child Entities should get saved with Parent Entities. I'm a beginner so facing challanges.
After wasting a lot of time, I came to know that I need to fix my EDMX file & Entity Code.
So I added in my entity:
[DatabaseGenerated( DatabaseGeneratedOption.Identity)]
In the SSDL file in my Users -> Usersgroup (1-M) Relationship
Usersgroup Id Node I Added:
StoreGeneratedPattern="Identity" [SSDL]
In CSDL:
ed:StoreGeneratedPattern="Identity"
In my code:
this.UserService.Users.Add(eUser);
if (SelectedRewindItems != null && SelectedRewindItems.Count > 0)
{
foreach (var ug in SelectedRewindItems)
{
HpmModel.Usergroup nUg = new HpmModel.Usergroup();
decimal numId;
var a = Decimal.TryParse(ug.Key.ToString(), out numId);
nUg.Groupid = numId;
nUg.Userid = eUser.Userid;
eUser.Usergroups.Add(nUg);
}
}
After applying these changes, SaveChanges() worked.
This blog post helped me.
I am using continuous integration with TeamCity, NUnit, and Git. I recently migrated (pardon the pun) from FluentMigrator to Entity Framework Migrations. I primarily did this to take advantage of the scaffolding functionality therein.
However, it is potentially possible to check in some changes to source control without having first scaffolded the changes into migrations (imagine the scenario where the application was not run before committing and pushing the commit). I am using a pre-tested commit workflow, so I would like to detect this problem in the pre-test rather than waiting until calling migrate.exe (which would be too late in my workflow and break the "green repository").
My question is, how to create a unit/integration test to detect when the migrations model doesn't match the context model so I can fail the build before attempting to migrate? Do note that I expect it not to match the database and would prefer not to access the database from the test.
I tried this approach:
[Test]
public void CheckWhetherEntityFrameworkMigrationsContextIsUpToDate()
{
Assert.DoesNotThrow(() =>
{
CallDatabase();
});
}
private void CallDatabase()
{
using (var ctx = new MyContext("SERVER=(local);DATABASE=MyDatabase;Integrated Security=True;"))
{
var tenant = (from t in ctx.Tenant
select t).FirstOrDefault();
}
}
But this will always fail when there are pending migrations (rather than just in the case where the migrations model is not in sync with the context model, which is what I am after).
Update
I have added a work item on the EntityFramework project for this issue. Hopefully, they will look into adding a way to do this.
In case anybody finds this useful, I test migrations by running all of them against a test database, using the following code:
[TestClass]
public class MigrationsTests
{
[TestMethod]
public void RunAll()
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
// back to 0
migrator.Update("0");
// up to current
migrator.Update();
// back to 0
migrator.Update("0");
}
}
This tests all Up and Down migrations, as well as detecting pending changes when automatic migrations is turned off.
NOTE: Make sure you run this against a test database. In my case the test project's app.config has the connectionString to the test database (just a local SQLExpress instance).
Here is some code that will check whether all model changes have been scaffolded to migrations or not.
bool HasPendingModelChanges()
{
// NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
var migrationsConfiguration = new Migrations.Configuration();
var migrator = new DbMigrator(migrationsConfiguration);
var scriptingMigrator = new MigratorScriptingDecorator(migrator);
try
{
// NOTE: Using InitialDatabase so history won't be read from the database
scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
}
catch (AutomaticMigrationsDisabledException)
{
return true;
}
return false;
}
I wanted the best of all the answers given here so far so here's what I've come up with:
[TestClass()]
public class MigrationsTests
{
[TestMethod()]
public void MigrationsUpDownTest()
{
// Unit tests don't have a DataDirectory by default to store DB in
AppDomain.CurrentDomain.SetData("DataDirectory", System.IO.Directory.GetCurrentDirectory());
// Drop and recreate database
BoxContext db = new BoxContext();
db.Database.Delete();
var configuration = new Migrations.Configuration();
var migrator = new DbMigrator(configuration);
// Retrieve migrations
List<string> migrations = new List<string>;
migrations.AddRange(migrator.GetLocalMigrations());
try
{
for (int index = 0; index < migrations.Count; index++)
{
migrator.Update(migrations[index]);
if (index > 0) {
migrator.Update(migrations[index - 1]);
} else {
migrator.Update("0"); //special case to revert initial migration
}
}
migrator.Update(migrations.Last());
}
catch (SqlException ex)
{
Assert.Fail("Should not have any errors when running migrations up and down: " + ex.Errors[0].Message.ToString());
}
// Optional: delete database
db.Database.Delete();
}
[TestMethod()]
public void PendingModelChangesTest()
{
// NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
var migrationsConfiguration = new Migrations.Configuration();
var migrator = new DbMigrator(migrationsConfiguration);
var scriptingMigrator = new MigratorScriptingDecorator(migrator);
try
{
// NOTE: Using InitialDatabase so history won't be read from the database
scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
}
catch (AutomaticMigrationsDisabledException)
{
Assert.Fail("Should be no pending model changes/migrations should cover all model changes.");
}
}
}
Couple things worth noting:
You need to install Entity Framework in your test project
You need to add [assembly: InternalsVisibleTo("MyTestsProject")] to the top of your Configuration class
You need to specify an Entity Framework connection string in App.config that is only for test project since database will be deleted and recreated frequently - if running on build machine keep in mind that parallel runs could result in conflicts so you may want to change string per build
I think this works better than Pablo Romeo's code. This upgrades and then downgrades one step again to catch missing items in the downgrade scripts.
[TestFixture]
class MigrationTest
{
[Test]
public void RunAll()
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
// Retrieve migrations
List<string> migrations = new List<string> {"0"}; // Not sure if "0" is more zero than the first item in list of local migrations
migrations.AddRange(migrator.GetLocalMigrations());
migrator.Update(migrations.First());
// Doe een stapje naar voren, en een stapje terug (www.youtube.com/watch?v=2sg1KAxuWKI)
// (Dutch pun) meaning: take a small step forward, and a small step back ;)
for (int index = 0; index < migrations.Count; index++)
{
migrator.Update(migrations[index]);
if (index > 0)
migrator.Update(migrations[index - 1]);
}
migrator.Update(migrations.Last());
migrator.Update(migrations.First());
}
}
EF Core has a pretty straight forward method:
myDbContext.Database.Migrate();
We have two different query strategies that we'd ideally like to operate in conjunction on our site without opening redundant connections. One strategy uses the enterprise library to pull Database objects and Execute_____(DbCommand)s on the Database, without directly selecting any sort of connection. Effectively like this:
Database db = DatabaseFactory.CreateDatabase();
DbCommand q = db.GetStoredProcCommand("SomeProc");
using (IDataReader r = db.ExecuteReader(q))
{
List<RecordType> rv = new List<RecordType>();
while (r.Read())
{
rv.Add(RecordType.CreateFromReader(r));
}
return rv;
}
The other, newer strategy, uses a library that asks for an IDbConnection, which it Close()es immediately after execution. So, we do something like this:
DbConnection c = DatabaseFactory.CreateDatabase().CreateConnection();
using (QueryBuilder qb = new QueryBuilder(c))
{
return qb.Find<RecordType>(ConditionCollection);
}
But, the connection returned by CreateConnection() isn't the same one used by the Database.ExecuteReader(), which is apparently left open between queries. So, when we call a data access method using the new strategy after one using the old strategy inside a TransactionScope, it causes unnecessary promotion -- promotion that I'm not sure we have the ability to configure for (we don't have administrative access to the SQL Server).
Before we go down the path of modifying the query-builder-library to work with the Enterprise Library's Database objects ... Is there a way to retrieve, if existent, the open connection last used by one of the Database.Execute_______() methods?
Yes, you can get the connection associated with a transaction. Enterprise Library internally manages a collection of transactions and the associated database connections so if you are in a transaction you can retrieve the connection associated with a database using the static TransactionScopeConnections.GetConnection method:
using (var scope = new TransactionScope())
{
IEnumerable<RecordType> records = GetRecordTypes();
Database db = DatabaseFactory.CreateDatabase();
DbConnection connection = TransactionScopeConnections.GetConnection(db).Connection;
}
public static IEnumerable<RecordType> GetRecordTypes()
{
Database db = DatabaseFactory.CreateDatabase();
DbCommand q = db.GetStoredProcCommand("GetLogEntries");
using (IDataReader r = db.ExecuteReader(q))
{
List<RecordType> rv = new List<RecordType>();
while (r.Read())
{
rv.Add(RecordType.CreateFromReader(r));
}
return rv;
}
}
We are using a SQL Server Data-tier application (dacpac or DAC pack) and I'm having a hard time finding the current version of the database.
Is there a way to obtain the current version using any of these methods:
From within SQL Server Management Studio
Via a SQL statement
Programmatically using .NET code
From within SQL Server Management Studio
From http://msdn.microsoft.com/en-us/library/ee210574.aspx
To view the details of a DAC deployed to an instance of the Database Engine:
Select the View/Object Explorer menu.
Connect to the instance of the from the Object Explorer pane.
Select the View/Object Explorer Details menu.
Select the server node in Object Explorer that maps to the instance, and then navigate to the Management\Data-tier Applications node.
The list view in the top pane of the details page lists each DAC deployed to the instance of the Database Engine. Select a DAC to display the information in the detail pane at the bottom of the page.
The right-click menu of the Data-tier Applications node is also used to deploy a new DAC or delete an existing DAC.
Via a SQL statement
SELECT instance_name, type_version FROM msdb.dbo.sysdac_instances
Via a SQL statement on Azure
SELECT instance_name, type_version FROM master.dbo.sysdac_instances
Programmatically using .NET code
Note that in DacFx 3.0 this is no longer valid. See my other answer for a way to do it.
C#
ServerConnection serverConnection;
string databaseName;
// Establish a connection to the SQL Server instance.
using (SqlConnection sqlConnection =
new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
serverConnection = new ServerConnection(sqlConnection);
serverConnection.Connect();
// Assumes default database in connection string is the database we are trying to query.
databaseName = sqlConnection.Database;
}
// Get the DAC info.
DacStore dacstore = new DacStore(serverConnection);
var dacInstance = dacstore.DacInstances[databaseName];
System.Diagnostics.Debug.Print("Database {0} has Dac pack version {1}.", databaseName, dacInstance.Type.Version);
VB.NET
Dim serverConnection As ServerConnection
Dim databaseName As String
' Establish a connection to the SQL Server instance.
Using sqlConnection As New SqlConnection(ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString)
serverConnection = New ServerConnection(sqlConnection)
serverConnection.Connect()
' Assumes default database in connection string is the database we are trying to query.
databaseName = sqlConnection.Database
End Using
' Get the DAC info.
Dim dacstore As New DacStore(serverConnection)
Dim dacInstance = dacstore.DacInstances(databaseName)
System.Diagnostics.Debug.Print("Database {0} has Dac pack version {1}.", databaseName, dacInstance.Type.Version)
In DacFx 3.0 the DacStore is no longer available. To get the version from C# code you need to query the database. Here's an example:
using System;
using System.Data;
using System.Data.SqlClient;
class Program
{
static void Main()
{
try
{
string version = GetDatabaseVersion(#"Initial Catalog=xxx;Data Source=yyy;Integrated Security=True;Pooling=False", false);
Console.WriteLine("Database has DAC pack version {0}.", version);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
Console.WriteLine();
Console.ResetColor();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey(true);
}
/// <summary>
/// Gets the database version.
/// </summary>
/// <param name="connectionString">The connection string of database to query.</param>
/// <param name="isAzure">True if we are querying an Azure database.</param>
/// <returns>DAC pack version</returns>
private static string GetDatabaseVersion(string connectionString, bool isAzure)
{
var connectionStringBuilder = new SqlConnectionStringBuilder(connectionString);
string instanceName = connectionStringBuilder.InitialCatalog;
string databaseToQuery = "msdb";
if (isAzure)
{
// On Azure we must be connected to the master database to query sysdac_instances
connectionStringBuilder.InitialCatalog = "Master";
databaseToQuery = "master";
}
string query = String.Format("select type_version from {0}.dbo.sysdac_instances WHERE instance_name = '{1}'", databaseToQuery, instanceName);
using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
command.CommandText = query;
command.CommandTimeout = 15;
command.CommandType = CommandType.Text;
var version = (string)command.ExecuteScalar();
return version;
}
}
}