0
votes

I'm having a hard time using NHibernate with MVC 5. The problem is that NHibernate needs the Id property of my class to be private or protected, but then the MVC model binder can't set the Id on an existing entity, so NHibernate views it as a new record, and inserts it instead of editing it.

public interface IEntity
{
    int Id {get; }
}

public class User : IEntity
{
    public virtual int Id {get; protected set;}
    public virtual string Email {get; set;}
    public virtual DateTime? LastLogin {get; set;}
}

This works just fine

public JsonResult SaveUser(User user)
{
    var userModel = new UserModel();
    if(userModel.SaveUser(user)) return Json(new {success = true});
    return Json(new {success = false});
}

This fails, because Id has a protected set, which is necessary for an NHibernate entity. So, since the MVC model binder can't set Id, NHibernate views it as a new entity.

public JsonResult EditUser(User user)
{
    var userModel = new UserModel();
    userModel.EditUser(user);
}

So it looks like I need to basically duplicate my entity classes as view models, but this seems really tedious (not to mention anti-DRY). The only difference between the two classes would be the view model class would have a publicly settable Id. I can make this a little less tedius by using AutoMapper, but it still looks like I'd need to duplicate my classes as view models. It does have a feature to map from dynamic, but that still wouldn't allow me to properly set the Id for an existing entity.

So, am I missing something here? Is there a way I can do this without having to make two almost identical classes?

4

4 Answers

3
votes

I would recommend having MVC specific view models because NHibernate models usually implement dynamic proxies and cannot be serialized to XML/JSON/etc. They can be used to populate data in views, but will cause problems if some of the properties lazy load data.

View models can be close to the data model classes but usually they are more specific to what needs to be shown on the screen.

1
votes

NHibernate does not need the Id to be private or protected, where did you get that idea? Assuming it's an auto incrementing identity, it's a good practice to make it private but it's not required. Using view models is optional but a very good practice both from development and application security viewpoints. For example, using view models can avoid mass assignment attacks: http://odetocode.com/blogs/scott/archive/2012/03/12/complete-guide-to-mass-assignment-in-asp-net-mvc.aspx

0
votes

Unless you are doing something fancy in the model or in view model or using lazy loading, you don't necessarily need to create view models. I always use the NHibernate models classes and if I need something special that is not the exact Model then I create separate view models. For example Login is performed with view model, since some crystallographic and security options are there for the password. Other than that I use the help of default ASP.Net MVC model binder and my edit functions usually look like this -

The view -

@model ConsoleUser
<h2>Create</h2>

@Html.HiddenFor(x=>Model.Id)
......
@Html.EditorFor(x=>Model.UserName)

The model -

public class ConsoleUser : .... // some base IDomainEntity inheritence
{
    #region Account
    //account information
    public virtual String UserName { get; set; }
    public virtual String Password { get; set; }
    public virtual DateTime? LastLogin { get; set; }
    #endregion
}

GET -

    /// <summary>
    /// Edits this instance.
    /// </summary>
    /// <returns></returns>
    [ValidateInput(false)]
    public virtual async Task<ActionResult> Edit(string id)
    {
        var item = (await _domainService.Get(x => x.Id == id)).FirstOrDefault();
        if (item.IsNotNull())
            return View(item);
        throw new CodedException("E.102.3");
    }

POST for update -

    /// <summary>
    /// 
    /// </summary>
    /// <param name="Id">The id.</param>
    /// <param name="form">The form.</param>
    /// <returns></returns>
    [HttpPost, ValidateInput(false)]
    public virtual async Task<ActionResult> Edit(string Id, FormCollection form)
    {
        var model = (await _domainService.Get(x=>x.Id == Id)).FirstOrDefault(); //load the entity from db to update, unless we do this, Nhibernate will consider a new entity
        if (TryUpdateModel(model))
        {
            await _domainService.Save(model);
            return RedirectToAction("Details", new { id = Id, ....});
        }
        return View(model);
    }

Notice that I fetched the entity from repository _domainService.Get(x=>x.Id == Id)).FirstOrDefault() before I am applying TryUpdateModel(model), this is very important. Because this where Nhibernate know that I am trying to update it not creating a new one. The create method looks like this and notice the difference -

   [HttpPost, ValidateInput(false)]
    public virtual async Task<ActionResult> Create(FormCollection form)
    {
        var model = ModelFactory.Instance.Get<T>(); //.... instead of getting from repository I am creating a new model
        if(TryUpdateModel(model))
        {
            await _domainService.Save(model);
            return View("Details", model);
        }
        return View(model);
    }

And in the save method it looks like this -

    public static void Save<T>(T entity, ISession _session = null, IList<Exception> errors = null) where T : IDomainEntity
    {
        if (_session == null)
        {
            using (var session = OpenEngineSession())
            using (var trans = session.BeginTransaction())
            {
                try
                {
                    session.SaveOrUpdate(entity);
                    trans.Commit();
                }
                catch (Exception e)
                {
                    errors = errors ?? new List<Exception>();
                    errors.Add(e);
                    trans.Rollback();
                }
            }
        }else
        {
            _session.SaveOrUpdate(entity);
        }
    }

That is all. NHibernate has a built in functionality, in the sense that if you use get the object through nhibernate it will keep a references and will automatically run update query instead of insert.

0
votes

To me ViewModels play an very important role in the MVC application development. In the tables we store data in relational manner , but in the application we surely need another set of models that stay close to view and Business Logic, specificially when your domain is complex. we can slice and dice the via viewmodels easily, we traverse through the relationships and build the view models as we require.