Will SaveChanges be Disposed if an Exception Occurs? - c#

Kindly consider the following code:
public class UnitOfWork
{
private readonly Context _context;
public IEmployeeRepository Employees { get; private set; }
public UnitOfWork(Context context)
{
_context = context;
Employees = new EmployeeRepository(_context);
}
public int SaveChanges()
{
return _context.SaveChanges();
}
}
public class Program
{
try
{
using (var unitOfWork = new UnitOfWork(new Context()))
{
var employee = unitOfWork.Employees.GetById(1);
employee.Name = "John";
unitOfWork.SaveChanges();
ComputeSalary(employee);
}
}
catch (Exception ex)
{
// Exception logic goes here...
}
private void ComputeSalary(Employee employee)
{
int x = 1/0; // This will throw an exception.
return;
}
}
The Context class inherits from DbContext of Entity Framework. So in the ComputeSalary() function, you will see that an exception will be thrown since division of zero is not allowed. This will cause the code to exit the using block, thereby, disposing unitOfWork. My question is, will the changes performed by SaveChanges() still propagate to the database even though an exception occurred in the ComputeSalary() function?

If you don't have a TransactionScope or something like that, the changes will be committed.
PS: Not sure, but think if your using EF Core, it will commit to DataBase event if you have a TransactionScope. See https://github.com/aspnet/EntityFrameworkCore/issues/5595

Related

Entity Framework Core - One Transaction for multiple API-REST-Requests

Currently I'm trying to create integration tests to test my .net core REST-API. I don't want to get the test data stored in database finally. One integration test sends multiple HTTP-Requests.
My idea was to start a global transaction before I do the first HTTP Request and finally do an rollback.
But I can't find any way how to store the transaction globally.
Is there a way?
Following my code of the "transactionRepository" where I tried to store the transaction globally:
public class TransactionRepository : ITransactionRepository
{
private static IDbContextTransaction TRANSACTION;
private readonly Context _context;
public TransactionRepository(Context context)
{
_context = context;
}
public void Commit()
{
if(TRANSACTION == null)
{
throw new ValidationException($"There is no transaction");
}
TRANSACTION.Commit();
}
public void Rollback()
{
if(TRANSACTION == null)
{
throw new ValidationException($"There is no open transaction");
}
TRANSACTION.Rollback();
}
public void Begin()
{
if(TRANSACTION != null)
{
throw new ValidationException($"already opened");
}
TRANSACTION = _context.Database.BeginTransaction();
}
}
then I call following URLs:
POST /transaction/begin //Start Transaction
POST /myentity //CREATE
PUT /myentity //UPDATE
POST /transaction/rollback
But finally all data gets stored in database despite the opened transaction.
Does anyone has an idea what I'm doing wrong respectively if it is possible to have one transaction for multiple API-Requests anyway?

How to fix 'A second operation started on this context before a previous operation completed...' when working with dependency injection?

