I have overridden the SaveChanges() method to call a stored procedure after calling base.SaveChanges(). The stored procedure is in a repository that I can mock out easily enough, but how do I prevent the DbContext from actually saving the entity to the database?
Update
I should mention also that I'm wrapping the two actions in a transaction like this:
public partial class MyDbContext
{
private readonly IProcRepository _procRepository;
public MyDbContext(IProcRepository procRepository, string connectionString) : base(connectionString)
{
_procRepository = procRepository;
}
public override int SaveChanges()
{
var stateEntries = 0;
using (var dbContextTransaction = Database.BeginTransaction())
{
try
{
stateEntries = base.SaveChanges();
_procRepository.MyStoredProcedure();
dbContextTransaction.Commit();
}
catch
{
dbContextTransaction.Rollback();
throw;
}
}
return stateEntries;
}
}
Related
I'm attempting to write a unit test for my repository class for its Create method that uses DbContext and the Add method.
My idea was to get a count of the number of existing records. Call the Create method. Get the new count of records and check that its incremented by 1.
However, when running the unit test, it errors when calling the Add method with the following error:
{"Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DbContext'."}
I'm trying to understand why is this and how to overcome this?
public class MyDatabaseContext : DbContext
{
public MyDatabaseContext(DbContextOptions<MyDatabaseContext> options) : base(options)
{
}
public DbSet<Record> Records { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Record>();
}
}
public class Repository : IRepository
{
private readonly MyDatabaseContext _dbContext;
public Repository(MyDatabaseContext dbContext)
{
_dbContext = dbContext;
}
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
public IEnumerable<Record> GetAll()
{
try
{
using (_dbContext)
{
return _dbContext.Records.ToList();
}
}
catch (Exception ex)
{
return null;
}
}
}
public interface IRepository
{
Record Create(Record record);
IEnumerable<Record> GetAll();
}
Startup.cs:
services.AddDbContext<MyDatabaseContext>(opt => opt.UseInMemoryDatabase("memoryDb"));
services.AddScoped<IRepository, Repository>();
Unit test:
[TestMethod]
public async Task Create_Successfully()
{
var repository = new Repository(await GetDbContext());
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
private async Task<DbContext> GetDbContext()
{
var options = new DbContextOptionsBuilder<DbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
var context = new DbContext(options);
context.Database.EnsureCreated();
if (await context.Records.CountAsync() <= 0)
{
for (int i = 1; i <= 3; i++)
{
context.Records.Add(new Records());
await context.SaveChangesAsync();
}
}
return context;
}
You need to remove the using statements in the GetAll and Create methods:
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
To:
public Record Create(Record record)
{
try
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
catch (Exception ex)
{
return null;
}
}
You don't need to worry about disposing the service as the conatiner will do that for you in production. In your tests you can do this if you want to clean things up:
[TestMethod]
public async Task Create_Successfully()
{
using (var context = await GetDbContext())
{
var repository = new Repository(context);
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
}
I'm trying to implement the tracker-enabled-dbcontext package from the documentaion and tracker-enabled-dbcontext git repository
But I'm unable to change save changes to target a different database. I've modified my SaveChanges
public class MyDBContext : DbContext, IUnitOfWork {}
public class MacsAuditDbContext : TrackerEnabledDbContext.TrackerContext {}
in MyDBContext
public override int SaveChanges()
{
DateTime nowAuditDate = DateTime.Now;
IEnumerable<System.Data.Entity.Infrastructure.DbEntityEntry<DomainEntity>> changeSet = ChangeTracker.Entries<DomainEntity>();
if (changeSet != null)
{
foreach (System.Data.Entity.Infrastructure.DbEntityEntry<DomainEntity> entry in changeSet)
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.Created = nowAuditDate;
entry.Entity.Modified = nowAuditDate;
break;
case EntityState.Modified:
entry.Entity.Modified = nowAuditDate;
break;
}
}
}
using (MacsAuditDbContext db = new MacsAuditDbContext())
{
db.SaveChanges();
}
return base.SaveChanges();
}
in my startup class
public class Startup
{
public void Configuration(IAppBuilder app)
{
AuthConfig.Register(app);
GlobalConfiguration.Configuration
.UseSqlServerStorage("MacsAuditDbContext");
}
}
But I am still unable to save audit logs to the target(secondary) database. my Domain entires saving my primary DB but not audit logs.
Do I want to pass MyDBContext to MacsAuditDbContext? Or Am I doing something wrong? please help me.
You can try leveraging OnAuditLogGenerated event. Something along this lines:
public sealed class MyDBContext : TrackerContext
{
public MyDBContext ()
{
OnAuditLogGenerated += SaveToAnotherDb;
}
private void SaveToAnotherDb(object? sender, AuditLogGeneratedEventArgs args)
{
var auditLog = args.Log;
using (MacsAuditDbContext db = new MacsAuditDbContext())
{
db.AuditLog.Add(auditLog);
db.SaveChanges();
}
//skips saving to local database
args.SkipSavingLog = true;
}
protected override void Dispose(bool disposing)
{
OnAuditLogGenerated -= SaveToAnotherDb;
base.Dispose(disposing);
}
}
I have an ASP.Net MVC 5 application using EF6. I'm using Ninject for dependency injection.
I have the following code to add comments to a ticket. But sometimes it creating duplicate record. Why is is this happening and how can I fix?
So all in all I'm ending up with 3 comments being added as opposed to two with the user comments being duplicated - this is only happening occasionally.
var ticket = _unitOfWork.TicketRepository.GetTicket(dto.TicketId);
using (var transaction = _unitOfWork.BeginTransaction())
{
try
{
// Add user posted comment
var comment = new Comment
{
TicketId = dto.TicketId,
Comments = dto.Comments
};
ticket.AddComment(comment);
// save changes
_unitOfWork.Complete();
// Add a system comment
ticket.AddComment(new Comment
{
TicketId = dto.TicketId,
Comments = "<b>System Comment:</b><br>Some auto comment"
});
_notificationService.Send(ticket, comment, User.Identity.GetUserId());
// save changes
_unitOfWork.Complete();
// Commit
transaction.Commit();
return Ok(Mapper.Map<CommentDto>(_unitOfWork.CommentRepository.GetComment(comment.Id)));
}
catch (Exception ex)
{
transaction.Rollback();
return InternalServerError(ex);
}
}
These are the methods in my unitOfWork class:
public void Complete()
{
_context.SaveChanges();
}
public DbContextTransaction BeginTransaction()
{
return _context.Database.BeginTransaction();
}
This is the add comment method in my Ticket class:
public void AddComment(Comment comment)
{
Comments.Add(comment);
}
* UPDATE *
The only other thing I'm aware of that uses db context is a background job to send email which runs every minute using hangfire but doesn't use unitofwork - see my code below:
[DisallowConcurrentExecution]
public class EmailJob : IJob
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly IUserNotificationRepository _userNotificationRepository;
private readonly INotificationMailer _notificationMailer;
private readonly ApplicationDbContext _context;
public EmailJob()
{
_context = new ApplicationDbContext();
_userNotificationRepository = new UserNotificationRepository(_context);
_notificationMailer = new NotificationMailer();
}
public EmailJob(IUserNotificationRepository userNotificationRepository,
INotificationMailer notificationMailer)
{
_userNotificationRepository = userNotificationRepository;
_notificationMailer = notificationMailer;
}
// Use for hangfire
public void Execute()
{
SendMail(_logger);
}
private void SendMail(Logger logger)
{
// some mail sending code...
// Persist changes back to database
_context.SaveChanges();
}
}
Any ideas chaps?
This seems like an obvious question, but is CreateDatabaseIfNotExists.InitializeDatabase atomic?
Currently I have code similar to below. This an attempt to make sure that either database creation completes fully, or the database doesn't exist.
public class MyContext : CreateDatabaseIfNotExists<MyContext>
{
public override void InitializeDatabase(MyContext context)
{
base.InitializeDatabase(context);
//Removed custom initialization code
}
protected override void Seed(MyContext context)
{
//Removed adding / updating seed data
context.SaveChanges();
base.Seed(context);
}
}
public static class DbCreator
{
public static void CreateDatabase()
{
using (MyContext dbContext = new MyContext())
{
try
{
var dbIntializer = new MyDbInitializer();
dbIntializer.InitializeDatabase(dbContext);
}
catch (Exception ex)
{
dbContext.Database.Delete();
throw;
}
}
}
}
I've looked in some obvious places for an answer such as https://msdn.microsoft.com/en-us/library/gg696403(v=vs.113).aspx.
I am using Entity Framework 6.1.3
I am trying to make sense of mocking in unit testing and to integrate the unit testing process to my project. So I have been walking thru several tutorials and refactoring my code to support mocking, anyway, I am unable to pass the tests, because the DB method I am trying to test is using a transaction, but when creating a transaction, I get
The underlying provider failed on Open.
Without transaction everything works just fine.
The code I currently have is:
[TestMethod]
public void Test1()
{
var mockSet = GetDbMock();
var mockContext = new Mock<DataContext>();
mockContext.Setup(m => m.Repository).Returns(mockSet.Object);
var service = new MyService(mockContext.Object);
service.SaveRepository(GetRepositoryData().First());
mockSet.Verify(m => m.Remove(It.IsAny<Repository>()), Times.Once());
mockSet.Verify(m => m.Add(It.IsAny<Repository>()), Times.Once());
mockContext.Verify(m => m.SaveChanges(), Times.Once());
}
// gets the DbSet mock with one existing item
private Mock<DbSet<Repository>> GetDbMock()
{
var data = GetRepositoryData();
var mockSet = new Mock<DbSet<Repository>>();
mockSet.As<IQueryable<Repository>>().Setup(m => m.Provider).Returns(data.Provider);
// skipped for brevity
return mockSet;
}
Code under test:
private readonly DataContext _context;
public MyService(DataContext ctx)
{
_context = ctx;
}
public void SaveRepositories(Repository repo)
{
using (_context)
{
// Here the transaction creation fails
using (var transaction = _context.Database.BeginTransaction())
{
DeleteExistingEntries(repo.Id);
AddRepositories(repo);
_context.SaveChanges();
transaction.Commit();
}
}
}
I was trying to mock the transaction part as well:
var mockTransaction = new Mock<DbContextTransaction>();
mockContext.Setup(x => x.Database.BeginTransaction()).Returns(mockTransaction.Object);
but this is not working, failing with:
Invalid setup on a non-virtual (overridable in VB) member: conn =>
conn.Database.BeginTransaction()
Any ideas how to solve this?
As the second error message says, Moq can't mock non-virtual methods or properties, so this approach won't work. I suggest using the Adapter pattern to work around this. The idea is to create an adapter (a wrapper class that implements some interface) that interacts with the DataContext, and to perform all database activity through that interface. Then, you can mock the interface instead.
public interface IDataContext {
DbSet<Repository> Repository { get; }
DbContextTransaction BeginTransaction();
}
public class DataContextAdapter {
private readonly DataContext _dataContext;
public DataContextAdapter(DataContext dataContext) {
_dataContext = dataContext;
}
public DbSet<Repository> Repository { get { return _dataContext.Repository; } }
public DbContextTransaction BeginTransaction() {
return _dataContext.Database.BeginTransaction();
}
}
All of your code that previously used the DataContext directly should now use an IDataContext, which should be a DataContextAdapter when the program is running, but in a test, you can easily mock IDataContext. This should make the mocking way simpler too because you can design IDataContext and DataContextAdapter to hide some of the complexities of the actual DataContext.
I've tried the wrapper/adapter approach, but came up against the problem that when you then go to test the code:
using (var transaction = _myAdaptor.BeginTransaction())
Your mock/fake still needs to return something so the line transaction.Commit();
can still execute.
Normally I'd set the fake of my adapter to return an interface from BeginTransaction() at that point (so I can fake that returned object too), but the DbContextTransaction returned by BeginTransaction() only implements IDisposable so there was no interface that could give me access to the Rollback and Commit methods of DbContextTransaction.
Furthermore, DbContextTransaction has no public constructor, so I couldn't just new up an instance of it to return either (and even if I could, it wouldn't be ideal as I couldn't then check for calls to commit or rollback the transaction).
So, in the end I took a slightly different approach and created a separate class altogether to manage the transaction:
using System;
using System.Data.Entity;
public interface IEfTransactionService
{
IManagedEfTransaction GetManagedEfTransaction();
}
public class EfTransactionService : IEfTransactionService
{
private readonly IFMDContext _context;
public EfTransactionService(IFMDContext context)
{
_context = context;
}
public IManagedEfTransaction GetManagedEfTransaction()
{
return new ManagedEfTransaction(_context);
}
}
public interface IManagedEfTransaction : IDisposable
{
DbContextTransaction BeginEfTransaction();
void CommitEfTransaction();
void RollbackEfTransaction();
}
public class ManagedEfTransaction : IManagedEfTransaction
{
private readonly IDataContext _context;
private DbContextTransaction _transaction;
public ManagedEfTransaction(IDataContext context)
{
_context = context;
}
/// <summary>
/// Not returning the transaction here because we want to avoid any
/// external references to it stopping it from being disposed by
/// the using statement
/// </summary>
public void BeginEfTransaction()
{
_transaction = _context.Database.BeginTransaction();
}
public void CommitEfTransaction()
{
if (_transaction == null) throw new Exception("No transaction");
_transaction.Commit();
_transaction = null;
}
public void RollbackEfTransaction()
{
if (_transaction == null) throw new Exception("No transaction");
_transaction.Rollback();
_transaction = null;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// free managed resources
if (_transaction != null)
{
_transaction.Dispose();
_transaction = null;
}
}
}
}
I then inject that service class into whatever classes need to use a transaction. For example, using the code from the original question:
private readonly DataContext _context;
private readonly IEfTransactionManager _transactionManager;
public MyService(DataContext ctx, IEfTransactionManager transactionManager)
{
_context = ctx;
_transactionManager = transactionManager;
}
public void SaveRepositories(Repository repo)
{
using (_context)
{
// Here the transaction creation fails
using (var managedEfTransaction = _transactionManager.GetManagedEfTransaction())
{
try
{
managedEfTransaction.BeginEfTransaction();
DeleteExistingEntries(repo.Id);
AddRepositories(repo);
_context.SaveChanges();
managedEfTransaction.CommitEfTransaction();
}
catch (Exception)
{
managedEfTransaction.RollbackEfTransaction();
throw;
}
}
}
}
You can find a pretty good solution here.
In short, you need to create proxy class for DbContextTransaction and use it instead of an original one. So that you can mock your proxy and test your method with BeginTransaction().
PS. In article which I linked above, author forgot about the virtual keyword for BeginTransaction() method placed in dbContext class:
// <summary>
/// When we call begin transaction. Our proxy creates new Database.BeginTransaction and gives DbContextTransaction's control to proxy.
/// We do this for unit test.
/// </summary>
/// <returns>Proxy which controls DbContextTransaction(Ef transaction class)</returns>
public virtual IDbContextTransactionProxy BeginTransaction()
{
return new DbContextTransactionProxy(this);
}