I'm working with an existing set of code that manages multiple database updates in a single transaction. Here is a simplified example:
Database db = DatabaseFactory.CreateDatabase();
using (DbConnection dbConnection = db.CreateConnection())
{
dbConnection.Open();
DbTransaction dbTransaction = dbConnection.BeginTransaction();
try
{
//do work
dbTransaction.Commit();
}
catch (Exception ex)
{
dbTransaction.Rollback();
}
}
I am also using EntityFramework in this same project for new development. Below is a simplified example of the usage of my repository class:
List<ThingViewModel> things = new List<ThingViewModel>();
// populate list of things
IObjectRepository thingRepository = new ThingRepository();
thingRepository.AddThings(things);
thingRepository.Save();
I want the work done in 'AddThings' to happen as part of the transaction in the first block of code.
Is there some clean way of blending my repository pattern into this existing code or vice-versa? I'm not at the point yet where it is feasible to rewrite the existing code to be entirely within EntityFramework, so I'm looking for some some interim approach.
I have tried passing the transaction from the older code into the repository, and thus EntityFramework, but that does not seem to work. I have also tried passing the ObjectContext back out to the older code in order to enlist it in the transaction. Neither approach works.
I cannot believe that I am the first person to encounter this hurdle in migrating existing code to EntityFramework... there must be something I am not considering.
I'll list the things that I have tried below:
using (TransactionScope transactionScope = new TransactionScope())
{
Database db = DatabaseFactory.CreateDatabase();
using (DbConnection dbConnection = db.CreateConnection())
{
dbConnection.Open();
DbTransaction dbTransaction = dbConnection.BeginTransaction();
try
{
//do work
dbTransaction.Commit();
}
catch (Exception ex)
{
dbTransaction.Rollback();
}
}
Thing thing = new Thing(){
Prop1 = Val1,
Prop2 = Val2
};
ThingObjectContext context = new ThingObjectContext();
context.Things.AddObject(thing);
context.SaveChanges();
transactionScope.Complete();
}
This last example 'works', it does not function as a transaction. When the EF insert fails, the EL commands are not rolled back by the TransactionScope. If I don't put those explicit calls to .Commit() and .SaveChanges(), nothing happens. I would really like for this to share the same connection if possible. Two other variations of this I am currently playing around with is trying to use the same connection between both EF and EL as well as use EnlistTransaction on one side or the other. Definitely trying to keep this from becoming a MSDTC - don't want the extra overhead associated with that.
Use TransactionScope instead of explicit transaction management. You'll simplify the overall code and everything you do should automatically detect and use the same transaction.
Is there any way you can call Database.GetOpenConnection() instead of CreateConnection() in your EL code, and pass in the things.Connection that you create inside of a TransactionScope block? I haven't tested this, but that is what I would try first.
Related
I have a weird problem with Entity Framework code first migrations. I've been using EF and code first migrations on a project for months now and things are working fine. I recently created a new migration and when running Update-Database a restored backup of my database I get this error:
The model backing the context has changed since the database was
created. Consider using Code First Migrations to update the database
The migration does something like the following:
public override void Up()
{
using (SomeDbContext ctx = new SomeDbContext())
{
//loop through table and update rows
foreach (SomeTable table in ctx.SomeTables)
table.SomeField = DoSomeCalculation(table.SomeField);
ctx.SaveChanges();
}
}
I'm not using the Sql() function because DoSomeCalculation must be done in C# code.
Usually when I get something like this is means that I have updated my model somehow and forgot to create a migration. However that's not the case this time. The weird thing is that the error isn't even occurring on a migration that I created a few days ago and had been working fine.
I looked a quite a few articles about this and they all seems to say call
Database.SetInitializer<MyContext>(null);
Doing that does seem to work, but my understanding (based on this article) is that doing that will remove EF's ability to determine when the database and model are out of sync. I don't want to do that. I just want to know why it thinks they are out of sync all of a sudden.
I also tried running Add-Migration just to see if what it thought changed about the model but it won't let me do that stating that I have pending migrations to run. Nice catch 22, Microsoft.
Any guesses as to what's going on here?
I'm wondering if maybe the fact that migration listed above is using EntityFramework is the problem. Seems like maybe since it's not the latest migration anymore, when EF gets to it tries to create a SomeDbContext object it checks the database (which is not fully up to date yet since we're in the middle of running migrations) against my current code model and then throws the "context has changed" error.
It's possibly related to your using EF within the migration. I'm not sure how you're actually managing this, unless you've set a null database initialiser.
If you need to update data within a migration, use the Sql function, e.g.
Sql("UPDATE SomeTable SET SomeField = 'Blah'");
You should note that the Up() method is not actually running at the time of doing the migration, it's simply used to set up the migration which is then run later. So although you may think you've done something in the migration above the bit where you're using EF, in reality that won't have actually run yet.
If you cannot refactor your calculation code so it can be written in SQL, then you would need to use some mechanism other than migrations to run this change. One possibility would be to use the Seed method in your configuration, but you would need to be aware that this does not keep track of whether the change has been run or not. For example...
internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(MyContext context)
{
// Code here runs any time ANY migration is performed...
}
}
I tried replacing the EntityFramework code with regular ADO.NET code and it seems to work. Here is what it looks like:
public override void Up()
{
Dictionary<long, string> idToNewVal = new Dictionary<long, string>();
using (SqlConnection conn = new SqlConnection("..."))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("SELECT SomeID, SomeField FROM SomeTable", conn))
{
SqlDataReader reader = cmd.ExecuteReader();
//loop through all fields, calculating the new value and storing it with the row ID
while (reader.Read())
{
long id = Convert.ToInt64(reader["SomeID"]);
string initialValue = Convert.ToString(reader["SomeField"]);
idToNewVal[id] = DoSomeCalculation(initialValue);
}
}
}
//update each row with the new value
foreach (long id in idToNewVal.Keys)
{
string newVal = idToNewVal[id];
Sql(string.Format("UPDATE SomeTable SET SomeField = '{0}' WHERE SomeID = {1}", newVal, id));
}
}
Everytime when I open the database connection, the very first interaction with the database takes a lot of time. From the second interaction on the "speed" is much higher. I think it's an optimization of the Entity Framework and it looks like something good. But it's still the same even if I'm working with more than one database. The very first interaction with the first database is slow. But the first interaction with the second database is fast. Why isn't the first interaction with another database slow too? Another problem is that the Initializer only works with the first database. (I think this problem is caused by this optimization)
Related to this question (asked by myself):
Entity Framework 6 SetInitializer DbContext does not work for SQL Server Express and SQL Server Compact
Does anyone know how this optimization works and how to disable it? I don't actually work with the databases I compare them, for this case it would be nice to disable any optimization.
I'm wrote an answer as comment is not appropriate.
For the speed Yuliam pointed you to the good link.
For the rest, I'm not sure to understand your problem.
For me the following code (pertinent extract)
class Program {
static void Main(string[] args) {
Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
String cs = #"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";
using (TestEFContext ctx = new TestEFContext(cs)) {
Console.WriteLine(ctx.Orders.Count());
}
cs = #"Data Source=ALIASTVALK;Initial Catalog=TestEF2;Integrated Security=True; MultipleActiveResultSets=True";
using (TestEFContext ctx = new TestEFContext(cs)) {
Console.WriteLine(ctx.Orders.Count());
}
}
}
well create and recreate databases TestEF and TestEF2.
Did I misunderstand your question ?
===== Response to comment:
In this case both databases are always (re)created:
first run: creation
use SSMS to populate some data in some tables
second run
check tables content with SSMS : tables are empty <=> databases were recreated at second run.
Only trivial difference with your case: I use EF 6;
I'm using Entity Framework 5 with Code First. I've written a custom IDbConnectionFactory which I want to use for all the connections made by my DbContext class, so early on in the application's lifecycle, before any database work is done, I call
Database.DefaultConnectionFactory = new MyConnectionFactory();
However, MyConnectionFactory.CreateConnection is never called, which suggests to me that EF's changed it back - but the debugger shows that it's still a MyConnectionFactory after several queries have run. For some reason, it's just not using it.
My DbContext is initialised by passing the name of a connection string from the app.config file, and those connection strings do specify an explicit provider (as indeed they have to) so I'm wondering if that's causing a per-connection override of the connection factory based on the connection string. Does this happen and can I stop it without registering a completely new provider (although maybe that's not too hard to do?).
Whatever I see online about this (much obscured by the defaultConnectionFactory tag in various app.config examples) suggests you can just change it to an IDbConnectionFactory instance of your choice and it'll work, but mine isn't behaving.
The purpose of this is to allow me to run a particular set of SQL statements whenever a new connection is opened, so the second part of this question would be does anybody know a better way to do this?
I know it is not ideal but this worked for me:
public class DBBase : DbContext
{
public DBBase(string nameOrConnectionString)
: base(Database.DefaultConnectionFactory.CreateConnection(nameOrConnectionString), true)
{
}
// ...
}
You need to get the connection that you built for each call that you are wanting to use. For example using the following code.
private static void UsingCustomConnection()
{
using (var conn = Database.DefaultConnectionFactory.CreateConnection("YourDbName"))
{
using (var context = new YourContext(conn))
{
context.Destinations.Add(new Destination {Name = "Colorado"});
context.SaveChanges();
}
}
}
You will need to setup this in YourContext
public YourContext(DbConnection connection)
: base(connection, contextOwnsConnection: false)
{
}
I want to use TransactionScope class in my business layer to manage database operation in data access layer.
Here is my sample code. When i execute it, it tries to enable the dtc. I want to do the operation without enable dtc.
I already checked https://entlib.codeplex.com/discussions/32592 article. It didn't work for me. I read many articles on this subject but none of them really touch enterprise library or i didn't see.
by the way, i am able to use TransactionScope using dotnet sql client and it works pretty well.
what would be the inside of SampleInsert() method?
Thanks,
Business Layer method:
public void SampleInsert()
{
using (TransactionScope scope = new TransactionScope())
{
Sample1DAL dal1 = new Sample1DAL(null);
Sample2DAL dal2 = new Sample2DAL(null);
Sample3DAL dal3 = new Sample3DAL(null);
dal1.SampleInsert();
dal2.SampleInsert();
dal3.SampleInsert();
scope.Complete();
}
}
Data Access Layer method:
//sampleInsert method structurally same for each 3 dal
public void SampleInsert()
{
Database database = DatabaseFactory.CreateDatabase(Utility.DATABASE_INFO); ;
using (DbConnection conn = database.CreateConnection())
{
conn.Open();
DbCommand cmd = database.GetStoredProcCommand("P_TEST_INS", "some value3");
database.ExecuteNonQuery(cmd);
}
}
Hi yes this will enable dtc because you are creating 3 DB connections within one TransactionScope . When more than one DB connection is created within same TransactionScope the local transaction escalate to Distributed Transaction and hence dtc will be enabled to manage Distributed Trnsactions.You will have to do it in a way that only one DB connection is created for entire TransactionScope. I hope this will give you an idea.
After research and waching query analyzer, I changed the SampleInsert() body as follows and it worked. The problem was as ethicallogics mentioned opening new connection each time i access the database.
public void SampleInsert()
{
Database database = DatabaseFactory.CreateDatabase(Utility.DATABASE_INFO);
using (DbCommand cmd = database.GetStoredProcCommand("P_TEST_INS", "some value1"))
{
database.ExecuteNonQuery(cmd);
}
}
I'm trying to update a table using JPA
EntityManager em=JPA.em();
EntityTransaction entr = em.getTransaction();
try{
if(!entr.isActive())
entr.begin();
Tblrecordtypefields updateTblrecordtypes = em.find(Tblrecordtypefields.class,9);
updateTblrecordtypes.setFieldlabel("JPATest");
em.getTransaction().commit();
}finally
{
if(entr.isActive())
entr.rollback();
}
i'm getting the error
NullPointerException occured : null at
updateTblrecordtypes.setFieldlabel("JPATest");
What should i do.
I see some possible issues in there:
First, Play manages the transactions on it's own. A transaction is created at the beginning of the request and committed (rollback if exception) at the end. You are trying to force your way into it, that's not recommended. To manage the entity, just do an entity.save() to mark it as "to be saved" and don't do tht to ignore any changes.
Second, if you are using the Model class in Play (as you should) you can use the "find" and "findById" methods provided by this class. This is recommened, instead of using the EntityManager directly.
See the documentation for more information.
Basically, redo your code to follow the Play way, to avoid problems :)
EDIT: as a clarification, I'm not really answering your question on why you get the NPE, but I think that as you are forcing your way into the settings of the framework you might (maybe not!) be seeing unexpected artifacts that will dissapear once you fix your code to follow convention.
If after that you still have the error let us know :)
This means that there is no row with ID 9 in the database table mapped by the entity Tblrecordtypefields.
BTW: I find it very questionable to commit a transaction in a method which is not necessary the one that started the transaction.
I have changed my code as below
Tblrecordtypefields updateTblrecordtypeFields = Tblrecordtypefields.findById(9);
updateTblrecordtypeFields.setFieldlabel("Test");
validation.valid(updateTblrecordtypeFields);
if(validation.hasErrors())
{
updateTblrecordtypeFields.refresh();
}
else
{
updateTblrecordtypeFields.save();
}
in my model class
public void setFieldlabel(String fieldlabel) {
this.fieldlabel = fieldlabel;
}
Works Fine.....