EntityFramework Update Deletes Record - asp.net-mvc-4

I'm developing a MVC4 app using .Net4.5, EF5 and MSSQL 2008R2. I'm using Db Context and autogenerated entity classes/models and the Unit of Work pattern.
When I attempt to update a table record through the Edit ActionResult method, the record gets deleted when I data updated was originally null, like adding a middle name for example.
I can follow the object from the ActionResult to the GenericRepository to the UnitOfWork Save() where the db context savechanges() is called. The data is there until then. After that, I'm not sure how to debug it.
So, any help with either debugging or with resolving this would be appreciated. I can successfully create and delete table records. I also tried simply using the db context in the Edit ActionResult with the same delete results. I tested the mapped stored procedure, which updated correctly.
[HttpPost]
public ActionResult Edit(v_Demographics vm)
{
try
{
if (ModelState.IsValid)
{
unitOfWork.DemographicsRepository.Update(vm);
unitOfWork.Save();
//db.Entry(vm).State = EntityState.Modified;
//db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(vm);
}
public class GenericRepository<TEntity> where TEntity : class
{
internal SEntities db = new SEntities();
internal DbSet<TEntity> dbSet;
public GenericRepository(AIPIMSEntities db)
{
this.db = db;
this.dbSet = db.Set<TEntity>();
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
db.Entry(entityToUpdate).State = EntityState.Modified;
}
}
public class UnitOfWork : IDisposable
{
private AIPIMSEntities db = new AIPIMSEntities();
private GenericRepository<v_Demographics> demographicsRepository;
public GenericRepository<v_Demographics> DemographicsRepository
{
get
{
if (this.demographicsRepository == null)
{
this.demographicsRepository = new GenericRepository<v_Demographics>(db);
}
return demographicsRepository;
}
}
public void Save()
{
db.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

As it turns out, the delete error was caused by a MSSQL view. The code here is based upon this tutorial, and works very now.

Related

ASP .NET Entity Framework Core Cannot access a disposed object

Wanting to get into .NET Core, I created a WEB API that takes a file upload and then saves the transactions in the file into a DB table. I'm using .NET Core 2 with Entity Framework Core. I created my context using the example from here.
My problem is that I get the error "System.ObjectDisposedException Cannot access a disposed object" when it tries to save to the context object in my repository. It's a simple stack, so I'm hoping someone can help me out.
My container setup looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<SyncFinContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<ITransactionProcessor, TransactionProcessor>();
services.AddScoped<ITransactionRepository, TransactionRepository>();
}
My DBInitializer which I also got from the link above:
public static class DbInitializer
{
public static async Task Initialize(SyncFinContext context)
{
await context.Database.EnsureCreatedAsync();
// Look for any students.
if (context.Transactions.Any())
{
return; // DB has been seeded
}
var ts = new Transaction[]
{
// insert test data here
};
await context.SaveChangesAsync();
}
}
My DB Context:
public class SyncFinContext : DbContext
{
public SyncFinContext(DbContextOptions<SyncFinContext> options) : base(options)
{
}
public DbSet<Transaction> Transactions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Transaction>().ToTable("Transactions");
}
}
My Controller looks like this:
[Produces("application/json")]
public class TransactionController : Controller
{
ITransactionRepository _transactionRepository { get; set; }
ITransactionProcessor _transactionProcessor { get; set; }
public TransactionController(ITransactionRepository m, ITransactionProcessor p) : base()
{
_transactionRepository = m;
_transactionProcessor = p;
}
// POST: transaction/import
[HttpPost]
public async void Import(List<IFormFile> files)
{
if (files == null || files.Count == 0)
{
throw new FileNotFoundException("No file was received.");
}
// copy file to temp location so that it can be processed
var filepath = Path.GetTempFileName();
using (var stream = new FileStream(filepath, FileMode.Create))
{
await files[0].CopyToAsync(stream);
}
ImportTransactionRequest input = new ImportTransactionRequest
{
FilePath = filepath
};
var transactions = await _transactionProcessor.ReadDocument(filepath);
await _transactionRepository.AddBulk(transactions);
}
}
And my repository looks like this:
public class TransactionRepository : ITransactionRepository
{
// TODO: move the context
private SyncFinContext _context;
public TransactionRepository(SyncFinContext context)
{
_context = context;
}
public async Task AddBulk(List<Transaction> transactions)
{
foreach(var t in transactions)
{
await _context.Transactions.AddAsync(t);
}
_context.SaveChanges();
}
}
For full transparency, the transaction Processor just gets a list of rows from a csv:
public async Task<List<Transaction>> ReadDocument(string filepath)
{
try
{
var ret = new List<Transaction>();
var lines = await File.ReadAllLinesAsync(filepath);
foreach (var line in lines)
{
var parts = line.Split(',');
var tx = new Transaction
{
PostedDate = DateTime.Parse(parts[0]),
TransactionDate = DateTime.Parse(parts[1]),
Description = parts[2],
Deposit = ParseDecimal(parts[3]),
Withdrawal = ParseDecimal(parts[4]),
Balance = ParseDecimal(parts[5])
};
ret.Add(tx);
}
return ret;
}
catch(Exception e)
{
throw;
}
}
I've read where the whole stack must be async in order for the db context instance to be available, and, unless I'm doing it wrong, I seem to be doing that, as you can see above.
My expectations are that AddDbContext() will indeed properly scope the context to be available throughout the stack unless I explicitly dispose of it. I have not found anything to make me think otherwise.
I've tried hard-coding data in my DB Initializer also, as I read that may be a factor, but that does not solve the problem. Not sure what else to try. If someone can give me some ideas I would appreciate it.
The Import() action method needs to have a return type of Task. MVC will await execute an action method with a return type of Task.
Also, probably best to get in the habit of returning an IActionResult on your action methods. The task based equivalent is Task<IActionResult>. This makes your controllers easier to test.
Since the AddBulk(List<Transaction> transactions) method is public async Task, the DbContext will be disposed if any part returns void (not awaited) at any point.
Try changing _context.SaveChanges();
To await _context.SaveChangesAsync();
This would ensure a Task is being returned and not void.
https://stackoverflow.com/a/46308661/3062956
https://learn.microsoft.com/en-us/ef/core/saving/async

ObjectContext disposed when use Dispose in EF

I use Entity Framework on my project and I decided to change standard way, which means use - 'using'.
I write my DB layer for communicate Database, but sometimes return this error, please help me to fix this problem:
An exception of type 'System.ObjectDisposedException' occurred in
EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The ObjectContext instance has been disposed
and can no longer be used for operations that require a connection.
Here is my BaseController Code:
//For Data Accsess
public CBEntities DB
{
get
{
return DataAccsesLayer.DbeEntities;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (DataAccsesLayer.DbeEntities != null)
{
DataAccsesLayer.DbeEntities.Dispose();
DataAccsesLayer.DbeEntities = null;
}
}
base.Dispose(disposing);
}
Data project:
//this part of code is member of Data project which is Data Layer
public static class DataAccsesLayer
{
private static CBEntities _context;
public static CBEntities DbeEntities
{
get
{
if (_context == null)
{
_context = new CBEntities();
return _context;
}
else
{
return _context;
}
}
set { _context = value; }
}
}

Fluent NHibernate - Cascade All Delete Orphan not doing anything on delete

I have two simple classes which reference each other as a one-to-many relationship defined below:
public class Project
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Document> Documents { get; set; }
}
public class Document
{
public virtual int Id { get; set; }
public string FileName { get; set; }
}
And my mappings are defined as:
public class ProjectMapping : ClassMap<Project>
{
public ProjectMapping()
{
Table("Projects");
Id(x => x.Id).Column("Project_Id").GeneratedBy.TriggerIdentity();
HasMany(x => x.Documents)
.Table("Documents")
.KeyColumn("Document_Project_Id")
.Cascade.AllDeleteOrphan()
.Not.KeyNullable();
Map(x => x.Name).Column("Project_Name");
}
}
public class DocumentMapping : ClassMap<Document>
{
public DocumentMapping()
{
Table("Documents");
Id(x => x.Id).Column("Document_Id").GeneratedBy.TriggerIdentity();
Map(x => x.FileName).Column("Document_File_Name");
}
}
Everything seems to be working fine, adding/updating documents and calling session.Save(project) reflects the correct changes in my database, however if I am to delete a document from a list of documents associated with a project and call session.Save(project) the deleted document never gets deleted from the database.
Any ideas why everything else would work except for delete?
EDIT:
My MVC 4 project is set up with Fluent NHibernate as follows:
public class SessionFactoryHelper
{
public static ISessionFactory CreateSessionFactory()
{
var c = Fluently.Configure();
try
{
//Replace connectionstring and default schema
c.Database(OdbcConfiguration.MyDialect.
ConnectionString(x =>
x.FromConnectionStringWithKey("DBConnect"))
.Driver<NHibernate.Driver.OdbcDriver>()
.Dialect<NHibernate.Dialect.Oracle10gDialect>())
.ExposeConfiguration(cfg => cfg.SetProperty("current_session_context_class", "web"));
c.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Project>());
c.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Document>());
}
catch (Exception ex)
{
Log.WriteLine(ex.ToString());
}
return c.BuildSessionFactory();
}
}
public class MvcApplication : System.Web.HttpApplication
{
public static ISessionFactory SessionFactory { get; private set; }
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
SessionFactory = SessionFactoryHelper.CreateSessionFactory();
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
protected void Application_EndRequest(object sender, EventArgs e)
{
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();
}
}
My repository is defined as follows:
public class Repository<T> : IRepository<T>
{
public virtual ISession Session
{
get { return MvcApplication.SessionFactory.GetCurrentSession(); }
}
public T FindById(int iId)
{
return Session.Get<T>(iId);
}
public void Save(T obj)
{
using (var transaction = Session.BeginTransaction())
{
try
{
Session.Save(obj);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Log.WriteLine(ex.ToString());
}
}
}
public T SaveOrUpdate(T obj)
{
using (var transaction = Session.BeginTransaction())
{
try
{
Session.SaveOrUpdate(obj);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Log.WriteLine(ex.ToString());
}
}
return obj;
}
public T Update(T obj)
{
using (var transaction = Session.BeginTransaction())
{
try
{
Session.Update(obj);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Log.WriteLine(ex.ToString());
}
}
return obj;
}
}
I have 2 Actions defined in my ProjectsController as follows:
private IRepository<Project> repository;
public ProjectsController()
{
repository = new Repository<Project>();
}
public ActionResult Edit(int iId)
{
Project project = repository.FindById(iId);
if (project == null)
return HttpNotFound();
return View(project);
}
[HttpPost]
public ActionResult Edit(Project project)
{
project = repository.Update(project);
return View(project);
}
If I am to delete a document in my first action (without HttpPost):
project.Documents.RemoveAt(0);
repository.Update(project);
The correct row is removed from the database.
However, if I am to do the very same in the action with HttpPost attribute, the row is never removed.
Also I should note that if I add a document to project.Documents in the action with HttpPost attribute, repository.Update(project) successfully adds the row with the correct foreign key reference to the project. This is only failing when I remove a document.
Have you tried adding .Inverse to your HasMany mapping?
Also, I'm not familiar with the Not.KeyNullable. I don't think it's necessary here.
The cascade setting seems to be correct. The issue mentioned could be elsewhere:
however if I am to delete a document from a list of documents associated with a project
Suspected to me is a session flush mode, or missing explicit call to update parent entity Project, which was previously detached. Assure:
First, that the Flush() was called. In case that project instance is still kept in Session, the default behavior of flushing could be changed. (e.g. session.FlushMode = FlushMode.Never; or Commit without having transaction...)
// 1) checking the explicit Flush()
project.Documents.Remove(doc);
Session.Flush(); // this will delete that orphan
The second could be evicted project instance, needing the explicit update call
// 2) updating evicted project instance
project.Documents.Remove(doc);
Session.Update(project);
//Session.Flush(); // Session session.FlushMode = FlushMode.Auto
The setting inverse will in this case (only) help to reduce one trip to database with UPDATE statement, resetting reference to doc.Project = null, then executing DELETE.

