Is CreateDatabaseIfNotExists.InitializeDatabase atomic? - c#

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

Related

How do I target another database with tracker-enabled-dbcontext

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);
}
}

ASP.Net MVC - Entity Framework 6 sometimes creates duplicate records?

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?

How to persist entity with business rules?

I'm using the ASP.NET Boilerplate for my project. And I have an entity as shown in the code snippet below.
public class Transaction : FullAuditedEntity<Guid>, IMustHaveTenant
{
protected Transaction()
{
TransactionState = TransactionState.Uncompleted;
}
public TransactionState TransactionState { get; protected set; }
public virtual Loan Loan { get; protected set; }
public int TenantId { get; set; }
// ...
public async Task CompleteAsync(ICoreBankingService coreBankingService, IRepository<Transaction, Guid> transactionRepository)
{
try
{
// Perform a series of compulsory actions in some given sequence with coreBankingService that might throw exception
Loan.SetSomeStuffThatOriginatedFromMotherTransaction();
TransactionState = TransactionState.Completed;
}
catch (Exception ex)
{
// Log the exception and set this Transaction entity state appropriately
TransactionState = TransactionState.Failed;
}
finally
{
// Make sure by all means to persist the resulting the entity within itself
await transactionRepository.UpdateAsync(this);
}
}
}
I understand I should separate persistence from the entity (which by the way, is an architecture provided by ASP.NET Boilerplate out of the box! using Application Services).
However, I NEED to make certain that I perform a series of compulsory actions in some given sequence with coreBankingService and persist changes at each stage of these computations on the Transaction entity, hence the reason for my naive and probably wrong approach.
Please, what is the right approach to this kind of problem? How do I persist states of an entity that results from a computation or action within the same entity?
You can expose internal methods that change the state:
public class Transaction : FullAuditedEntity<Guid>, IMustHaveTenant
{
protected Transaction()
{
TransactionState = TransactionState.Uncompleted;
}
public TransactionState TransactionState { get; protected set; }
public virtual Loan Loan { get; protected set; }
// ...
internal void Abort()
{
TransactionState = TransactionState.Failed;
}
internal void Complete()
{
TransactionState = TransactionState.Completed;
}
}
And define the domain service in the same assembly:
public class TransactionManager : DomainService
{
private readonly ICoreBankingService _coreBankingService;
private readonly LoanManager _loanManager;
private readonly IRepository<Transaction, Guid> _transactionRepository;
public TransactionManager(
ICoreBankingService coreBankingService,
LoanManager loanManager,
IRepository<Transaction, Guid> transactionRepository)
{
_coreBankingService = coreBankingService;
_loanManager = loanManager;
_transactionRepository = transactionRepository;
}
public async Task TryCompleteAsync(Transaction transaction)
{
try
{
// Use _coreBankingService to do something that might throw exception
_coreBankingService.DoSomething();
_loanManager.SetSomeStuffThatOriginatedFromMotherTransaction(transaction.Loan);
transaction.Complete();
}
catch (Exception ex)
{
// Log the exception and abort the Transaction
transaction.Abort();
}
finally
{
// Make sure by all means to persist the resulting the entity
await _transactionRepository.UpdateAsync(transaction);
}
}
}
Usage:
await _transactionManager.TryCompleteAsync(transaction);

How can I unit test my overridden DbContext.SaveChanges()?

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;
}
}

"The operation cannot be completed because the DbContext has been disposed" #2

I'm building a simple ASP.NET API using EF and Oracle Database. When I want to get all elements from a database table the response (500) says "The operation cannot be completed because the DbContext has been disposed".
Well, I've tried to solve this problem before to post it here. But I can't. My Controller Code is as follows.
public class PruebasController : ApiController
{
//Prueba[] pruebas = new Prueba[]
//{
// new Prueba { Name = "Tomato Soup"},
// new Prueba { Name = "Yo-yo"},
// new Prueba { Name = "Hammer"}
//};
public IQueryable<Prueba> GetAllPruebas()
{
Database.SetInitializer(new DropCreateDatabaseAlways<OracleDbContext>());
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas;
}
}
}
(As you see, I have a "pruebas" List and when I return it the http service works)
And this is my OracleDbContext
public class OracleDbContext : DbContext
{
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
You are returning an IQueryable object. Once you return, you exit your Using statement, which closes your Context. You need to enumerate using .ToList() before you exit your using statement. This will execute the query while the context is still open.
Change it to this:
public List<Prueba> GetAllPruebas()
{
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas.ToList();
}
}
Also, you should add your initializer in the constructor of your context, not your GetAllPruebas method, like this:
public class OracleDbContext : DbContext
{
public OracleDbContext()
{
Database.SetInitializer<OracleDbContext>(new DropCreateDatabaseAlways<OracleDbContext>());
}
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
Problem Solved. I wrote CreateDatabaseIfNotExists instead DropCreateDatabaseAlways in the Database.SetInitializer and it works.

Categories