when reading data from the database I get this error:
A second operation started on this context before a previous operation
completed. Any instance members are not guaranteed to be thread safe.
I have the following ApplicationContext.cs:
public class ApplicationContext : Microsoft.EntityFrameworkCore.DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{ }
public DbSet<MyClass> MyClasses{ get; set; }
}
The following ApplicationContextFactory.cs
public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
public ApplicationContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationContext>();
var connection = "myConnectionString";
builder.UseSqlServer(connection);
return new ApplicationContext(builder.Options);
}
}
The following ServiceLoader.cs (where I declare the DI):
public static class ServiceLoader
{
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IRepository, Repository>();
var connection = "myConnectionString";
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection));
}
}
and finally, the following Repository, where the exception is thrown:
public class Repository : IRepository
{
private ApplicationContext _db;
public Repository (ApplicationContext db)
{
_db = db;
}
public List<MyClass> Get()
{
_db.MyClasses.ToList();
}
}
I have also tried to declare the Repository as Transient instead of Singleton, but a similar error is thrown
'An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Any idea on how to fix this? Thanks!
In my case I found the following information helpful:
https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
And changed the lifetime scope of my Db Context to transient using the overloaded AddDbContext method in startup:
services.AddDbContext<MyAppDbContext>(options => {
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"));
}, ServiceLifetime.Transient);
You can wrap an async Task around your Get() function and await your results:
public async Task<List<MyClass>> Get()
{
return await _db.MyClasses.ToListAsync();
}
I wrote a solution, which uses a queue. It is still single threaded, but you can call it from different threads.
public class ThreadSafeDataContext
{
private Thread databaseThread;
private Queue<PendingQuery> pendingQueries = new Queue<PendingQuery>();
private DatabaseContext db = new DatabaseContext();
private bool running = true;
public ThreadSafeDataContext()
{
databaseThread = new Thread(new ThreadStart(DoWork));
databaseThread.Start();
}
public void StopService()
{
running = false;
}
private void DoWork()
{
while(running)
{
if (pendingQueries.Count > 0)
{
// Get and run query
PendingQuery query = pendingQueries.Dequeue();
query.result = query.action(db);
query.isFinished = true;
}
else
{
Thread.Sleep(1); // Waiting for queries
}
}
}
public T1 Query<T1>(Func<DatabaseContext, T1> action)
{
Func<DatabaseContext, object> a = (DatabaseContext db) => action(db);
PendingQuery query = new PendingQuery(a);
pendingQueries.Enqueue(query);
while (!query.isFinished) {
Thread.Sleep(1); // Wait until query is finished
}
return (T1)query.result;
}
}
class PendingQuery
{
public Func<DatabaseContext, object> action;
public bool isFinished;
public object result;
public PendingQuery(Func<DatabaseContext, object> action)
{
this.action = action;
}
}
Then you can just run a query from different threads by using:
TeamMembers teamMembers = threadSafeDb.Query((DatabaseContext c) => c.team.ToArray())

Use the same transaction in different methods with Entity Framework Core

