Multiple connections in one transaction, on Azure - entity-framework

We are building an application, that is using a legacy framework utilising ADO.NET. This framework manages its own connection to the DB for calls to its code API.
For any customisations and custom tables we are using Entity Framework and hence a separate connection to the DB is made.
The application and DB is to be hosted on Azure.
What we would like to do is wrap both calls to the legacy framework and to Entity Framework into the same transaction.
Our understanding is that this is a distributed transaction, but this feature is not available in Azure.
Is there a way to make this to work in the Azure environment?
e.g.
using (var transaction = new TransactionScope())
{
using (var db = new EntityFrameworkDBEntities())
{
Order order = db.Orders.FirstOrDefault();
order.Name = "1";
db.SaveChanges();
}
using (var legacyAPI = new LegacyAPI())
{
Customer customer = legacyAPI.GetCustomers.FirstOrDefault();
customer.Name = "Charles";
legacyAPI.SaveCustomer(customer);
}
transaction.Complete();
}

AFAIK you need to use the same connection for your transaction since SQL Azure doesn't support distributed transaction. ADO.NET will upgrade to distributed transaction if you utilizes multiple connections in the same transaction even though all of them are connected to the same database.

As Shaun Xu says, you need to use just one connection. If you are able to change your LegacyAPI to take an open connection and a transaction as input, here is how, using EF6 and edmx:
var workspace = new MetadataWorkspace(new[] { "res://*/" }, new[] { Assembly.GetExecutingAssembly() });
using (var connection = new SqlConnection("Normal ADO connection string with MultipleActiveResultSets=True"))
{
using (var entityConnection = new EntityConnection(workspace, connection, false))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
using (var db = new EntityFrameworkDBEntities(entityConnection))
{
db.Database.UseTransaction(transaction);
// Do stuff with db
db.SaveChanges();
}
// Do ADO stuff on LegacyAPI using the connection and transaction objects
transaction.Commit();
}
}
}
To obtain the extra constructor on your dbcontext, you make this partial class, where false indicates that you open and close the connection manually.
partial class EntityFrameworkDBEntities
{
public EntityFrameworkDBEntities(DbConnection connection) : base(connection, false) { }
}
As a bonus you now only need one connection string in your config and it doesn't include all the useless EF junk that normally comes with it (metadata=res://*/blabla).
This also works if, say, you have a database with multiple schemas and an edmx for each. Note that although the EntityConnections are identical, you need one for each dbcontext.

Related

Entity Framework 5 using Db Transactions "connection is already part of a local or distributed transaction"

I have not had any luck with transactions and entity framework 5. I have the following code:
context.Database.Connection.Open();
transaction = context.Database.Connection.BeginTransaction(IsolationLevel.Serializable);
//some work happens
context.SaveChanges();
//some additional work
context.SaveChanges();
transaction.Commit();
At the very first context.SaveChanges call, I get an exception: "Connection is already part of a local or a distributed transaction"
Right now I am actually just doing a trivial proof of concept where all I am doing is attaching an entity, marking it as modified and then calling save changes.
As a troubleshooting deal, I put in an event handler for when the connection state changes and had a breakpoint in there. Doing that, I verified that the connection did not close on me between when I started the transaction and when I called save changes.
Any help figuring out why it is giving me that exception would be tremendously appreciated.
This is the way we used transactions before. It worked for us:
public void DoSomething()
{
using (var db = GetContext())
{
using (var ts = GetTransactionScope())
{
//do stuff
db.SaveChanges();
ts.Complete();
}
}
}
public TransactionScope GetTransactionScope()
{
var tso = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
return new TransactionScope(TransactionScopeOption.Required, tso);
}
If for some reason you have to do multiple SaveChanges calls in one transaction the recommended way is to wrap them in a TransactionScope:
using(var tran = new TransactionScope())
{
using(var context = new MyContext())
{
//some work happens
context.SaveChanges();
//some additional work
context.SaveChanges();
}
tran.Complete(); // without this call the transaction is rolled back.
}
The default isolation level is serializable. Each connection that is opened within the transaction enlists in this transaction. By default, EF always opens and closes connections when it executes queries.
I guess the cause of this exception you've got is that EF creates a transaction object itself when it executes SaveChanges. It tries to use its connection to start this transaction, but the connection is already part of the transaction you created. By using a TransactionScope, the EF transaction just enlists in the ambient transaction.

Entity Framework 6: Data Manipulation

I'm new to EF and I've only worked with EF 6. When I use it to access the data from a database that I have already designed I don't see any method that allows me to manipulate data. To solve this temporarily I created some stored procedures for adding, deleting and updating data.
I would like to know if what I am doing is the right way to manipulate data in EF or not. In case it is not the right way how can I do this using the built in features of EF6. MSDN said there is an add object but couldn't find it.
There have been some changes in the API. EF6 does not use ObjectContext anymore, it uses a DbContext. This can be generated from a Database Model, or created using a Model first approach.
Old syntax:
objectContext.AddToUsers(user);
is now:
dbContext.Users.Add(user);
Here are some basic samples:
insert:
using(var dbContext = new MyDbContext())
{
var user = new User { ID=1, Name="Test" };
dbContext.Users.Add(user); // Add user
dbContext.SaveChanges(); // Save changes to DB
}
update:
using(var dbContext = new MyDbContext())
{
var user = dbContext.Users.Find(1);// find by ID = 1
user.Name = "New Name"; // Change name
dbContext.SaveChanges(); // Save changes to DB
}
delete:
using(var dbContext = new MyDbContext())
{
var user = dbContext.Users.Find(1);// find by ID = 1
dbContext.Users.Remove(user); // delete user
dbContext.SaveChanges(); // Save changes to DB
}
So, no need for stored procedures.. definetly not needed for simple CRUD.

