Use TransactionScope & SqlCommand to keep databases in sync - c#

We track the same information across two databases in tables that have a similar (enough) schema. When we update the data in one database we want to make sure the data stays in sync with the table in the other database.
We use Entity Framework 5 in both databases, so I had originally wanted to simply import a DbContext of the secondary database and use TransactionsScope to make sure the Create/Updates were atomic.
However, I quickly found out that would be a pain to code, since the table names are the same (anyone working in this controller would have to refer to the Product table as <Conext>.Product), so I used a SqlConnection object for the secondary table, but received some results I don't quite undestand.
If I use the syntax below, the two tables will update atomically/everything goes as planned.
var scopeOptions = new TransactionOptions();
scopeOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
scopeOptions.Timeout = TimeSpan.MaxValue;
var sqlConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Monet"].ConnectionString);
sqlConn.Open();
SqlCommand sqlCommand = sqlConn.CreateCommand();
sqlCommand.CommandText = InsertMonetProduct(product);
using (var ts = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
db.Product.Add(product);
db.SaveChanges();
sqlCommand.ExecuteNonQuery();
ts.Complete();
}
However if I use this syntax below the code crashes on the db.SaveChanges() command with the following message:
Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool.
var scopeOptions = new TransactionOptions();
scopeOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
scopeOptions.Timeout = TimeSpan.MaxValue;
using (var ts = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
using(var sqlConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Monet"].ConnectionString))
{
sqlConn.Open();
using (SqlCommand sqlCommand = sqlConn.CreateCommand())
{
sqlCommand.CommandText = InsertMonetProduct(product);
sqlCommand.ExecuteNonQuery();
db.Product.Add(product);
db.SaveChanges();
}
ts.Complete();
}
}
Any idea why the first syntax works and the second crashes? From what I've read online this is supposed to be a change made on the database/database server itself.

The second bit of code is causing an error because it is opening multiple database connections within a single TransactionScope. When a program opens a second database connection inside of a single scope, it gets promoted to a distributed transaction. You can read more information about distributed transactions here.
Searching for "multiple database connections in one transaction scope" is going to help you find a lot more StackOverflow posts. Here are two relevant ones:
C# controlling a transaction across multiple databases
How do you get around multiple database connections inside a TransactionScope if MSDTC is disabled?
Before you walk off into the land of distributed transactions though, there may be a simpler solution for this case. Transaction scopes can be nested, and parent scopes will rollback if any of their nested scopes fail. Each scope only has to worry about one connection or just nested scopes, so we may not run into the MSDTC issue.
Give this a try:
var scopeOptions = new TransactionOptions();
scopeOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
scopeOptions.Timeout = TimeSpan.MaxValue;
using (var ts = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
using (var scope1 = new TransactionScope(TransactionScopeOption.Required))
{
// if you can wrap a using statment around the db context here that would be good
db.Product.Add(product);
db.SaveChanges();
scope1.Complete();
}
using (var scope2 = new TransactionScope(TransactionScopeOption.Required))
{
// omitted the other "using" statments for the connection/command part for brevity
var sqlConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Monet"].ConnectionString);
sqlConn.Open();
SqlCommand sqlCommand = sqlConn.CreateCommand();
sqlCommand.CommandText = InsertMonetProduct(product);
sqlCommand.ExecuteNonQuery(); // if this fails, the parent scope will roll everything back
scope2.Complete();
}
ts.Complete();
}

Related

DbMigrator does not commit changes to database

I'm trying to programmatically initialize a SQL Server database using DbMigrator class from EntityFramework:
var configuration = new MyDataBaseAssembly.Migrations.Configuration();
configuration.TargetDatabase = new System.Data.Entity.Infrastructure.DbConnectionInfo(myConnectionString, "System.Data.SqlClient");
var dbMigrator = new DbMigrator(configuration);
dbMigrator.Update();
using (var context = new MyDataBaseAssembly.MyDataBaseContext(myConnectionString))
{
// do some queries
context.MyTable1.ToList();
}
The weird thing is that the code works fine for all my database assemblies except one. For some reason one of the DB's is not getting initialized, and I'm receiving an exception:
System.Data.SqlClient.SqlException: Invalid object name 'dbo.MyTable1'.
When I open the SQL Server Profiler, I can see that all the migrations are being applied with no errors. However, when I open the database in SQL Server Management Studio, I can see that it contains no tables at all.
After further investigating, I found out that after dbMigrator.Update is executed, the transaction it uses is not committed to the DB (DBCC OPENTRAN outputs that the transaction is still open). And for some reason, the transaction data gets discarded before I try to do some queries using MyDataBaseContext.
What can be the cause of such behavior? Why does dbMigrator.Update commit all the necessary migrations for some databases, but not for others? Can I control dbMigrator transactions manually and force them to commit?
I ran into the same problem. I ended up working around it with
var scriptor = new MigratorScriptingDecorator(dbMigrator);
var script = scriptor.ScriptUpdate(sourceMigration: null, targetMigration: null);
using (var conn = new SqlConnection(connString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(script, conn))
{
cmd.ExecuteNonQuery();
}
}

Query not saving to database

I'm trying to save a note into the database by user input but my code isn't saving it to the Database.
I'm planning on checking the user input aswell with functions.
SqlConnection con = database.GetConnection();
SqlCommand command = new SqlCommand("INSERT INTO notities (notities_gebruiker, notities_datum, notities_doeldatum, notities_bericht) values(#notities_gebruiker, #notities_datum, #notities_doeldatum, #notities_bericht)", con);
command.Parameters.AddWithValue("#notities_gebruiker", this.gebruikerid.ToString());
command.Parameters.AddWithValue("#notities_datum", DateTime.Now);
command.Parameters.AddWithValue("#notities_doeldatum", DateTime.ParseExact(this.targetDate.Text, "dd/MM/yyyy", null));
command.Parameters.AddWithValue("#notities_bericht", this.Note.Text);
con.Open();
command.ExecuteNonQuery();
con.Close();
initialiseListBox();
What's happening is that whenever I close my application the database loses it's values saved and goes back to original state. I can add items manually though..
EDIT: It rolls back the rows I have added in via application but I am able to pull the data from the database I saved if I keep it running.
You're forgetting to commit the data you've just inserted. Use the SqlTransaction class to begin a transaction:
SqlTransaction myTransaction = con.BeginTransaction();
command.ExecuteNonQuery();
myTransaction.Commit();
It would be a good idea to make use of the Using statement to make sure your connection, command and transaction are disposed of once the code block has been complete, rather than manually calling Close() methods:
Using (SqlConnection con = database.GetConnection())
{
con.Open();
Using (SqlCommand command = new SqlCommand("sqlhere"))
{
Using (SqlTransaction myTransaction = con.BeginTransaction())
{
//your code here
}
}
}
SqlTransaction class:
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction(v=vs.110).aspx
Neat article on using statements:
http://www.codeproject.com/Articles/6564/Understanding-the-using-statement-in-C
Look for a copy of your database containing the data in your bin/debug folder.
Best way to avoid this is to use a full path in your connection string and avoid |DataDirectory| while debugging

C# Alternative of Multiple Active Result Set for MS-Access

I want to know if a multiple active result set, MARS, exists for the Microsoft's Access database? I am aware this exists for SQL Server. I tried using it with Access but it didn't work for me. I want to know how to use MARS with Access.
In short, Microsoft Access does not support multiple active result sets (MARS). It is not supported by the ODBC provider and the reason why that is not the case should be obvious if you think about it in terms of what MARS actually offers you from a performance stand point.
If you think about the most important reason for MARS to exist is if you have stored procedures executed on a SQL server that produce multiple result sets. If you have such queries you need to be able to somehow access those multiple results sets.
But in Access there is no such thing as stored procedures. If you have multiple queries you can just execute each one of them separately and get the result set for each. Hence, no need for MARS.
NOTE
In light of the comments, here's an example of how to have two data readers open at the same time:
using(var connection1 = new OdbcConnection("your connection string here"))
{
connection1.Open();
using(var connection2 = new OdbcConnection("your connection string here"))
{
connection2.Open();
using(var cmd1 = connection1.CreateCommand())
{
cmd1.CommandText = "YOU FIRST QUERY HERE";
using(var dataReader1 = cmd1.ExecuteReader())
{
while(dataReader1.Read())
{
// keep reading data from dataReader1 / connection 1
// .. at some point you may need to execute a second query
using(var cmd2 = connection2.CreateCommand())
{
cmd2.CommandText = "YOUR SECOND QUERY HERE";
// you can now execute the second query here
using(var dataReader2 = cmd2.ExecuteReader())
{
while(dataReader2.Read())
{
}
}
}
}
}
}
connection2.Close();
}
connection1.Close();
}

TransactionScope works in some places and not in others

Using ASP.NET 3.5, Linq to SQL, SQL Server 2005 on Windows Server 2003. Running VS 2008 on XP SP3 locally.
We need to be able to wrap inserts, updates, and deletes in a transaction. When we first tried this by wrapping code blocks with using(var trans = new TransactionScope()) { ...; trans.Complete(); }, we got an appropriate exception telling us we needed to enable network access for remote transactions. We did so and things began to work the way we expected.
Fast-forward to today. There is a little-used part of our app that also received a TransactionScope treatment. Though transactions work properly in all other parts of our codebase, we discovered today that this seldom used piece is throwing the same “Network Access” exception as before:
Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool. http://img101.imageshack.us/img101/5480/msdtcnetworkaccesserror.jpg
Here's the code that causes the exception:
using (TransactionScope trans = new TransactionScope(TransactionScopeOption.Required, TimeSpan.MaxValue))
{
using (var dc = new ChargeXferDataContext())
{
//create 'Line' object and set initial values
Line line = new Line();
line.Unit_Num = UnitId;
line.SubmittedBy = Viewer.Alias();
line.LineSubmittedOn = DateTime.Now;
//get codes to move from checked rows
//iterate rows in current gridview
foreach (GridViewRow row in gv.Rows)
{
//if checked, insert move order
HtmlInputCheckBox cb = (HtmlInputCheckBox)row.FindControl("RowLevelCheckBox");
if (cb.Checked)
{
//1st: get required values
int id = Convert.ToInt32(((TextBox)row.FindControl("fldCodeId")).Text);
int newId = Convert.ToInt32(((DropDownList)row.FindControl("ddlNewId")).SelectedValue);
char newPOA = Convert.ToChar(((DropDownList)row.FindControl("ddlPOA")).SelectedValue);
//2nd: get current diag code from old patient
//######## Exception happens here...
DiagCode code = dc.DiagCodes.SingleOrDefault(c => c.Id == id);
//########
//3rd: add code to emenline object
addCode(line, code, newId, newPOA);
}
}
dc.SubmitChanges();
trans.Complete();
}
}
If you've got any suggestions, they would be appreciated. Let me know if I can explain something more. Thanks in advance!!

ADO.Net DataReader error: Already an Open DataReader

I am using C# + .Net 3.5 + VSTS 2008 + ADO.Net + SQL Server 2008. And I am sharing one single SQL Connection object (TestDBConnection variable in my below sample) within my application.
The exception I met with is, "There is already an open DataReader associated with this Command which must be closed first.." Any ideas what is wrong?
The patterns within my application which I am using are all like this, i.e. sharing the single db connection object TestDBConnection, and using the single TestDBConnection variable to create command on it and execute store procedure.
using (SqlCommand testCommand = new SqlCommand())
{
testCommand.Connection = TestDBConnection;
testCommand.CommandType = CommandType.StoredProcedure;
testCommand.CommandText = "prc_AddOrderStatus";
testCommand.Parameters.Add("#orderID", SqlDbType.NVarChar).Value = orderID;
testCommand.ExecuteNonQuery();
}
thanks in advance,
George
Don't share the connection, use connection pooling instead. If you are doing two things at the same time on the connection, you might want to look into MARS.
For a test add this to your connection string: ;MultipleActiveResultSets=True; and see if this "fixes" the error. A lot of people believe you should avoid using MARS, so this is something to consider.
using (sqlConnection theconnection = new sqlconnection(initialise it))
{
using (SqlCommand testCommand = new SqlCommand())
{
testCommand.Connection = theConnection
testCommand.CommandType = CommandType.StoredProcedure;
testCommand.CommandText = "prc_AddOrderStatus";
testCommand.Parameters.Add("#orderID", SqlDbType.NVarChar).Value = orderID;
testCommand.ExecuteNonQuery();
}
}
is the pattern that i use in multi threaded cases with no problems at all.
Incidently this is connection pooling.
George, is it possible that the exception is telling you the truth? Are there any other commands that you've started but not yet finished?

Categories