EDIT (02/03/2018) : Since Entity Framework Core 2.1, EF Core implements transactions, cross-context transactions, ambient transactions and transactions scopes so this question is now out of date.
This is the official documentation about the transactions in EF Core : https://learn.microsoft.com/en-us/ef/core/saving/transactions.
How can I use the same transaction in differents methods ? The objective is to can commit or rollback all the modification if an error occurred.
I'm using Entity Framework Core version 1.1.0-preview1-final, and SQL Server 2014.
For example, I have an Entity Framework database context :
public class ApplicationDatabaseContext : DbContext
{
public ApplicationDatabaseContext(DbContextOptions<ApplicationDatabaseContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TransactionLog1>(entity =>
{
entity.ToTable("TRANSACTION_LOG_1");
entity.Property(e => e.CreationDate)
.HasColumnType("datetime")
.HasDefaultValueSql("getdate()");
});
modelBuilder.Entity<TransactionLog2>(entity =>
{
entity.ToTable("TRANSACTION_LOG_2");
entity.Property(e => e.CreationDate)
.HasColumnType("datetime")
.HasDefaultValueSql("getdate()");
});
}
public virtual DbSet<TransactionLog1> TransactionLog1 { get; set; }
public virtual DbSet<TransactionLog2> TransactionLog2 { get; set; }
}
And I have two classes to deal with data, the both are using the same context :
public interface IRepository1
{
void Create(Guid key);
}
public sealed class Repository1 : IRepository1
{
private readonly ApplicationDatabaseContext _dbContext;
public Repository1(ApplicationDatabaseContext dbcontext)
{
_dbContext = dbcontext;
}
public void Create(Guid key)
{
using (_dbContext.Database.BeginTransaction())
{
try
{
_dbContext.TransactionLog1.Add(new TransactionLog1 { Key = key });
_dbContext.SaveChanges();
_dbContext.Database.CommitTransaction();
}
catch (Exception)
{
throw;
}
}
}
}
public interface IRepository2
{
void Create(Guid key);
}
public sealed class Repository2 : IRepository2
{
private readonly ApplicationDatabaseContext _dbContext;
public Repository2(ApplicationDatabaseContext dbcontext)
{
_dbContext = dbcontext;
}
public void Create(Guid key)
{
using (_dbContext.Database.BeginTransaction())
{
try
{
_dbContext.TransactionLog2.Add(new TransactionLog2 { Key = key });
_dbContext.SaveChanges();
_dbContext.Database.CommitTransaction();
}
catch (Exception)
{
throw;
}
}
}
}
In my business logic, I have a service and I would like to call the method void Create(Guid key) on my first repository, then the same method from my second repository and commit only if the both occurred without error (if any error occurred in the secon method, I would like to rollback the commit done in the first method).
How can I do that ? What is the best practice with Entity Framework Core and transactions ?
I tried several things, like this, but it never works (with this method I have the error ) :
Warning as error exception for warning
'RelationalEventId.AmbientTransactionWarning': An ambient transaction
has been detected. Entity Framework Core does not support ambient
transactions.
public sealed class Service3 : IService3
{
private readonly IRepository1 _repo1;
private readonly IRepository2 _repo2;
public Service3(IRepository1 repo1, IRepository2 repo2)
{
_repo1 = repo1;
_repo2 = repo2;
}
public void Create(Guid key)
{
using (TransactionScope scope = new TransactionScope())
{
try
{
_repo1.Create(key);
_repo2.Create(key);
scope.Complete();
}
catch (Exception)
{
throw;
}
}
}
}
I read the documentation, espacially this page (https://learn.microsoft.com/en-us/ef/core/saving/transactions) but I don't have the method UseTransaction on Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.
One possible approach is to use a middleware and put your logic for begin/commit/rollback there. For example, at the beginning of each request you begin a transaction on the underlying database connection. At the end of the request commit or rollback the transaction. Since you most probably use single context instance per request, this would solve your problem. Additionally you will extract this concern from your repository/service classes.
Here is a sample code you might you use as a startup. Haven't tested it in real scenario though:
public class TransactionPerRequestMiddleware
{
private readonly RequestDelegate next_;
public TransactionPerRequestMiddleware(RequestDelegate next)
{
next_ = next;
}
public async Task Invoke(HttpContext context, ApplicationDbContext dbContext)
{
var transaction = dbContext.Database.BeginTransaction(
System.Data.IsolationLevel.ReadCommitted);
await next_.Invoke(context);
if (context.Response.StatusCode == 200)
{
transaction.Commit();
}
else
{
transaction.Rollback();
}
}
}
Then in your Startup.Configure() method:
app.UseMiddleware<TransactionPerRequestMiddleware>();
EDIT (02/03/2018) : Since Entity Framework Core 2.1, you can use transactions, cross-context transactions, ambient transactions and transactions scopes so you don't have to implement a work-around.
This is the official documentation : https://learn.microsoft.com/en-us/ef/core/saving/transactions.
I finally found a solution waiting the next release of Entity Framework Core who will enable to use transaction scopes and ambient transactions.
As the db transaction is related to a database context, and the database context is the same in all my data access classes (thank's dependency injection), when I start a transaction in a process, it will be shared by others data access classes in the same process until the transaction will be disposed (I had to upgrade my Entity Framework Core to 1.1.0-preview1-final to have disposable transactions).
Concretely, I had a class to deal with transactions :
public interface ITransactionDealerRepository
{
void BeginTransaction();
void CommitTransaction();
void RollbackTransaction();
void DisposeTransaction();
}
public sealed class TransactionDealerRepository : BaseEntityFrameworkRepository, ITransactionDealerRepository
{
public TransactionDealerRepository(MyDBContext dbContext)
: base(dbContext)
{ }
public void BeginTransaction()
{
_dbContext.Database.BeginTransaction();
}
public void CommitTransaction()
{
_dbContext.Database.CommitTransaction();
}
public void RollbackTransaction()
{
_dbContext.Database.RollbackTransaction();
}
public void DisposeTransaction()
{
_dbContext.Database.CurrentTransaction.Dispose();
}
}
And I use this class like this in my services :
public void Create(Guid key)
{
_transactionProvider.BeginTransaction();
try
{
_repo1.Create(key);
_repo2.Create(key);
_transactionProvider.CommitTransaction();
}
catch (Exception)
{
_transactionProvider.RollbackTransaction();
throw;
}
finally
{
_transactionProvider.DisposeTransaction();
}
}
once entity framework itself wrap every request as transaction, you can avoid explicit transactions and explicit "saveChanges", and u are get all the request committed or rollbacked atomically

Unit Test Fails for Unit Of Work Test

This is my test:
[TestMethod]
public void TestUnitOfWork()
{
UnitOfWork unitOfWork = new UnitOfWork();
unitOfWork.ContactRepository.Insert(new Contact
{
Id = Guid.NewGuid(),
FirstName = "Dom",
LastName = "A",
Email = "dominicarchual#yahoo.com"
});
var contacts = unitOfWork.ContactRepository.Get(x => x.FirstName == "Dominic");
Assert.AreEqual(1, contacts.Count());
}
The error I get is:
Test method
MvcContacts.Tests.Controllers.HomeControllerTest.TestUnitOfWork threw
exception: System.Data.ProviderIncompatibleException: An error
occurred while getting provider information from the database. This
can be caused by Entity Framework using an incorrect connection
string. Check the inner exceptions for details and ensure that the
connection string is correct. --->
System.Data.ProviderIncompatibleException: The provider did not return
a ProviderManifestToken string. --->
System.Data.SqlClient.SqlException: A network-related or
instance-specific error occurred while establishing a connection to
SQL Server. The server was not found or was not accessible. Verify
that the instance name is correct and that SQL Server is configured to
allow remote connections. (provider: SQL Network Interfaces, error: 26
- Error Locating Server/Instance Specified)
I don't have any database set up; i.e. my context looks like this:
namespace MvcContacts.DAL
{
public class ContactsContext : DbContext
{
public DbSet<Contact> Contacts { get; set; }
}
}
I don't know exactly how to map this to my database; but, I was thinking that I wouldn't have to do that yet since I am just trying to test using mock data. Am I wrong?
E1: This is my unit of work class.
namespace MvcContacts.DAL
{
public class UnitOfWork : IDisposable
{
private ContactsContext context = new ContactsContext();
private GenericRepository<Contact> contactRepository;
public GenericRepository<Contact> ContactRepository
{
get
{
if (this.contactRepository == null)
{
this.contactRepository = new GenericRepository<Contact>(context);
}
return contactRepository;
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
As i said, the problem is that you are actually calling real database inside your UnitOfWork. I'm pretty sure, your GenericRepository<> class just wraps DbSet inside your context. Here is where you create the 'real' database accessor.
private ContactsContext context = new ContactsContext();
But the problem is you misunderstand the whole concept of repositories. Unit of work is an abstraction of some data source. You should not unit test the abstraction, instead you should unit test some functionality which depends upon it. By the way, DbContext itself is a Unit of work by that definition (from martinfowler.com):
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
Why don't people just leave it as it is? Because there is a flaw in it. Let me explain by example. Seems like you're learning ASP.Net MVC so let's write some controller:
public class ContactsController
{
public ActionResult Index(int pageSize, int currentPage)
{
using(var db = new MvcLearningContext())
{
var contacts = db.Contacts
.Skip((currentPage - 1) * pageSize)
.Take(pageSize)
.ToList();
return View(contacts);
}
}
}
As you may know, one of the great advantages of MVC is the ability to unit test controller logic. So, let's try to write a simple unit test to make sure out controller action doesn't return more entries than the given page size:
[TestMethod]
public void IndexShouldNotReturnMoreThanPageSizeResults()
{
// arrange
var controller = new ContactsController();
// act
var view = (ViewResult) controller.Index(10, 1);
// assert
var Model = (IEnumerable<Contact>) view.Model;
Assert.IsTrue(view.Model.Count() <= 10)
}
But wait... We do not want to query the real database in the unit test. Here comes the problem with EF's DbContext: it completely depends on real database. But how can we avoid that? UnitOfWork comes in play:
public class ContactsController
{
private UnitOfWorkFactoryBase _factory { get; set; }
public ContactsController(UnitOfWorkFactoryBase factory)
{
factory = _factory;
}
public ActionResult Index(int pageSize, int currentPage)
{
using(var db = _factory.Create())
{
var contacts = db.Contacts
.Skip((currentPage - 1) * pageSize)
.Take(pageSize)
.ToList();
return View(contacts);
}
}
}
unit test code:
[TestMethod]
public void IndexShouldNotReturnMoreThanPageSizeResults()
{
// arrange
var factory = new MockUnitOfWorkFactory();
var controller = new ContactsController(factory);
// act
var view = (ViewResult) controller.Index(10, 1);
// assert
var Model = (IEnumerable<Contact>) view.Model;
Assert.IsTrue(view.Model.Count() <= 10)
}
and in production you replace MockUnitOfWorkFactory with UnitOfWorkFactory
UPD: basic implementation of factories:
public abstract class UnitOfWorkFactoryBase
{
public abstract UnitOfWorkBase Create();
}
public class UnitOfWorkFactory : UnitOfWorkFactoryBase
{
public override UnitOfWorkBase Create()
{
return new UnitOfWork();
}
}
public class MockUnitOfWorkFactory : UnitOfWorkFactoryBase
{
public override UnitOfWorkBase Create()
{
return new MockUnitOfWork();
}
}
UnitOfWork and MockUnitOfWork are implementations of UnitOfWorkBase abstract class.

EF Core Savechanges not working for Remove

I am trying to remove child item(s) from my domain, but Savechanges() is not working and no exceptions occurred and I am tracing and find my entity in Dbcontext that state changed to modified. add or update working correctly. everything worked ok before I add IEventbus interface to publish an event. my remove method in the domain class.
public void RemoveItem(Guid id, IEventBus eventBus)
{
if (!string.IsNullOrWhiteSpace(this.Confirmer.UserId))
{
throw new RemoveItemOfConfirmedScrapException();
}
var scrapItem = this.ScrapItems.First(p => p.Id == id);
var assetId = scrapItem.AssetId;
this.ScrapItems.Remove(this.ScrapItems.First(p => p.Id == id));
eventBus.Publish(new ScrapItemRemovedEvent(assetId));
}
and this is my UnitOfWork for saving in the database.
public class UnitOfWork : IUnitWork
{
private readonly IDbContext dbContext;
private DbContextBase dbContextBase;
public UnitOfWork(IDbContext dbContext)
{
this.dbContext = dbContext;
this.dbContextBase = dbContext as DbContextBase;
}
public void Commit()
{
try
{
this.dbContext.SaveChanges();
}
catch
{
RollBack();
throw;
}
}
public void RollBack()
{
this.dbContextBase.ChangeTracker.Entries()
.Where(e => e.Entity != null).ToList()
.ForEach(e => e.State = EntityState.Detached);
}
}
what happens to that delete works in other domains even in parent entity but in this case not delete in database. and ChangeTracker shows that specific entity state changed to "modified" but without any exception finish job and not effected on the database.
I have an application service layer with decoration design pattern to call UnitOfWork
public void Dispatch<TCommand>(TCommand command)
where TCommand : Command
{
var commandHandler = this.diContainer.Resolve<ICommandHandler<TCommand>>();
var transactionCommandHandler = new TransactionalCommandHandler<TCommand>(commandHandler, this.diContainer);
var logCommandHandler = new LogCommandHandler<TCommand>(transactionCommandHandler);
logCommandHandler.Execute(command);
}
and transactional class that calls UnitOfWork
public class TransactionalCommandHandler<TCommand> : ICommandHandler<TCommand>
where TCommand : Command
{
private readonly ICommandHandler<TCommand> commandHandler;
private readonly IDiContainer container;
public TransactionalCommandHandler(ICommandHandler<TCommand> commandHandler, IDiContainer container)
{
this.commandHandler = commandHandler;
this.container = container;
}
public void Execute(TCommand command)
{
var unitOfWork = this.container.Resolve<IUnitWork>(); // ServiceLocator.Current.Resolve<IUnitWork>();
try
{
this.commandHandler.Execute(command);
unitOfWork.Commit();
}
catch (Exception e)
{
unitOfWork.RollBack();
throw;
}
}
}
var scrapItem = this.ScrapItems.First(p => p.Id == id);
var assetId = scrapItem.AssetId;
this.ScrapItems.Remove(scrapItem);
this.Commit(); // you weren't committing before
Removing entity from collection only marks it's state as Deleted.
You need to call SaveChanges method in order to reflect the changes to database.
A very simple carelessness had caused this problem.
in my command facade have transaction scope that forgot put "scope.complete()"
public void DeleteScrapItem(DeleteScrapItemCommand command)
{
using (var scope = new TransactionScope())
{
Action<dynamic> updateAssetStatus = p => this.CommandBus.Dispatch(new UpdateAssetStatusCommand()
{
AssetId = p.AssetId,
Status = 0
});
this.eventBus.Subscribe<ScrapItemRemovedEvent>(updateAssetStatus);
this.CommandBus.Dispatch(command);
scope.Complete(); // forgot put this ...
}
}

Categories

Resources