Reputation: 911
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.
Upvotes: 4
Views: 8850
Reputation: 123861
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.
Upvotes: 2
Reputation: 2023
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.
Upvotes: 3