Entity Framework MigrateDatabaseToLatestVersion giving error

I am attempting to use Entity Framework code based migrations with my web site. I currently have a solution with multiple projects in it. There is a Web API project which I want to initialize the database and another project called the DataLayer project. I have enabled migrations in the DataLayer project and created an initial migration that I am hoping will be used to create the database if it does not exist.
Here is the configuration I got when I enabled migrations
public sealed class Configuration : DbMigrationsConfiguration<Harris.ResidentPortal.DataLayer.ResidentPortalContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(Harris.ResidentPortal.DataLayer.ResidentPortalContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
The only change I made to this after it was created was to change it from internal to public so the WebAPI could see it and use it in it's databaseinitializer. Below is the code in the code in the Application_Start that I am using to try to initialize the database
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
If I run this whether or not a database exists I get the following error
Directory lookup for the file "C:\Users\Dave\Documents\Visual Studio 2012\Projects\ResidentPortal\Harris.ResidentPortal.WebApi\App_Data\Harris.ResidentPortal.DataLayer.ResidentPortalContext.mdf" failed with the operating system error 2(The system cannot find the file specified.).
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.
It seems like it is looking in the totally wrong place for the database. It seems to have something to do with this particular way I am initializing the database because if I change the code to the following.
Database.SetInitializer(new DropCreateDatabaseAlways<ResidentPortalContext>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
The database will get correctly created where it needs to go.
I am at a loss for what is causing it. Could it be that I need to add something else to the configuration class or does it have to do with the fact that all my migration information is in the DataLayer project but I am calling this from the WebAPI project?
I have figured out how to create a dynamic connection string for this process. You need to first add this line into your EntityFramework entry on Web or App.Config instead of the line that gets put there by default.
<defaultConnectionFactory type="<Namespace>.<ConnectionStringFacotry>, <Assembly>"/>
This tells the program you have your own factory that will return a DbConnection. Below is the code I used to make my own factory. Part of this is a hack to get by the fact that a bunch of programmers work on the same set of code but some of us use SQL Express while others use full blown SQL Server. But this will give you an example to go by for what you need.
public sealed class ResidentPortalConnectionStringFactory: IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["PortalDatabase"].ConnectionString);
//save off the original catalog
string originalCatalog = builder.InitialCatalog;
//we're going to connect to the master db in case the database doesn't exist yet
builder.InitialCatalog = "master";
string masterConnectionString = builder.ToString();
//attempt to connect to the master db on the source specified in the config file
using (SqlConnection conn = new SqlConnection(masterConnectionString))
{
try
{
conn.Open();
}
catch
{
//if we can't connect, then append on \SQLEXPRESS to the data source
builder.DataSource = builder.DataSource + "\\SQLEXPRESS";
}
finally
{
conn.Close();
}
}
//set the connection string back to the original database instead of the master db
builder.InitialCatalog = originalCatalog;
DbConnection temp = SqlClientFactory.Instance.CreateConnection();
temp.ConnectionString = builder.ToString();
return temp;
}
}
Once I did that I coudl run this code in my Global.asax with no issues
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
using (ResidentPortalUnitOfWork temp = new ResidentPortalUnitOfWork())
{
temp.Context.Database.Initialize(true);
}

Firebird and Entity Framework Transaction Rollback

I need to use a Transaction Scope with Entity Framework 4 and a Firebird database. I am using the FireBird Entity Framework provider.
My problem is that once SaveChanges has been called on an object, the data is persisted to the database, instead of when transactionScope.Complete() is called. This results in data never rolling back, even if an exception occurs inside the using (TransactionScope ...) block.
This seems to be a problem with the FireBird DB, I have tested the exact same code with MS SQL 2008 and RollBack works correctly.
What do I need to do to enable Rolling Back with FireBird?
using ( var context = new Model1Container() )
{
bool success = false;
using ( TransactionScope transactionScope = new TransactionScope() )
{
PERSON person = new PERSON();
person.NAME = "test";
context.AddToPERSON(person);
context.SaveChanges(SaveOptions.DetectChangesBeforeSave);
success = true;
//transactionScope.Complete(); If this line is not hit, Transaction should Roll Back, but it does not.
}
if ( success )
{
context.AcceptAllChanges();
}
}
For firebird you need to explicitly say that it has to participate by adding Enlist=True in the connectionstring.

EF Code First - Recreate Database If Model Changes

