Don’t get me wrong, I want them to get saved. But I always thought I had to call Session.SaveOrUpdate(x); to do it. But here is what is going on.
Basic Question
Objects are getting ‘saved’ despite me never calling a save method.
Details
Objects are mapped through Fluent nHibernate , nothing special here.
I am mapping my ISessionFactory and ISession through Ninject in ASP.NET MVC like this.
Bind<ISessionFactory>()
.ToMethod(c => CreateSessionFactory())
.InSingletonScope();
Bind<ISession>()
.ToMethod(c => OpenSession())
.InRequestScope()
.OnActivation(session =>
{
System.Diagnostics.Debug.WriteLine("Creating Session");
session.BeginTransaction();
session.FlushMode = FlushMode.Commit;
})
.OnDeactivation(session =>
{
if (session.Transaction.IsActive)
{
try
{
System.Diagnostics.Debug.WriteLine("Disposing Session");
session.Transaction.Commit();
}
catch
{
session.Transaction.Rollback();
}
}
});
Then, when I want to perform something, I have either a simple method on the domain class, or an extension method (simply for the fact that it is easier to organize), like this.
class Member {
public virtual int Id { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
}
Then I pepper in some extension methods for flavor..
using System;
using System.Linq;
namespace System.Linq
{
public static class MembershipExtensions
{
public static void ChangePassword(this Membership.Member member, string password)
{
member.Password = System.Security.Cryptography.Hashing.ComputeHash(password, "SHA512", null);
}
public static bool VerifyPassword(this Membership.Member member, string password)
{
return System.Security.Cryptography.Hashing.VerifyHash(password, "SHA512", member.Password);
}
}
}
So far, all is good in the land of Ooo. Now here is where stuff gets wonky.
[HttpPost]
public ActionResult Password(ViewModels.MemberEditPasswordViewModel model)
{
if (ModelState.IsValid)
{
// attempt to perform the password change on the current member.
var member = User.AsMember(); // retrieve the current user
if (member.VerifyPassword(model.Current)) // verify the current password
member.ChangePassword(model.Password);
//if (Bus.Update(member) != null) {
// return RedirectToAction("Index", "Home");
//}
//else {
// ModelState.AddModelError("", "The original password is incorrect.");
//}
}
// If we got this far, something failed, redisplay the form
// with the errors listed and give them a chance to try again.
return View(model);
}
Here’s the crazy part. This works. Notice what is so strange? I never call any kind of SaveOrUpdate to the Member object. I commented out my Update code just to test it out.
Additional Data
This is information that may be relevant to the question, but I didn’t want to bog down everything with extra code.
For those looking for my AsMember() method, here it is as well. I’m trying to give all the data I can. I like this behavior, but I’m not sure if it is normal, or correct…
using System;
using System.Linq;
using System.Linq.Expressions;
namespace System.Web.Mvc
{
public static class MembershipProviderExtensions
{
public static Membership.Member AsMember(this System.Security.Principal.IPrincipal user)
{
// if the user is not authenticated, then we should
// not have made it this far, and error out gracefully.
if (!user.Identity.IsAuthenticated)
return null;
// get the service bus from the locator
var bus = DependencyResolver.Current.
GetService<IServiceBus>();
// return the queried result
return bus.Request<Membership.Queries.CurrentMember>(user);
}
}
}
The current member is simply fetched through a query object, sent to the IServiceBus. (This is a custom service bus, not an open source project one).
/// <summary>
/// Resolves the currently authenticated <see cref="Member"/>.
/// </summary>
public class CurrentMember : IRequestFor<System.Security.Principal.IPrincipal, Member>
{
private readonly ISession session;
public CurrentMember(ISession session) {
this.session = session;
}
public Member Reply(System.Security.Principal.IPrincipal Model)
{
// if the user is not authenticated, then we should
// not have made it this far, and error out gracefully.
if (!Model.Identity.IsAuthenticated)
return null;
return session.Query<Member>()
.Where(context => context.Email == Model.Identity.Name)
.Take(1)
.SingleOrDefault();
}
}
Here is the Request<T> implementation..
public dynamic Request<T>(dynamic message)
{
dynamic implementation = kernel.Get<T>();
return implementation.Reply(message);
}
You should set the session to FlushAction.Never
Objects that are loaded using the session will flush at the disposable.
Moreover, there is the bonus performance boost since the session does not need to check the state of all the loaded objects.
This is significant in scenarios where a lot of data is loaded.