I have a program, which is related to database. I need to unit-test some methods from class, which represents repository. I've decided to do it without localdb, but using Rollback attribute implementation:
public class Rollback : Attribute, ITestAction
{
private TransactionScope transaction;
public void BeforeTest(ITest test)
{
transaction = new TransactionScope();
}
public void AfterTest(ITest test)
{
transaction.Dispose();
}
public ActionTargets Targets => ActionTargets.Test;
}
I've got that from the Internet. This attribute implicitly begins transaction before method's code, and rolls it back after. That really works nice.
But at one moment I've wanted to debug this test:
[Test, Rollback]
public async Task AddingExistingDictionaryTypeThrowsExceptionTest()
{
await _repository.AddDictionaryType(tempDictionaryTypeName, tempDictionaryTypeDescription);
Assert.ThrowsAsync<Exception>(async () => { await _repository.AddDictionaryType(tempDictionaryTypeName, tempDictionaryTypeDescription); });
}
AddDictionaryType - is a method from repository. It adds new entity to database after checking, if such record doesn't exist already:
public async Task AddDictionaryType(string name, string description)
{
try
{
var sameNameCollection = _dbContext.DictionaryTypes.FromSqlRaw(#$"select * from dictionary_type where name = '{name}'");
var sameDescriptionCollection = _dbContext.DictionaryTypes.FromSqlRaw(#$"select * from dictionary_type where description = '{description}'");
if (sameNameCollection.Any() || sameDescriptionCollection.Any())
{
throw new AddingExistingDictionaryException();
}
_dbContext.Add(new DictionaryType(name, description));
await _dbContext.SaveChangesAsync();
}
catch (Exception ex)
{
throw ex;
}
}
Don't pay attention to two SELECTS, I know, that I may make one check, but that's for my needs.
So, I've set the point after first call of AddDictionaryType method to check new record in database, after that I've done SELECT in SSMS(I know, that was silly, too, because method worked in transaction) from same table, in which I've tried to insert record. So, I've got an error. And now I will tell you about most interesting:
After that I can't normally execute the test, I always get error: "This connection was used with the ambient transaction. The original ambient transaction needs to be completed before this connection can be used outside of it".
So I think, that there is that transaction, which hasn't been closed.
The problem is that I cannot find it and kill.
I've operated with such queries to find it:
SELECT * FROM sys.sysprocesses;
SELECT * from sys.dm_tran_current_transaction with(nolock);
SELECT * from sys.dm_tran_active_transactions;
EXEC SP_who2;
SELECT * FROM sys. dm_exec_sessions;
SELECT * FROM fn_dblog(NULL, NULL);
I've seen processes, tried to kill them, didn't help.
I've reloaded server, didn't help.
There is no any information about such transaction in transaction log.
No I see, that testing like this is a big problem, because I don't even know, how that transaction could be named to rollback it by my hands.
May be it's not because of transaction? But I don't have any ideas. What about you?
I've refused using Rollback attribute, but follow
Charlieface's advice. Now I use using statement in each test, and everything works nice:
[Test]
public async Task Test()
{
using (var tran = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
// test logic
}
}
Also, I've understodd, that there is know any stuck transaction in database.
Related
Issue:
Basically, update statement awaits endlessly ( ExecuteNonQueryAsync() ) for some reason.
Insert works just fine, I tried to hardcode a simple update statement with hardcoded parameters(without merge), same story, even tried to remove where clause and update all rows, again, same problem.
After setting commandTimeout to 1s I managed to identiy TimeoutException, why it happens? I have no idea.
Edit: Plot twist.
Now I have randomly successful updates, which dazes me even more.
I tried basically every possible scenario I could think of to identify any patterns residing in the issue, my only conclusion is that updates success rate is totally random...
Still inserts are working just fine.
This is the simplified code portion.
public class CipCheckRepository : ICipCheckRepository
{
private string connectionString = "Data Source=****;User Id=****;PASSWORD=****;Persist Security Info=True;";
public CipCheckRepository()
{}
public async Task InsertOrUpdate(CipCheck cipCheck)
{
using (var uow = new UnitOfWork(connectionString))
{
using (var cmd = uow.CreateCommand())
{
cmd.CommandText = #"
MERGE INTO test
USING dual
ON (id = :CAD_ID)
WHEN MATCHED THEN
UPDATE SET value = :ISRA_NUMBER
WHEN NOT MATCHED THEN
INSERT
(
id,
value
)
VALUES
(
:CAD_ID,
:ISRA_NUMBER
)";
cmd.Parameters.AddWithValue("CAD_ID", 1);
cmd.Parameters.AddWithValue("ISRA_NUMBER", cipCheck.IsraNumber);
cmd.CommandTimeout = 1;
try
{
await cmd.ExecuteNonQueryAsync();
}
catch (OracleException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
}
UnitOfWork class is created for abstraction matters.
How can I identify the cause of the never-ending query and what steps can be taken to prevent it?
It seems that I've been locking the table somehow, after committing all statements via Oracle Developer the quirks are gone.
Considering this piece of code:
using(TransactionScope tran = new TransactionScope()) {
insertStatementMethod1();
insertStatementMethod2();
// this might fail
try {
insertStatementMethod3();
} catch (Exception e) {
// nothing to do
}
tran.Complete();
}
Is anything done in insertStatementMethod1 and insertStatementMethod2 going to be rolled back? In any case?
If I want them to execute anyway, I would need to check if it insertStatementMethod3 will fail before the transaction, and build my transaction code based on that?
Update
The code looks similar to this
using(TransactionScope tran = new TransactionScope()) {
// <standard code>
yourExtraCode();
// <standard code>
tran.Complete();
}
where I get to write the yourExtraCode() method
public void yourExtraCode() {
insertStatementMethod1();
insertStatementMethod2();
// this call might fail
insertStatementMethod3();
}
I can only edit the yourExtraCode() method, so I cannot chose to be in the transaction scope or no. One simple possible solution would be this:
public void yourExtraCode() {
insertStatementMethod1();
insertStatementMethod2();
// this call might fail
if (findOutIfIcanInsert()) { // <-- this would come by executing sql query
try {
insertStatementMethod3();
} catch (Exception e) {
// nothing to do
}
}
}
But that would come with the need of looking up things in the db which would affect performance.
Is there a better way, or I need to find out before I'd call the method?
I tried out and, of course the transaction was rolled back as expected.
If you don't want your first two methods to be transacted, just move them out from the ambient transaction's scope.
If you don't have control over the code which starts an ambient transaction, you can suppress it by creating a new ambient transaction: using (var scope = new TransactionScope(TransactionScopeOption.Suppress)).
As learning exercise and before trying to use any ORM (like EF) I want to build a personal project using ADO.NET and stored procedures.
Because I don't want my code to become a mess over time, I want to use some patterns like the repository and UoW patterns.
I've got almost everything figured it out, except for the transaction handling.
To somehow 'simulate' a UoW, I used this class provided by #jgauffin, but what's stopping me from using that class is that every time you create a new instance of that class (AdoNetUnitOfWork) you're automatically beginning a transaction and there a lot of cases where you only need to read data.
In this regard this is what I found in one of the SQL books I've been reading:
Executing a SELECT statement within a transaction can create locks on the referenced tables, which can in turn block other users or sessions from performing work or reading data
This is the AdoNetUnitOfWork class:
public class AdoNetUnitOfWork : IUnitOfWork
{
public AdoNetUnitOfWork(IDbConnection connection, bool ownsConnection)
{
_connection = connection;
_ownsConnection=ownsConnection;
_transaction = connection.BeginTransaction();
}
public IDbCommand CreateCommand()
{
var command = _connection.CreateCommand();
command.Transaction = _transaction;
return command;
}
public void SaveChanges()
{
if (_transaction == null)
throw new InvalidOperationException("Transaction have already been commited. Check your transaction handling.");
_transaction.Commit();
_transaction = null;
}
public void Dispose()
{
if (_transaction != null)
{
_transaction.Rollback();
_transaction = null;
}
if (_connection != null && _ownsConnection)
{
_connection.Close();
_connection = null;
}
}
}
And this is how I want to use the UoW in my repositories:
public DomainTable Get(int id)
{
DomainTable table;
using (var commandTable = _unitOfWork.CreateCommand())
{
commandTable.CommandType = CommandType.StoredProcedure;
//This stored procedure contains just a simple SELECT statement
commandTable.CommandText = "up_DomainTable_GetById";
commandTable.Parameters.Add(commandTable.CreateParameter("#pId", id));
table = ToList(commandTable).FirstOrDefault();
}
return table;
}
I know I can tweak this code a bit so that the transaction would be optional, but since I trying to make this code as platform independent as possible and as far as I know in other persistence frameworks like EF you don't have to manage transactions manually, the question is, will I be creating some kind of bottleneck by using this class as it is, that is, with transactions always being created?
It all depends on the transaction isolation level. Using the default isolation level (ie. read committed) then your SELECT should occur no performance penalty if is wrapped in a transaction. SQL Server internally wraps statements in a transaction anyway if one is not already started, so your code should behave almost identical.
However, I must ask you why not use the built-in .Net TransactionScope? This way your code will interact much better with other libraries and frameworks, since TransactionScope is universally used. If you do decide to switch to this I must warn you that, by default, TransactionScope uses SERIALIZABLE isolation level and this does result in performance penalties, see using new TransactionScope() Considered Harmful.
My problem is that the transaction is not working properly it should not save the data for one table if an exception occurs during the trascation
When all the table is correct then only save data.
Consider the following:
databaseEntites objEntites = null;
using (objEntites = new databaseEntites())
{
objEntites.Connection.Open();
using (System.Data.Common.DbTransaction transaction =
objEntites.Connection.BeginTransaction())
{
try
{
customer objcust=new customer();
objcust.id=id;
objcust.name="test1";
objcust.email="test#gmail.com";
objEntites.customer.AddObject(objcust);
order objorder=new order();
objorder.custid=objcust.id;
objorder.amount=500;
objEntites.order.AddObject(objorder);
objEntites.SaveChanges();
transaction.Commit();
}
catch()
{
transaction.Rollback();
}
}
}
In this my second table column name is not correct and on SaveChanges() giving the exception.
When i see the database and found that it saving the data for customer table which is wrong i want data will go in the customer table when all table is correct and this savechanges either save for all table or not save for any.
For this i have also try the TransactionScope
using (TransactionScope tscope =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
......all the code here....
objEntites.SaveChanges(false);
tscope.Complete();
objEntites.AcceptAllChanges();
}
But its giving the same issue as described above.
Thanks in advance.
You can use database transaction or EF TransactionScope. For using database transaction it is enough to do as below:
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
//Some stuff
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
And for using second way that EF TransactionScope just use easily as below:
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
try
{
//Some stuff
scope.Complete();
}
catch (Exception)
{
//Don't need call any rollback like method
}
}
The point is no Rollback() method exist in the TransactionScope (against the normal ADO.NET Transaction) and Unless you call the Complete() method, the transaction do not complete and all the changes are rolled back automatically. You can see MSDN for better understand: http://msdn.microsoft.com/en-us/data/dn456843.aspx
Hope this help
If you have already a try ... catch block, you don't need using - just add finally. The following example should have everything you need:
databaseEntites objEntites = null;
var System.Data.Common.DbTransaction transaction = null;
try
{
objEntites = new databaseEntites();
objEntites.Connection.Open();
transaction = objEntites.Connection.BeginTransaction();
customer objcust=new customer();
objcust.id=id;
objcust.name="test1";
objcust.email="test#gmail.com";
objEntites.customer.AddObject(objcust);
order objorder=new order();
objorder.custid=objcust.id;
objorder.amount=500;
objEntites.order.AddObject(objorder);
objEntites.SaveChanges();
transaction.Commit();
}
catch()
{
if (transaction != null) transaction.Rollback();
}
finally
{
if (objEntites != null && objEntites.Connection.State != System.Data.ConnectionState.Closed
&& objEntites.Connection.State != System.Data.ConnectionState.Broken)
objEntites.Connection.Close();
}
Hints:
The finally block is executed even after an exception has occured, hence in case of an exception the exception handling code is executed first, then the code in the finally block. Only if severe exceptions (system errors) - such as a StackOverflowException - occur, it is not executed but you can't handle such kinds of exceptions anyway easily. For more information about this topic please look here.
For SaveChanges you can also use the option System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave, which I prefer because it guarantees that every entity object has its changes accepted after successful save.
BeginTransaction allows also to specify the kind of transaction, but I would not use the parameter options because if you omit it then it creates a transaction as specified by the database's default - so the administrator is still able to change this easily if required. But if you need to specify it, ensure that you don't hardcode it, but allow to configure it in the App.Config or Web.Config file.
Please let me know if that works for you.
I started working on this "already started" project, and I'm having a really annoying error when trying to execute some interactions with SQL Server 2008:
The server failed to resume the
transaction. Desc.:
One of these errors I get in this specific method call:
The aspx.cs Call:
busProcesso openProcess = new busProcesso(pProcessoId);
try
{
if (openProcess.GetDocument() == null)
{
//Irrelevant code.
}
}
catch{ //... }
The Business class (relevant part):
public class busProcesso : IbusProcesso
{
public Processo vProcesso { get; set; }
RENDBDataContext db;
public busProcesso()
{
vProcesso = new Processo();
}
public busProcesso(decimal pProcessoId)
{
db = new RENDBDataContext();
try
{
vProcesso = db.Processos.SingleOrDefault(x => x.Id == pProcessoId);
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public string GetDocument()
{
try
{
string document = null;
foreach (Processo_has_Servico ps in ListaServicosProcesso())
{
if (ps.Servico.Document != null) //Get the error right at this line.
{
document = ps.Servico.Document;
}
}
return document ;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public IQueryable<Processo_has_Servico> ListaServicosProcesso()
{
db = new RENDBDataContext();
try
{
return from ps in db.Processo_has_Servicos
join s in db.Servicos on ps.Servico_Id equals s.Id
where ps.Processo_Id == vProcesso.Id
select ps;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
}
As I said, the error occurs right at the line:
if (ps.Servico.Document != null) from the GetDocument() method.
Opening SQL Server Activity Monitor, I see there is a process for my database (.Net SqlClient Data Provider)
After some time/use (when I start to get the "server failed to resume the transaction" error), I go to the SQL Server Activity Monitor and there's around 5 or 6 more identical processes that weren't killed and (probably) should've been. When I manually kill them, the error stops for a while, until it starts again.
I'm not really good at working in OO and all, so I'm probably missing something, maybe some way to close one of these connections. Also, any help/tip about this structure will be welcome.
PS. The error doesn't happen everytime. Sometimes it runs just perfectly. Then it starts to give the error. Then it stops. Sometimes it happens just once.. pretty weird.
The code in ListaServicosProcesso is creating the context db. Then it is returning an IQueryable.
At this point no request has been sent to the database.
Then there is a for each in the code. At this point EF says "I need to get the data from the database". So it tries to get the data.
But the context db is now out of scope, so it crashes, on the first line that tries to use the data.
There are 2 ways to get around this:
return a list from ListaServicosProcesso, this will force the database call to execute
move the for each into ListaServicosProcesso
Edit
Pharabus is correct db is not out of scope. The problem is here:
db = new RENDBDataContext();
A new instance of the context is being created without the old one being disposed. Try Dispose of db at the end of ListaServicosProcesso. Even better place db in a using statement. But then the foreach must be moved inside the using statement.
Here's a couple of ideas to try.
1/ You can attach SQL server profiler to see the query that is being executed, which will allow you to copy and paste that query to see the data that is in the database. This might be help.
2/ You never check whether ps.Servico is null - you jump straight to ps.Servico.Document. If ps.Servico is null then you will get a null reference exception if you try to access any properties on that object.
I'm not sure of the exact cause of the error you're seeing (if you Google it, the references are all over the place...), but there are a few things you could improve in your code and I've found that just cleaning things up a bit often makes problems go away. Not always, but often.
I agree with the other answerers that it would help to keep better track of your DataContext(s). For example in you're creating it once in the constructor, then again in ListaServicosProcesso(). At that point vProcesso is on one DataContext and other entities will be on another, which gets messy.
I think you could simplify the whole thing a bit, for example you could combine GetDocument() and ListaServicosProcesso() like this:
public string GetDocument()
{
try
{
// Are you sure vProcesso is not null?
if (vProcesso == null)
return null;
// Only create the context if it wasn't already created,
if (db == null)
db = new RENDBDataContext();
return db.Processo_has_Servicos
.Where(ps => ps.Processo_Id == vProcesso.Id && ps.Servico.Document != null)
.Select(ps => ps.Servico.Document) // use an implicit join
.SingleOrDefault();
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}