2
votes

I have created an MVC 4 and use NHibernate for persisting the model and mapped it with fluent nhibernate. the entity has a "Name" property and mapped like in this way:

 Map(x => x.Name).Not.Nullable().Length(100); 

I have created a table for viewing the list of objects and let me to edit, view object details and delete them. when I delete the object it the view tier post back the model Id to the corresponding controller and controller via the repository object tries to delete the object.

 [HttpPost]
    public ActionResult DeleteElement(Element element)
    {
        Element deletedElement = repository.Delete(element);
        TempData["message"] = string.Format("{0} has been deleted.",deletedElement.Name);
        return RedirectToAction("Index");
    }

the partial of table view:

<td>
            @using (Html.BeginForm("DeleteMenu", "Admin"))
            { 
                @Html.Hidden("ID", item.ID)

                <input type="submit" value="Delete"/>
            }
        </td>

So the view only post back the elemntID to the controller. and element object has only its ID. and all of its properties are null.when trying to delete the object because the name property is null, the session object in the repository can't delete the object because name field is null.

the Error message:

not-null property references a null or transient value Element.Name

If I am only removing an object and have the primary key, why does nHibernate care if other fields are null? and how can I delete the object only with its id?

 public IQueryable<T> GetAll()
    {
        return session.Query<T>();
    }

    public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
    {
        return GetAll().Where(predicate);
    }


 public void Delete(T entity)
        {
            session.Delete(entity);
        }
1

1 Answers

2
votes

Retrieve the instance from NHibernate first, and use that as the object to pass into Delete.

What is happening is you are creating an object outside of NHibernate's purview (in the MVC model binding). Since you are specifying nothing but the ID in the HTML form, the model's properties are all null when the model binder finishes, too.

When you pass this object to NHibernate, it will notice that it is not being observed by the session and try to attach it, which will see lots of dirty property values (all nulls), and thus try to flush changes to it first.

Your action should look like this:

[HttpPost]
public ActionResult DeleteElement(int id)
{   var element = repository.Get(e => e.Id == id).First();
    repository.Delete(element);

    TempData["message"] = string.Format("{0} has been deleted.",deletedElement.Name);
    return RedirectToAction("Index");
}

I would recommend adding a method to your repository that wraps Load by NHibernate. Load is useful because it creates an observed instance of the object with the ID you specify, but does not actually hit the database until you access a property other than the ID. It's very useful for situations where you know the object exists, but only need a pointer (such as deleting an entity, or adding it in a relationship).

Updated repository:

public IQueryable<T> GetAll()
{
    return session.Query<T>();
}

public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
{
    return GetAll().Where(predicate);
}

public T DeferredGet(int id) // I like to call it DeferredGet, you can call it Load or whatever you want
{
       return session.Load<T>(id);
}

public void Delete(T entity)
{
    session.Delete(entity);
}

and then your updated action:

[HttpPost]
public ActionResult DeleteElement(int id)
{   var element = repository.DeferredGet(id); // will not actually hit the database, saving you a query.
    repository.Delete(element); // deletes the element normally.

    TempData["message"] = string.Format("{0} has been deleted.",deletedElement.Name);
    return RedirectToAction("Index");
}