I am using Entity Framework and manipulating data in a sqlserver database via stored procedures (per client request).
Data is pulled from the database via stored procedures and the results of these stored procedures populates a SQLite db in the Winforms Application.
SQLite is used for additional querying and changing of data and then pushed back via update stored procedure to the sql server db when the user syncs
all stored procedures are on sql server (no in text / in line sql in the application)
I am faced with the scenario where multiple users can potentially attempt to update the same field, which poses 2 problems for me.
If they call the same stored procedure at the same time (select or update).
I am not sure what my options are here from a programming level, I don't have rights to make server changes.
if the field they are trying to update has already been updated.
for problem 2 I am trying to build in a check by date stamping the modification. ie. when a user syncs sql server adds that sync date to a date modified column, if a another user tries to modify the same field i want to check the date modified on his sqlite db and compare that to date modified in sql server, if sql server's date modified is more recent, keep sql server values, if syncing user's modified date is more recent use his...
I have looked into Resolving optimistic concurrency with a condition where the client wins.
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update original values from the database
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}
} while (saveFailed);
}
but this seems to only work when you directly query the db with Entity Framework and not when you want to update via stored procedure.
what can I use to perform these types of checks?
Ok, This is probably not the best solution, but it is what I was able to come up with, and although not tested extensively initial once over seems to be ok-ish.
I am not going to mark this as the answer, but its what i got working based on my question above.
calling stored procedure at same time, created a class for the transactions
public class TransactionUtils
{
public static TransactionScope CreateTransactionScope()
{
var transactionOptions = new TransactionOptions();
transactionOptions.IsolationLevel = IsolationLevel.ReadCommitted;
transactionOptions.Timeout = TransactionManager.DefaultTimeout;
return new TransactionScope(TransactionScopeOption.Required, transactionOptions);
}
}
and then in code use it as follows:
var newTransactionScope = TransactionUtils.CreateTransactionScope();
try
{
using (newTransactionScope)
{
using (var dbContextTransaction = db_context.Database.BeginTransaction(/*System.Data.IsolationLevel.ReadCommitted*/))
{
try
{
db_context.Database.CommandTimeout = 3600;
db_context.Database.SqlQuery<UpdateData>("UpdateProc #Param1, #Param2, #Param3, #Param4, #Param5, #Param6, #DateModified",
new SqlParameter("Param1", test1),
new SqlParameter("Param2", test2),
new SqlParameter("Param3", test3),
new SqlParameter("Param4", test4),
new SqlParameter("Param6", test5),
new SqlParameter("DateModified", DateTime.Now)).ToList();
dbContextTransaction.Commit();
}
catch (TransactionAbortedException ex)
{
dbContextTransaction.Rollback();
throw;
}
As for issue 2 (concurrency)
I could not find a way to use built in concurrency checks between data on SQL Server and the data that I want to update from SQLite (2 different contexts)
So I am storing Date modified in both sql server and sqlite.
the sqlite date modified is updated when the user modifies a record,
date modified on sql server is updated when a sync runs.
Before syncing I query the sqlServer db for the record to be updated's date modified and compare it with the sqlite's date modified for that record in a if statement and then either run the update stored procedure for that record or not
Related
I'm having the following error executing this piece of code:
private bool _updateList(SysInfo _sysInfo, List<pList> _pList)
{
try
{
foreach (var p in _pList)
{
_context.spUpdatePListApprovalFlow(p.countryID, _sysInfo.User.JobRoleID, p.src, p.id, p.status, _sysInfo.User.Username);
}
return true;
}
catch (Exception ex) //debug only
{
throw; //throws error to the main try catch
}
}
ERROR
The operation could not be performed because OLE DB provider "MSDASQL"
for linked server "AS400_LINKEDSRV" was unable to begin a distributed
transaction.
However, everything works fine when I run the Stored Procedure in SQL Management Studio:
exec [dbo].[spUpdatePListApprovalFlow]
#CountryID = 123456,
#UserTypeID = 23456,
#Src = 1,
#Id = '123456789',
#Status = 30,
#Username = 'username'
I'm tired of digging for an answer nothing works... Few things I've tried:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
insert multiple transactions in the stored procedure
This sp has 2 sub stored procedures on it. One that writes into a table of the application's database, and another that updates a table in as400.
In EF6 stored procedures are called in an explicit transaction, by default. You can turn this off for a particular DbContext instance by changing its configuration after creating it, or for all instances of a DbContext type by changing it in the constructor. EG
using (var db = new Db())
{
db.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
//. . .
}
See: https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbcontextconfiguration.ensuretransactionsforfunctionsandcommands
Ok, after half a day trying to solve this I've finally solved it.
Resolution
Downgraded from Entity Framework 6 to Entity Framework 5 and the
distribuited transactions error has gone.
Just pay attention, if you're going to do this, you have to change some usings in your code. ( in auto generated code in the Data Model as well)
EF 6 uses
using System.Data.Entity.Core.Objects
EF 5 uses
using System.Data.Objects;
If you don't need distributed transactions you can try to disable them in the settings of the linked server:
EXEC master.dbo.sp_serveroption
#server=N'AS400_LINKEDSRV',
#optname=N'remote proc transaction promotion',
#optvalue=N'false'
Refer to this Microsoft page on Linked Servers.
Your System Administrator and/or DBA will probably need to make changes to address the missing linked server definition to your AS/400 server.
Another possible issue is that the AS/400 server (from IBM) lacks software support for the OLE DB data sources. This too would be something that the System Administration staff may need to address.
I'm kind of new with databases and SQL and I'm struggling trying to understand how SQL Change Tracking and Microsoft Sync Framework work together.
I couldn't find some clear examples about how to sync databases with Microsoft Sync Framework but hopefully I found this site, modified the code and got syncing working on my two databases, here is the code I got:
// Server connection
using (SqlConnection serverConn = new SqlConnection(serverConnectionString))
{
if (serverConn.State == ConnectionState.Closed)
serverConn.Open();
// Client connection
using (SqlConnection clientConn = new SqlConnection(clientConnectionString))
{
if (clientConn.State == ConnectionState.Closed)
clientConn.Open();
const string scopeName = "DifferentPKScope";
// Provision Server
var serverProvision = new SqlSyncScopeProvisioning(serverConn);
if (!serverProvision.ScopeExists(scopeName))
{
var serverScopeDesc = new DbSyncScopeDescription(scopeName);
var serverTableDesc = SqlSyncDescriptionBuilder.GetDescriptionForTable(table, serverConn);
// Add the table to the descriptor
serverScopeDesc.Tables.Add(serverTableDesc);
serverProvision.PopulateFromScopeDescription(serverScopeDesc);
serverProvision.Apply();
}
// Provision Client
var clientProvision = new SqlSyncScopeProvisioning(clientConn);
if (!clientProvision.ScopeExists(scopeName))
{
var clientScopeDesc = new DbSyncScopeDescription(scopeName);
var clientTableDesc = SqlSyncDescriptionBuilder.GetDescriptionForTable(table, clientConn);
// Add the table to the descriptor
clientScopeDesc.Tables.Add(clientTableDesc);
clientProvision.PopulateFromScopeDescription(clientScopeDesc);
clientProvision.SetCreateTrackingTableDefault(DbSyncCreationOption.CreateOrUseExisting);
clientProvision.Apply();
}
// Create the sync orchestrator
var syncOrchestrator = new SyncOrchestrator();
// Setup providers
var localProvider = new SqlSyncProvider(scopeName, clientConn);
var remoteProvider = new SqlSyncProvider(scopeName, serverConn);
syncOrchestrator.LocalProvider = localProvider;
syncOrchestrator.RemoteProvider = remoteProvider;
// Set the direction of sync session
syncOrchestrator.Direction = direction;
// Execute the synchronization process
return syncOrchestrator.Synchronize();
}
}
So on this way any changes are synchronized between my two databases. But I wanted a way for my C# app to automatically synchronize both databases when something changes so I found something called Change Tracking here. I downloaded the example code that provides a SynchronizationHelper that also creates tables in my databases called "{TableName}_tracking". This is another table that tracks the changes and indeed it does, whenever I change something in my database the _tracking is updated with the elements I changed, added or removed. Change Tracking doesn't automatically synchronize my databases, it just keeps track of the changes in them, what's the purpose of this?
With the first code, synchronization works but no _tracking table is created, does it just synchronize everything in the table no matter what changed? If that's the case, for big databases I should be using Change Tracking?
Maybe this is something trivial but I have been googling and testing a lot of code but I can't find a clear answer.
When you install Sync Framework, it comes with a help file that includes several walkthroughs of synchronizing databases. the first link you referred to and the second uses the same sync provider and they both have tracking tables. Sync Framework supports using the built-in SQL Change Tracking feature or using a custom-one that Sync Framework creates by itself (the _tracking).
Sync Framework sits outside of your database and you need to invoke it in order to fire the synchronization. Change Tracking is what it says it is- tracking changes.
if you want your databases to do the sync, you might want to check SQL Replication instead.
I have a C# win form application which has a facility to backup databases with data to a script file while the application running.
I used following code to script the database using SMO;
public StringCollection GenerateDatabaseScript(string databaseName)
{
//Validate database name goes here
StringCollection dbScript = new StringCollection();
//Create db connection
sqlDataAccess.DBConnect(databaseName); //Custom class to do SQL data operations (sqlDataAccess)
//Create server and database objects
var serverConn = new ServerConnection(sqlDataAccess.Connection);
var dbServer = new Server(serverConn);
var database = dbServer.Databases[databaseName];
//Set script database options here
//--
//Set script database tables option here
//--
//Script database creation
//I also use a method 'ScriptObjectWithBatchDelimiter' to add GO delimiter for each command manually.
dbScript.AddRange(ScriptObjectWithBatchDelimiter(database.Script(dbScriptingOptions)).ToArray());
//Set focus to new db
dbScript.Add(string.Format("USE [{0}]", databaseName));
dbScript.Add("GO");
foreach (Table table in database.Tables)
{
//Skip scripting system tables
if (table.IsSystemObject)
continue;
//Script table
dbScript.AddRange(ScriptObjectWithBatchDelimiter(table.EnumScript(tableScriptingOptions)).ToArray());
}
return dbScript;
}
Problem occurs in this line when encountering a table in the database data is not committed ROWLOCK;
table.EnumScript(tableScriptingOptions)
The problem is how can I script data with READUNCOMMITTED? Is there any properties that I can set to achieve this?
The same question is asked here, but the only answer provided not suitable.
UPDATE: Following code is tried (assumed with the Isolation part in the name) but, still not working.
database.SetSnapshotIsolation(true);
I think what you are looking for is this:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
msdn: "Specifies that statements can read rows that have been modified by other transactions but not yet committed."
so wrap your scripting work inside a transaction with Isolation Level "ReadUncommited".
this is a good example how to use transactions in c#:
https://msdn.microsoft.com/en-us/library/5ha4240h(v=vs.110).aspx
I'm calling a stored procedure in Entity Framework 6 that can create Databases and tables if necessary. It is throwing the error;
Message "CREATE DATABASE statement not allowed within multi-statement transaction.\r\nALTER DATABASE statement not allowed within multi-statement transaction.\r\nDatabase 'CoreSnapshotJS3' does not exist. Make sure that the name is entered correctly." string
I do not want it in a transaction, and have used this to supress the transaction
using (var transation = new TransactionScope(TransactionScopeOption.Suppress))
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("spCreateSnapshotFromQueue", snapshotQueueIDParameter);
}
It still throws an error.
How do I stop automatic transactions?
I found a way:
var snapshotQueueIDParameter = new SqlParameter("SnapshotQueueID", entityId);
return _db.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction,
"EXEC spCreateSnapshotFromQueue #SnapshotQueueID", snapshotQueueIDParameter);
I have a simple application that uses Linq-to-SQL to retrieve and update data.
On my DataContext I have a procedure called GET_DATA that reads data from a non-SQL Server data source using a linked server. I use the data read to update a few tables on SQL Server.
The process of updating the data on SQL Server is done (summarizing) like this:
var db = new MyDBDataContext()
db.ExecuteCommand("Update table1 set field={0} where field={1}", value1, value2);
It works perfectly fine.
Now, the problem: a new requirement came to update 2 tables instead of one so I decided to add some transaction control:
using (var db = new MyDBDataContext())
using (var tran = new TransactionScope())
{
try
{
db.ExecuteCommand("Update table1 set field={0} where field={1}", value1, value2);
db.ExecuteCommand("Update table3 set field={0} where field={1}", value1, value2);
tran.Complete();
}
catch (Exception e)
{
tran.Dispose();
}
}
Once again it works fine, but after I update the data (regardless if the process fails or succeeds), if I try to run the search procedure, I get this error:
The operation could not be performed because OLE DB provider \"OraOLEDB.Oracle\" for linked server \"LINKED_SERVER_NAME\" was unable to begin a distributed transaction."}
The only way I can make it work again is if I right click on the ASP.Net development Server and close it. It seems that it is keeping something open, but I checked the dm_tran_session_transactions DMV and I don't see any transactions open (I can see it if I debug into the method, but once the method is completed, no transaction is open)
FYI: the dbcontext objects are different form the select to the update and I also tried to add db.SubmitChanges() but it didn't work either.
Any advise would be helpful thanks
EDIT:
The search procedure does not have transaction control inside it and it is called by a simple LINQ Statment:
var result = (from r in context.GET_DATA(parameter)
select new Object{
.....
}
);
return result.ToList();