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.
Related
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
Note: Relevant question over here has no solution
Keep in mind that I am no expert on Oracle or programming against Oracle. This is my test environment. I have a single table in the schema STVM called STVM_NOTIFICATION. This is what it looks like:
CREATE TABLE STVM_NOTIFICATION
(
"ID" NUMBER NOT NULL,
"PROPERTYNAME" VARCHAR2(16 BYTE) NOT NULL,
"PROPERTYVALUE" VARCHAR2(16 BYTE) NOT NULL,
"ACTION" VARCHAR2(32 BYTE) NOT NULL,
"POSTDATE" TIMESTAMP (6) NOT NULL,
"SENT" CHAR(1 BYTE) NOT NULL,
ADD CONSTRAINT "PK_ID" PRIMARY KEY ("ID")
)
I have created the following sequence and trigger to create a unique identity for each row:
CREATE SEQUENCE STVM_NOTIF_SEQ
START WITH 1
INCREMENT BY 1
CACHE 100;
CREATE OR REPLACE TRIGGER STVM_NOTIF_ID_TRG BEFORE INSERT ON STVM_NOTIFICATION
FOR EACH ROW
BEGIN
:NEW.ID := STVM_NOTIF_SEQ.NEXTVAL;
END;
I then set the following grants for STVM:
GRANT CREATE SESSION TO STVM;
GRANT CREATE TABLE TO STVM;
GRANT CREATE VIEW TO STVM;
GRANT CREATE ANY TRIGGER TO STVM;
GRANT CREATE ANY PROCEDURE TO STVM;
GRANT CREATE SEQUENCE TO STVM;
GRANT CREATE SYNONYM TO STVM;
GRANT CHANGE NOTIFICATION TO STVM;
Inserting into the table works just fine. Below is the simple test app provided by the Oracle documentation on OracleDependency (with tiny modifications) that I'm using to test notifications:
namespace SqlDependencyTest
{
class Program
{
private static string oraConnectionString = #"Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.0.164)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=STVM;Password=STVM;";
private static string oraQuery = "SELECT ID FROM STVM_NOTIFICATION";
private static OracleDependency oraDependency;
static void Main(string[] args)
{
using (OracleConnection oraConnection = new OracleConnection(oraConnectionString))
{
try
{
// Open the connection
oraConnection.Open();
// Create the Select command retrieving all data from the STVM_NOTIFICATION table.
OracleCommand selectCommand = new OracleCommand(oraQuery, oraConnection);
// Create an OracleDependency object and set it to track the result set returned by selectCommand.
oraDependency = new OracleDependency(selectCommand);
// Setting object-based change notification registration
oraDependency.QueryBasedNotification = false;
// When the IsNotifiedOnce property is true, only the first change
// of the traced result set will generate a notification.
// Otherwise, notifications will be sent on each change
// during the selectCommand.Notification.Timeout period.
selectCommand.Notification.IsNotifiedOnce = true;
// Set the event handler to the OnChange event.
oraDependency.OnChange += new OnChangeEventHandler(OnChange);
// When the select command is executed at the first time, a notification
// on changes of the corresponding result set is registered on the server.
//selectCommand.CommandTimeout = 5;
OracleDataReader reader = selectCommand.ExecuteReader(CommandBehavior.Default);
// Set and execute an insert command. The Dept table data will be changed,
// and a notification will be sent, causing the OnChange event of the 'dependency' object.
OracleCommand insertCommand = new OracleCommand
("INSERT INTO STVM_NOTIFICATION (PROPERTYNAME, PROPERTYVALUE, ACTION, POSTDATE, SENT) VALUES ('Heartbeat', 'NOK', 'REFRESH', SYSDATE, 'N')", oraConnection);
insertCommand.ExecuteNonQuery();
// Pause the current thread to process the event.
Console.Read();
}
catch (Exception e)
{
Console.WriteLine("Exception encountered: {0}", e.Message);
}
// Always try to both remove the notification registration
// oraConnection.Close() is autimatically called by .Dispose at the end of our 'using' statement
finally
{
try
{
oraDependency.RemoveRegistration(oraConnection);
}
catch (Exception e)
{
Console.WriteLine("Exception encountered: {0}", e.Message);
}
}
}
}
// A simple event handler to handle the OnChange event.
// Prints the change notification details.
private static void OnChange(Object sender, OracleNotificationEventArgs args)
{
DataTable dt = args.Details;
Console.WriteLine("The following database objects were changed:");
foreach (string resource in args.ResourceNames)
{
Console.WriteLine(resource);
}
Console.WriteLine("\n Details:");
Console.Write(new string('*', 80));
for (int rows = 0; rows < dt.Rows.Count; rows++)
{
Console.WriteLine("Resource name: " + dt.Rows[rows].ItemArray[0]);
string type = Enum.GetName(typeof(OracleNotificationInfo), dt.Rows[rows].ItemArray[1]);
Console.WriteLine("Change type: " + type);
Console.Write(new string('*', 80));
}
}
}
}
This actually works! However: only until another process performs an insert on the same table, at least, that is my observation. I performed the insert multiple times from SQL Developer, the issue is recreatable and I have done so for over 10 times.
As soon as another process performs said insert, my application hangs on the following statement:
OracleDataReader reader = selectCommand.ExecuteReader(CommandBehavior.Default);
I can clearly see the notification callback being registered in DBA_CHANGE_NOTIFICATION_REGS:
net8://(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.0.226)(PORT=64268))?PR=0
The connection remains open on the Oracle server for 30 minutes until it times out:
SELECT USERNAME, PROGRAM, BLOCKING_SESSION_STATUS, BLOCKING_INSTANCE, BLOCKING_SESSION, EVENT FROM V$SESSION
WHERE USERNAME = 'STVM'
USERNAME PROGRAM BLOCKING_SESSION_STATUS BLOCKING_INSTANCE BLOCKING_SESSION EVENT
STVM SQL Developer NO HOLDER (null) (null) SQL*Net message from client
STVM OracleDependencyTest.vshost.exe VALID 1 50 enq: TM - contention
Whenever this happens, the only solution is to kill the sessions, to REVOKE CHANGE NOTIFICATION and to GRANT it again. After this, my application will work again, until another process performs an insert.
The only clue I have is the event enq: TM - contention, but most people refer to this as an indicator of non-indexed foreign keys in the table. Considering that I currently only have a single table for testing purposes, there aren't a whole lot of foreign keys to go around here.
Does anyone have any ideas as to the relevance of enq: TM - contention?
I am using:
(Server side)
Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
"CORE 11.2.0.2.0 Production"
TNS for Linux: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production
Oracle.DataAccess
(Client Side)
Oracle Call Interface (OCI) 11.2.0.1.0
Oracle Client 11.2.0.1.0
Oracle Data Provider for .NET 11.2.0.1.0
Oracle JDBC/OCI Instant Client 11.2.0.1.0
Oracle JDBC/THIN Interfaces 11.2.0.1.0
Oracle SQL Developer 11.2.0.1.0
SQL*Plus 11.2.0.1.0
SQL*Plus Files for Instant Client 11.2.0.1.0
UPDATE: After days of trying to find out what the problem is and not finding the issue, I've decided to move on and look for a different technique. The great suggestion by Christian did not yield any results, and resolving the in-doubt transaction like Justin suggested didn't get me any further either unfortunately. I know a couple of Oracle DBA's over at work who were initially willing to help, but they quickly shoo'd me away as soon as I mentioned .NET.
There's a bug in the version of the database that you are using that may be related. You can check to see if this is the case by executing the following as SYSDBA:
alter system set events '10867 trace name context forever, level 1';
This will last until shutdown. If the problem goes away you are hitting the bug.
You should not leave this turned on, rather you should upgrade the database and and also patch ODP.NET.
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);
When using this code on a SqLite file database, it works fine.
using (var ctx = new Test2010Entities())
{
string s = "CREATE TABLE 'Company' ([Id] integer PRIMARY KEY AUTOINCREMENT NOT NULL, varchar(50) NOT NULL);";
ctx.ExecuteStoreCommand(s);
ctx.Companies.AddObject(new Company { Code = "_1" });
ctx.Companies.AddObject(new Company { Code = "_2" });
ctx.SaveChanges();
foreach (var c in ctx.Companies.ToList())
{
Console.WriteLine(c.Code);
}
}
But when runnning this code on a SqLite 'In Memory' database (Data Source=:memory:;Version=3;New=True;) , I get this exception:
Unhandled Exception: System.Data.UpdateException: An error occurred
while updating the entries. See the inner exception for details. --->
System.Data.SQLite.SQLiteException: SQL logic error or missing
database no such table: Company
Note this is tested with VS 2010, EF 4.4.0.0 and sqlite-netFx40-setup-bundle-x86-2010-1.0.84.0
::: UPDATE :::
As Simon Svensson suggested, opening the connection before any other commands does do the trick:
ctx.Connection.Open();
This happens when your ORM closes your connection, and reopens it. That will reset the sqlite in-memory database to its default state; i.e. empty.
The same thing happens with NHibernate unless you set connection.release_mode = close (the default is after_transaction.
I'm not familiar with Entity Framework, but I expect a similar setting or using the DataContext(IDbConnection) constructor which is documented as "If you provide an open connection, the DataContext will not close it."
The same documentation also states "In a System.Transactions transaction, a DataContext will not open or close a connection to avoid promotion." which may be a cleaner solution.
Using some Reflector magic shows that it's SQLiteConnection.Open that calls (via SQLite3.Open) sqlite3_open_interop (if you're using the NuGet sqlite package). This shows that you get a new empty in-memory database everytime you call SQLiteConnection.Open.
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();