NHibernate Session/Transaction Woes

I'm trying to use a session-per-request pattern and I'm having trouble with getting a record right after it's been saved. The reason for doing that being that I need to get the records that the foreign keys relate to.
Some (simplified) code:
// UnitOfWork Attribute
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
SessionFactory.Begin();
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Exception == null) {
try {
SessionFactory.Commit();
} catch {
SessionFactory.Rollback();
throw;
}
} else {
SessionFactory.Rollback();
throw filterContext.Exception;
}
}
// Service layer
public void Save(Form form)
{
_repository.Save(form);
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
// Repository
public void Save(Form form)
{
var session = SessionFactory.CurrentSession;
session.SaveOrUpdate(form);
}
The problem is that when I try to get the record, the transaction hasn't yet been committed, so it just gives me what's already in the session. Am I just going to have to commit the transaction after saving, and open a new transaction for getting it?
Thanks
Update:
I've implemented the Agathas Storefront way of doing things, giving the service layer control over the transactions, i.e.:
public class UnitOfWork : IUnitOfWork
{
public void Commit()
{
var session = SessionFactory.CurrentSession;
using (ITransaction transaction = session.BeginTransaction())
{
try {
transaction.Commit();
} catch {
transaction.Rollback();
throw;
}
}
}
public void Clear()
{
var session = SessionFactory.CurrentSession;
session.Clear();
}
}
Then in the service layer:
public void SaveForm(Form form)
{
_repository.Save(form);
_uow.Commit();
_uow.Clear();
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
Update 2
OK, I think I've found a suitable solution. I've gone back to the transaction-per-request pattern, and after saving the form I'm now flushing it and then evicting the form from the session in order to force NH to get it from the DB.
// Service layer
public void SaveForm(Form form)
{
_repository.Save(form);
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
// Repository
public void Save(Form form)
{
var session = SessionFactory.CurrentSession;
session.SaveOrUpdate(form);
session.Flush();
session.Evict(form);
}
Until you flush the session or reduce your transaction scope you won't have an ID because nhibernate will not have inserted the record.
One option, change your id strategy from an identity generated in the database to one that is managed by NHibernate (see options at http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-id-generator) or another option change your Session's flushmode to FlushMode.Auto.

Http-Post edit action calling ISession.SaveOrUpdate(obj) creates new entity

I'm creating an Asp.Net MVC 3 application with NHibernate as my ORM. In my my edit action method I call the Save method in my DatabaseAccessObject class, but instead of updating the object it creates a new one. I can't figure out why.
Here's the code for the method that returns my configured SessionFactory, and my global.asax.cs file where I'm storing the SessionFactory:
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString("Server=(local);Database=WebApplicationPbiBoard;Trusted_Connection=True;"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<WebApplicationPbiBoard.Models.ScrumModels_Mappings.PbiMap>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.CurrentSessionContext("web")
.BuildSessionFactory();
}
public class MvcApplication : System.Web.HttpApplication
{
public static ISessionFactory SessionFactory { get; private set; }
protected void Application_Start()
{
//my additions
SessionFactory = NHibernateConfigurator.CreateSessionFactory();
}
protected void Application_OnEnd()
{
SessionFactory.Dispose();
}
protected void Application_BeginRequest()
{
ISession session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
protected void Application_EndRequest()
{
CurrentSessionContext.Unbind(SessionFactory);
}
}
Here's the relevant snippet from my DataAccessObject, which simply wraps NHibernate CRUD operations:
public class DatabaseAccessObject<T> where T : class
{
private readonly ISession session = MvcApplication.SessionFactory.GetCurrentSession();
private ISession Session { get { return session; } }
public T Save(T obj)
{
ITransaction transaction = null;
try
{
transaction = Session.BeginTransaction();
Session.SaveOrUpdate(obj);
transaction.Commit();
return obj;
}
catch (Exception ex)
{
if (transaction != null && transaction.IsActive)
transaction.Rollback();
throw;
}
}
And finally, here's the code for my Http-Post edit method:
private readonly DatabaseAccessObject<Sprint> db = new DatabaseAccessObject<Sprint>();
private DatabaseAccessObject<Sprint> Db { get { return db; } }
[HttpPost]
public ActionResult Edit(Sprint editedSprint)
{
if (ModelState.IsValid)
{
Db.Save(editedSprint);
return RedirectToAction("Index");
}
else
return View(editedSprint);
}
Any help would be appreciated.
The object you're saving probably hasn't got the Id set on it.
SaveOrUpdate does one of two things:
- Update() if the Id is set.
- Save() if the Id is not set.
refer to the docs:
http://www.nhforge.org/doc/nh/en/index.html