ExecuteSqlCommand automatically calls SaveChanges() when I don't want it to - asp.net-mvc

In my ASP.NET MVC project using Entity Framework 5, I have one operation that I want to perform with a back-end stored procedure, because it affects a number of different entities. The nature of the stored procedure is that I don't need or want it to be tracked by EF; I call it from one entity, and then that entity's values are copied into a different table in the database. The problem is that I can't seem to stop EF from calling SaveChanges(), even though I don't want it to.
Here's a truncated version of my repository code:
public class SubmissionRepository : IRepository<RebateHeaderSubmission>
{
private readonly HbaRebatesContext _db;
public SubmissionRepository()
{
_db = new HbaRebatesContext();
}
public void Add(RebateHeaderSubmission entity) . . .
public RebateHeaderSubmission GetById(int id) . . .
public IQueryable<RebateHeaderSubmission> GetAll() . . .
public void Update(RebateHeaderSubmission entity) . . .
public void Save()
{
_db.SaveChanges();
}
public void AcceptSubmission(RebateHeaderSubmission entity)
{
var param = new SqlParameter("Rebate_Entry_Id", entity.Id);
_db.Database.ExecuteSqlCommand("EXEC dbo.AcceptSubmission #Id", param);
}
}
}
And here's a slightly-truncated version of the code that calls AcceptSubmission():
public ActionResult Edit(RebateHeaderSubmission editedRecord, bool acceptSubmission = false)
{
if (editedRecord.Id > 0 && ModelState.IsValid)
{
_db.Update(editedRecord);
}
else
{
return PartialView("_Edit", editedRecord);
}
_db.Save();
if (acceptSubmission == false)
{
return RedirectToAction("Single", new { id = editedRecord.Id });
}
else
{
var repo = _db as SubmissionRepository;
if (repo != null)
{
repo.AcceptSubmission(editedRecord);
}
return RedirectToAction("Single", new { id = editedRecord.Id });
}
}
But in AcceptSubmission(), as soon as _db.Database.ExecuteSqlCommand is called, the execution jumps up to the Save() method and I get a DbUpdateConcurrencyException saying that no rows were updated.
I've tried updating the method to detach the entity, but it hasn't done any good. This was the attempt I was most hopeful about:
public void AcceptSubmission(RebateHeaderSubmission entity)
{
try
{
var param = new SqlParameter("Rebate_Entry_Id", entity.Id);
var context = ((IObjectContextAdapter)_db).ObjectContext;
context.SaveChanges();
context.Detach(entity);
_db.Database.ExecuteSqlCommand("EXEC dbo.AcceptSubmission #Rebate_Entry_Id", param);
}
catch (DbUpdateConcurrencyException)
{
//Do nothing for now.
}
}
The execution still jumps up to Save(), and my catch block doesn't even work, because the exception happens in the Save() method instead. Is there anyway to get EF to stop calling SaveChanges()?
UPDATE: Two small clarifications. First, the stored procedure I'm calling is being executed, and it works. So AcceptSubmission() is working. Second, when I say that execution "jumps" to Save(), what actually happens is that as soon as I've run _db.Database.ExecuteSqlCommand, an exception is raised, but it's raised in the Save() method. I don't actually hit the end bracket for AcceptSubmission(), nor the opening bracket for Save().

It turns out I was mis-diagnosing the problem. EF wasn't "jumping" to SaveChanges. There were actually two active threads, caused by an error with Unobtrusive JavaScript, such as described in this answer. Fixing the error in my JavaScript fixed the problem.

Related

How to permanently delete values from the database

I have a method that i call from a javascript, the method suppose to delete the records permanently but it does not go in to the method if the method has DataContext db = new DataContext();, it gives the error Internal Server Error
public void PermanantlyDeleteComment(GetCommentInput input)
{
DataContext db = new DataContext();
//Follow by the code to delete the comment
}
If i comment out DataContext db = new DataContext(); the breakpoint does go in.
I think the problem is with the datacontext but i do know know where
Here is the datacontext
public DataContext() : base("name=Default")
{
this.Configuration.AutoDetectChangesEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
}
I'm using DataContext because abp boilerplate does not want to permanently delete, only soft delete, if you have a way that i can hard delete with boilerplate please let me know.
Answered in this topic: https://forum.aspnetboilerplate.com/viewtopic.php?p=6180#p6193
You can override CancelDeletionForSoftDelete method in your DbContext and prevent cancellation conditionally.
So, like this:
protected override void CancelDeletionForSoftDelete(EntityEntry entry)
{
if (IsSoftDeleteFilterEnabled)
{
base.CancelDeletionForSoftDelete(entry);
}
}
Usage:
public void PermanantlyDeleteComment(GetCommentInput input)
{
using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.SoftDelete))
{
// The code to delete the comment
}
}
I found out that the DataContext was correct, is just i had different EntityFramework version on my Database Library (which has DataContext.cs) and my Web Library