I'm currently working on a project which is using EF Code First with POCOs. I have 5 POCOs that so far depends on the POCO "User".
The POCO "User" should refer to my already existing MemberShip table "aspnet_Users" (which I map it to in the OnModelCreating method of the DbContext).
The problem is that I want to take advantage of the "Recreate Database If Model changes" feature as Scott Gu shows at: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - What the feature basically does is to recreate the database as soon as it sees any changes in my POCOs. What I want it to do is to Recreate the database but to somehow NOT delete the whole Database so that aspnet_Users is still alive. However it seems impossible as it either makes a whole new Database or replaces the current one with..
So my question is: Am I doomed to define my database tables by hand, or can I somehow merge my POCOs into my current database and still take use of the feature without wipeing it all?
As of EF Code First in CTP5, this is not possible. Code First will drop and create your database or it does not touch it at all. I think in your case, you should manually create your full database and then try to come up with an object model that matches the DB.
That said, EF team is actively working on the feature that you are looking for: altering the database instead of recreating it:
Code First Database Evolution (aka Migrations)
I was just able to do this in EF 4.1 with the following considerations:
CodeFirst
DropCreateDatabaseAlways
keeping the same connection string and database name
The database is still deleted and recreated - it has to be to for the schema to reflect your model changes -- but your data remains intact.
Here's how: you read your database into your in-memory POCO objects, and then after the POCO objects have successfully made it into memory, you then let EF drop and recreate the database. Here is an example
public class NorthwindDbContextInitializer : DropCreateDatabaseAlways<NorthindDbContext> {
/// <summary>
/// Connection from which to ead the data from, to insert into the new database.
/// Not the same connection instance as the DbContext, but may have the same connection string.
/// </summary>
DbConnection connection;
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map;
public NorthwindDbContextInitializer(DbConnection connection, Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> map = null) {
this.connection = connection;
this.map = map ?? ReadDataIntoMemory();
}
//read data into memory BEFORE database is dropped
Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> ReadDataIntoMemory() {
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map = new Dictionary<Tuple<PropertyInfo,Type>,System.Collections.IEnumerable>();
switch (connection.State) {
case System.Data.ConnectionState.Closed:
connection.Open();
break;
}
using (this.connection) {
var metaquery = from p in typeof(NorthindDbContext).GetProperties().Where(p => p.PropertyType.IsGenericType)
let elementType = p.PropertyType.GetGenericArguments()[0]
let dbsetType = typeof(DbSet<>).MakeGenericType(elementType)
where dbsetType.IsAssignableFrom(p.PropertyType)
select new Tuple<PropertyInfo, Type>(p, elementType);
foreach (var tuple in metaquery) {
map.Add(tuple, ExecuteReader(tuple));
}
this.connection.Close();
Database.Delete(this.connection);//call explicitly or else if you let the framework do this implicitly, it will complain the connection is in use.
}
return map;
}
protected override void Seed(NorthindDbContext context) {
foreach (var keyvalue in this.map) {
foreach (var obj in (System.Collections.IEnumerable)keyvalue.Value) {
PropertyInfo p = keyvalue.Key.Item1;
dynamic dbset = p.GetValue(context, null);
dbset.Add(((dynamic)obj));
}
}
context.SaveChanges();
base.Seed(context);
}
System.Collections.IEnumerable ExecuteReader(Tuple<PropertyInfo, Type> tuple) {
DbCommand cmd = this.connection.CreateCommand();
cmd.CommandText = string.Format("select * from [dbo].[{0}]", tuple.Item2.Name);
DbDataReader reader = cmd.ExecuteReader();
using (reader) {
ConstructorInfo ctor = typeof(Test.ObjectReader<>).MakeGenericType(tuple.Item2)
.GetConstructors()[0];
ParameterExpression p = Expression.Parameter(typeof(DbDataReader));
LambdaExpression newlambda = Expression.Lambda(Expression.New(ctor, p), p);
System.Collections.IEnumerable objreader = (System.Collections.IEnumerable)newlambda.Compile().DynamicInvoke(reader);
MethodCallExpression toArray = Expression.Call(typeof(Enumerable),
"ToArray",
new Type[] { tuple.Item2 },
Expression.Constant(objreader));
LambdaExpression lambda = Expression.Lambda(toArray, Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(tuple.Item2)));
var array = (System.Collections.IEnumerable)lambda.Compile().DynamicInvoke(new object[] { objreader });
return array;
}
}
}
This example relies on a ObjectReader class which you can find here if you need it.
I wouldn't bother with the blog articles, read the documentation.
Finally, I would still suggest you always back up your database before running the initialization. (e.g. if the Seed method throws an exception, all your data is in memory, so you risk your data being lost once the program terminates.) A model change isn't exactly an afterthought action anyway, so be sure to back your data up.
One thing you might consider is to use a 'disconnected' foreign key. You can leave the ASPNETDB alone and just reference the user in your DB using the User key (guid). You can access the logged in user as follows:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
And then use the User's key as a FK in your DB:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
This approach decouples your DB with the ASPNETDB and associated provider architecturally. However, operationally, the data will of course be loosely connected since the IDs will be in each DB. Note also there will be no referential constraints, whcih may or may not be an issue for you.