Cannot update a record in Entity Framework 5

I have the following method in the repository and I need to update an Entity.
Unfortunately I cannot update the record (no errors message).
Any idea what am I doing wrong?
public void Update(GaLocation entity)
{
context.GaLocations.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
}
This is how I update entries:
public void Update(GaLocation entity)
{
GaLocation gl = context.GaLocations.Where(h=>h.id == entity.id).FirstOrDefault();
gl.val1 = entity.val1;
gl.val2 = entity.val2;
//etc
context.SaveChanges();
}
I've tried using Attach, but I've run into issues with that too. I'm sure it works, but this way worked better for me.

NHibernate session is closed when refreshing page

This is another strange problem I've encountered this days!!! I've created and MVC 4 app using nhibernate. and added a filter attribute named [LoggingNHibernateSessionAttribute] on my HomeController which manages session for each action. I've followed 'ASP.NET MVC4 and the Web API published by Apress'.
public class LoggingNHibernateSessionAttribute : ActionFilterAttribute
{
private readonly IActionLogHelper _actionLogHelper;
private readonly IActionExceptionHandler _actionExceptionHandler;
private readonly IActionTransactionHelper _actionTransactionHelper;
public LoggingNHibernateSessionAttribute()
: this(WebContainerManager.Get<IActionLogHelper>(),
WebContainerManager.Get<IActionExceptionHandler>(),
WebContainerManager.Get<IActionTransactionHelper>())
{
}
public LoggingNHibernateSessionAttribute(
IActionLogHelper actionLogHelper,
IActionExceptionHandler actionExceptionHandler,
IActionTransactionHelper actionTransactionHelper)
{
_actionLogHelper = actionLogHelper;
_actionExceptionHandler = actionExceptionHandler;
_actionTransactionHelper = actionTransactionHelper;
}
public override void OnActionExecuting(ActionExecutingContext actionExectingContext)
{
_actionLogHelper.LogEntry(actionExectingContext.ActionDescriptor);
_actionTransactionHelper.BeginTransaction();
}
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
_actionTransactionHelper.EndTransaction(actionExecutedContext);
_actionTransactionHelper.CloseSession();
_actionExceptionHandler.HandleException(actionExecutedContext);
_actionLogHelper.LogExit(actionExecutedContext.ActionDescriptor);
}
}
ActionTransactionHelper
public class ActionTransactionHelper : IActionTransactionHelper
{
private readonly ISessionFactory _sessionFactory;
private readonly ICurrentSessionContextAdapter _currentSessionContextAdapter;
public ActionTransactionHelper(
ISessionFactory sessionFactory,
ICurrentSessionContextAdapter currentSessionContextAdapter)
{
_sessionFactory = sessionFactory;
_currentSessionContextAdapter = currentSessionContextAdapter;
}
public void BeginTransaction()
{
var session = _sessionFactory.GetCurrentSession();
if (session != null)
{
session.BeginTransaction();
}
}
public bool TransactionHandled { get; private set; }
public void EndTransaction(ActionExecutedContext filterContext)
{
var session = _sessionFactory.GetCurrentSession();
if (session == null) return;
if (!session.Transaction.IsActive) return;
if (filterContext.Exception == null)
{
session.Flush();
session.Transaction.Commit();
}
else
{
session.Transaction.Rollback();
}
TransactionHandled = true;
}
public bool SessionClosed { get; private set; }
public void CloseSession()
{
if (_currentSessionContextAdapter.HasBind(_sessionFactory))
{
var session = _sessionFactory.GetCurrentSession();
session.Close();
session.Dispose();
_currentSessionContextAdapter.Unbind(_sessionFactory);
SessionClosed = true;
}
}
}
when run the app, I can save an entity in the dataBase. but when I hit refresh button and exception thrown indication session is closed.
I don't know why this happens. (I searched and find this NHibernate throwing Session is closed but couldn't solve my problem).
in my NinjectConfigurator I added inRequestScope() to all of injections but no answer. I checked when I refresh the page session will be opened. but I donnow why it say session is closed?!
UPDATE:
when I first run the app. I can create a new member. but when I hit the refresh button, the session will be closed unexpectedly!!
first run:
everything works well
after hitting refresh button:
a new session bind to the current context.
the new session will be injected the repository (session is open)
the ActionTransactionHelper calls beginTransaction()
4- customMembership createUser (....) called
5- but when the _userRepositoy.save(user)called in the repository session is closed!!!!
note:but when still endTransaction and closeSession isn't called. but how session is closed?
if I comment closeSession() in onActionExecute(). session alway is open and everything woks well if refresh the page.
I checked a lot and tried different way I knew. it only happens when for the second time I want to do CRUD operations with my customMembership.
for other entities it works like a charm!
I have upoaded my sample code. for testing just create and empty database and change connection string. then go to localHost:*****/api/categories (user and pass doesn't required)
Download sample project:
Size: 47 MB
https://www.dropbox.com/s/o63wjng5f799fii/Hashem-MVC4ServicesBook.rar
size: 54 MB
Zip Format: https://www.dropbox.com/s/smrsbz4cbtznx1y/Hashem-MVC4ServicesBook2.zip
A very important thing here, could be the nature of the NHibernate. The NHibernate and its Session are in the ASP.NET MVC living longer, then could be expected. I mean not only inside of the
ActionExecuting (Controller Action starts)
ActionExecuted (the View or Redirect is called)
Session in fact must live also through the phase of rendering. Because, we could load some proxy in the "Action()" but its collection, could be lazily loaded only during the View rendering. So even in these phases Session must be opened (the same Session from the request begining)
ResultExecuting (the proxy could start to be loaded only here)
ResultExecuted (almost all is done, let's close the session)
Other words... keep the session opened throught the complete Request. From authorization untill the content is rendered.
NOTE: Anohter hint, just to be sure that all is ok, I am using this scenario (maybe you do as well):
Client FORM is about to send the data to server. The method is POST, the Action is Update()
Sent FORM is coming to server, Action Update() is triggerred - all the transactions stuff is in place (as described above)
Once NHibernate persists the data into DB, the Update() action ends, and is redirected to action
Detail() if all is ok or
Edit() if something goes wrong
The users Browser was redirected to action Detail or Edit. So if user does REFRESH, the Detail or Edit is refreshed. The Update() is not called at all (it is a POST method)
In fact, the step 1. was one of the Actions Detail or Edit. In this case, we would face this issue already...
You have this error since Asp.Net MVC does not create a new instance of LoggingNHibernateSessionAttribute every request. It creates a new instance when you request an action first time and then uses this instance in the future.
The behaviour is the following:
First invocation of Post -> new instance of 'LoggingNHibernateSession' is created
First invocation of Put -> another one instance of 'LoggingNHibernateSession' is created
Second invocation of Put -> instance of 'LoggingNHibernateSession' from previous step is used
First invocation of Delete -> another one instance of 'LoggingNHibernateSession' is created
[LoggingNHibernateSession]
public JsonResult Post(Dto data)
{
/* ... */
}
[LoggingNHibernateSession]
public JsonResult Put(int id, Dto data)
{
/* ... */
}
[LoggingNHibernateSession]
public JsonResult Delete(int id)
{
/* ... */
}
It can be solved using Func<IActionLogHelper> instead of IActionLogHelper in the constructor. An instance of IActionLogHelper can be initialised within OnActionExecuting method.
public class LoggingNHibernateSessionAttribute : ActionFilterAttribute
{
/* your code */
private readonly Func<IActionTransactionHelper> _getActionTransactionHelper;
private IActionTransactionHelper _actionTransactionHelper;
public LoggingNHibernateSessionAttribute()
: this(WebContainerManager.Get<IActionLogHelper>(),
WebContainerManager.Get<IActionExceptionHandler>(),
() => WebContainerManager.Get<IActionTransactionHelper>())
{
}
public LoggingNHibernateSessionAttribute(
IActionLogHelper actionLogHelper,
IActionExceptionHandler actionExceptionHandler,
Func<IActionTransactionHelper> getActionTransactionHelper)
{
_actionLogHelper = actionLogHelper;
_actionExceptionHandler = actionExceptionHandler;
_getActionTransactionHelper = getActionTransactionHelper;
_actionTransactionHelper = null;
}
public override void OnActionExecuting(ActionExecutingContext actionExectingContext)
{
_actionTransactionHelper = _getActionTransactionHelper();
_actionLogHelper.LogEntry(actionExectingContext.ActionDescriptor);
_actionTransactionHelper.BeginTransaction();
}
/* your code */
}

Orchard CMS ContentManager.New<>() Specified Cast Was Invalid

I am in the early stages of developing a new module.
Much of it is laid out in terms of the models etc. Also have the migrations all set up and my database now has the tables for my module.
I am encountering the following error when calling ContentManager.New<myPart> and would like some help please.
Error is this:
An unhandled exception has occurred and the request was terminated. Please refresh the page. If the error persists, go back
Specified cast is not valid.
System.InvalidCastException: Specified cast is not valid.
at Orchard.ContentManagement.ContentCreateExtensions.New[T]
(IContentManager manager, String contentType)
The chunk of code that fires the exception is this:
public static T New<T>(this IContentManager manager, string contentType) where T : class, IContent {
var contentItem = manager.New(contentType);
if (contentItem == null)
return null;
var part = contentItem.Get<T>();
if (part == null)
throw new InvalidCastException();
return part;
}
Here are the various parts to my module that are related to the operation i am struggling with:
ContentPart
public class GoogleMapsSettingsPart : ContentPart<GoogleMapsSettingsPartRecord>
{
public string ApiKey {
get { return Record.ApiKey; }
set { Record.ApiKey = value; }
}
}
ContentPartRecord
public class GoogleMapsSettingsPartRecord : ContentPartRecord
{
public virtual string ApiKey { get; set; }
}
Handler
public GoogleMapsSettingsPartHandler(IRepository<GoogleMapsSettingsPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
Migration for this table
// Settings Table
SchemaBuilder.CreateTable("GoogleMapsSettingsPartRecord", table => table
.ContentPartRecord()
.Column("ApiKey", DbType.String, c => c.WithLength(60))
);
Some of the code from the controller for this model etc
public AdminController(IContentManager contentManager, IShapeFactory shapeFactory, IServiceLocatorService serviceLocatorService, INotifier notifier)
{
_contentManager = contentManager;
_serviceLocatorService = serviceLocatorService;
_notifier = notifier;
Shape = shapeFactory;
T = NullLocalizer.Instance;
}
/// <summary>
/// Display Settings
/// </summary>
/// <returns></returns>
public ActionResult Settings()
{
var settings = _serviceLocatorService.GoogleMapsSettings;
var editor = CreateSettingsEditor(settings);
var model = _services.ContentManager.BuildEditor(settings);
return View((object)model);
}
Finally - the Services where my call throws this exception
private GoogleMapsSettingsPart _settings;
public GoogleMapsSettingsPart GoogleMapsSettings
{
get {
if (_settings == null)
{
_settings = _contentManager.Query<GoogleMapsSettingsPart, GoogleMapsSettingsPartRecord>().List().FirstOrDefault();
if (_settings == null)
{
_settings = _contentManager.New<GoogleMapsSettingsPart>("GoogleMapsSettings");
}
}
return _settings;
}
}
The actual line where the exception happens is _settings = _contentManager.New<GoogleMapsSettingsPart>("GoogleMapsSettings");
I have tried all sorts of stuff in place of "GoogleMapsSettings" though nothing is working.
I'm pretty sure at this point it's something simple, though it's avoiding me..My limited knowledge of Orchard is stumping me
Any help would be appreciated :)
The exception is thrown because your content type does not have the part you specified to get.
_contentManager.New<GoogleMapsSettingsPart>("GoogleMapsSettings");
This method call creates a new content item of type GoogleMapSettings and gets the content item as a GoogleMapsSettingsPart. However, it seems that GoogleMapSettings content type does not have a GoogleMapsSettingsPart. That's why the exception gets thrown here.
var part = contentItem.Get<T>();
if (part == null)
throw new InvalidCastException();
You must either attach the part dynamically to your content type or do it in a migration (or manually in the admin, but that's not a good idea). Your migration should look like this.
this.ContentDefinitionManager.AlterTypeDefinition("GoogleMapsSettings",
alt => alt
.WithPart("GoogleMapsSettingsPart");
Ok, so I fixed it...
My understanding of how Orchard works is still very much in the learning stages.
for this particular operation I didn't want to have a content type in the admin - though not sure why after adding the ContentType it still didn't work...
anyway, adding the lines below to my handler took care of the rest. I believe it's actually creating a temporary type so one isn't needed in the system.
public GoogleMapsSettingsPartHandler(IRepository<GoogleMapsSettingsPartRecord> repository)
{
Filters.Add(new ActivatingFilter<GoogleMapsSettingsPart>("GoogleMapsSettings"));
Filters.Add(StorageFilter.For(repository));
Filters.Add(new TemplateFilterForRecord<GoogleMapsSettingsPartRecord>("GoogleMapsSettings", "Parts/GoogleMapsSettings"));
}
I'v got the same error, but in my case it was everything ok with migration class.
The reason was unlucky merge, which deleted my driver class of my part.
Just look at this code of Activating method of ContentPartDriverCoordinator class. In my case there was no partInfo for my content part and resulted part became ContentPart, so casting throws an exception
var partInfos = _drivers.SelectMany(cpp => cpp.GetPartInfo()).ToList();
foreach (var typePartDefinition in contentTypeDefinition.Parts) {
var partName = typePartDefinition.PartDefinition.Name;
var partInfo = partInfos.FirstOrDefault(pi => pi.PartName == partName);
var part = partInfo != null
? partInfo.Factory(typePartDefinition)
: new ContentPart { TypePartDefinition = typePartDefinition };
context.Builder.Weld(part);
}

"An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."

When I run following code:
public ActionResult Complete()
{
try
{
VeriTabanDataContext db = new VeriTabanDataContext();
db.Persons.InsertOnSubmit(_person);
db.SubmitChanges();
return View(_person);
}
catch (Exception ex)
{
return RedirectToAction("Error", ex);
}
}
I'm getting following Exception, on SubmitChanges();
"An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."
Here "_person" object is taken from Session and is a good standing. Note: _person is a result of multistep wizard and this is the place where I add new Person object to the DB.
My Person table has 9 relations and it's not ok for me to add version column for each of them as is suggested by some geeks around
I've investigated this problem a lot and spend 2 days on it and still couldn't solve it. Some of the workarounds that other suggest don't solve my problem, and others seem to be just dirty workaround. Do you experts have a good solution for this problem, considering that Person class has many relations and also it isn't ok to add a column to the tables.
I also want to note that I've tried to use 'db.Persons.Attach(_person) ' and setting db.DeferredLoadingEnabled = false; THis time I'm not getting any Exception but data is NOT saved to DB
I create a class called applicationController which derives from Controller. Then i make all of my controller classes derive from this. The applicationController class has a constrcutor which creates a new instance of my repository (or datacontext in your instance) which is used throughout the application:
public class ApplicationController : Controller
{
private VeriTabanDataContext _datacontext;
public ApplicationController() : this(new VeriTabanDataContext())
{
}
public ApplicationController(VeriTabanDataContext datacontext)
{
_datacontext = datacontext;
}
Public VeriTabanDataContext DataContext
{
get { return _datacontext; }
}
}
Then you can use this in all of your controllers
public class MyController : ApplicationController
{
public ActionResult Complete()
{
DataContext.Persons.InsertOnSubmit(_person);
DataContext.SubmitChanges();
return View(_person);
}
}
Not on my PC with VS installed at the moment so not tested this code....
Hope this resolves the issue -Mark
Can you do the following:
var foundPerson = db.Person.FirstOrDefault( p => p.Id == _person.Id);
if(foundPerson == null)
{
db.InsertOnSubmit(_person);
}else{
Mapper.Map(_person,foundPerson);
}
db.SubmitChanges();
return View(_person);
Where I have used AutoMapper to map from one entity to another. To do this add the reference to AutoMapper to your project and in your start up code for the application you will need to configure your mappings, for the above to work you would need:
Mapper.CreateMap<Person, Person>().ForMember(src => src.Id, opt => opt.Ignore());

Resources