Tags: asp.net, c#, mvc, nhibernate, razor, model binding, view models, domain models, nhibernate.validator, dataannotations, validation |
Categories: ASP.Net, C#, MVC, NHibernate, Razor
Posted by
Scott on
12/19/2011 10:53 AM |
Comments (1)
We keep discussing what use a view model has and where (if at all) we should be using them. Eventually we came to some conculsions, not all of which are obvious.
Firstly a view model is important to keeping your views clean; it should provide all of the data required by a view - this avoids calling entity managers from within a view to get (for arguements sake) the list of orders for a customer. Putting this kind of code in your views is bad; it puts business logic into a file that should be exclusively UI logic, it puts executable code into a file that is not (usually) compiled, it puts code beyond the reach of (most) refactoring tools and, it creates a very confusing document.
Secondly a view model provides a buffer between our domain models and our web application - yes, our web application does have visibility of the domain models and an api via the entity manager to CRUD the entities but, should an entity be forced to implement a parameterless constructor so that binding can take place? Or (as in our case) should our protected internal parameterless .ctor (written for the exclusive use of NHibernate) now become public and therefore bypass some of the business logic that is implemented within the parameterised .ctors?
Clearly the answer is no but we can create a view model within our application that mirrors the domain model and provides the required parameterless .ctor, binding can take place, validation can happen and the only cost is calling the parameterised .ctor on the domain model, passing in the values from the view model.
Let's explain this with an example. Firstly, our domain class Client, note that the default parameterless .ctor is protected internal so it will not be available within our MVC application and therefore not available to MVC model binding. The public .ctor implements our business logic that all clients must have a forename and surname (this could be further enhanced within the .ctor by adding validation errors if either forename or surname are null or empty).
namespace Playground.Models
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Validator.Constraints;
public class Client : BaseEntity
{
private IList<ClientEmailAddress> emailAddresses = new List<ClientEmailAddress>();
private IList<ClientTelephoneNumber> telephoneNumbers = new List<ClientTelephoneNumber>();
// public .ctor requires forename and surname
public Client(string forename, string surname)
{
this.Forename = forename;
this.Surname = surname;
}
// internal .ctor for NHibernate. Never expose this publically.
protected internal Client() { }
// NotNullNotEmpty attribute from NHibernate.Validation.Constraints
[NotNullNotEmpty()]
public virtual string Forename { get; set; }
// NotNullNotEmpty attribute from NHibernate.Validation.Constraints
[NotNullNotEmpty()]
public virtual string Surname { get; set; }
public virtual IList<ClientEmailAddress> EmailAddresses
{
get { return this.emailAddresses; }
private set { this.emailAddresses = value; }
}
public virtual IList<ClientTelephoneNumber> TelephoneNumbers
{
get { return this.telephoneNumbers; }
private set { this.telephoneNumbers = value; }
}
}
}So, what do we do within our MVC application that wants to perform CRUD operations on our Client type? Well, we could use the type directly within our MVC controller:
namespace Playground.Web.Controllers
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Playground.BusinessLogic;
using Playground.Models;
public class ClientController : Controller
{
[HttpGet]
public ActionResult Add()
{
return View();
}
[HttpPost]
public ActionResult Add(string forname, string surname)
{
if (!ModelState.IsValid)
{
return View();
}
Client client = new Client(forename, surname);
client = this.entityManager.Save(client);
if (client.ValidationErrors.Any() || client.OperationErrors.Any())
{
foreach (var validationError in client.ValidationErrors)
{
ModelState.AddModelError(validationError.Property, validationError.Error);
}
foreach (var operationError in client.OperationErrors)
{
ModelState.AddModelError(operationError.Operation, operationError.Error);
}
return View();
}
return RedirectToAction("Index");
}
}
}The issue with this approach is that checking the ModelState.IsValid property really doesn't do anything and we're all the way into the Save method before we get any validation of our object. And when we do validate we're pulling business (or possibly database) errors and their not-so-user-friendly messages into our UI. The typical solution is to use the domain model for the parameter on the post method of the controller (and as the model in the view) which automagically wires up form values with properties on the model. This type of model binding requires a default parameterless .ctor which now doesn't exist because it would break the business rules on the Client type.
So what we really want is to use types throughout our MVC application, use binding within our controllers and, not break our business rules. If we create a model within our MVC application - a view model - we can solve our issue and get early validation with user friendly messages into the bargin. Note the namespace and the implicit default parameterless .ctor.
namespace Playground.Web.ViewModels
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
public class Client
{
// Required is from the System.ComponentModel.DataAnnotations namespace.
[Required(ErrorMessage="This is a user friendly validation message")]
public virtual string Forename { get; set; }
[Required()]
public virtual string Surname { get; set; }
}
}Now we can update our controller to use our view model:
namespace Playground.Web.Controllers
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Playground.BusinessLogic;
using Playground.Models;
public class ClientController : Controller
{
[HttpGet]
public ActionResult Add()
{
return View(new Playground.Web.ViewModels.Client());
}
[HttpPost]
public ActionResult Add(Playground.Web.ViewModels.Client postedClient)
{
if (!ModelState.IsValid)
{
return View(postedClient);
}
Client client = new Client(postedClient.Forename, postedClient.Surname);
client = this.entityManager.Save(client);
if (client.ValidationErrors.Any() || client.OperationErrors.Any())
{
foreach (var validationError in client.ValidationErrors)
{
ModelState.AddModelError(validationError.Property, validationError.Error);
}
foreach (var operationError in client.OperationErrors)
{
ModelState.AddModelError(operationError.Operation, operationError.Error);
}
return View(postedClient);
}
return RedirectToAction("Index");
}
}
}Now when we check ModelState.IsValid we're checking the attributes for the view model before we try to save the domain object.We also get the advantage of using model binding with the view like this:
@model Playground.Web.ViewModels.Client
@{
ViewBag.Title = "Add";
}
Add
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
}
@Html.ActionLink("Back to List", "Index")
And we have the advantage where we can add properties to the view model that aren't required on the domain model; the user input on a registration form might require the password to be confirmed (an attempt at mitigating typos resulting in the user setting their password incorrectly) so a ConfirmPassword property can be added to the view model together with a validator but the domain model need not be sullied with this non-persisted, UI only property.
528ce65e-be58-4276-a549-c6f899d7054f|0|.0
So today we were working on a new sample project using NHibernate, Ninject and, ASP.Net MVC3 which (as is usually the case at the start of these things) we wanted to be just right. Typically we ended up with a Client type with its expected lists of phone number and email address, in each list none or one of which can be flagged as being the default. We ended up with a class hierarchy like:
public abstract class BaseEntity
{
public virtual int Id { get; set; }
}
public abstract class BaseEmailAddress
{
public virtual string EmailAddress { get; set; }
public virtual bool IsDefault { get; set; }
}
public class ClientEmailAddress
{
private Client client;
public virtual Client Client
{
get { return this.client; }
private set { this.client = value; }
}
}
public class Client : BaseEntity
{
private IList<ClientEmailAddress> emailAddresses = new List<ClientEmailAddress>();
public virtual IList<ClientEmailAddress> EmailAddresses
{
get { return this.emailAddresses; }
private set { this.emailAddresses = value; }
}
}Everything inherits BaseEntity, ClientEmailAddress inherits BaseEmailAddress which implements the EmailAddress and IsDefault properties.
So then we questioned where do we implement the none or one ClientEmailAddress being flagged IsDefault? We decided that we could shadow the IsDefault property in ClientEmailAddress and implement the logic in the setter; if setting my IsDefault property to true then set the IsDefault property of all the other items in my parents ClientEmailAddresses collection. Like this:
public class ClientEmailAddress
{
private Client client;
public virtual Client Client
{
get { return this.client; }
private set { this.client = value; }
}
public new virtual bool IsDefault
{
get { return base.isDefault; }
set
{
if (value && this.client != null)
{
foreach (var clientEmailAddress in this.client.ClientEmailAddresses)
{
clientEmailAddress.IsDefault = false;
}
}
base.isDefault = value;
}
}
}However, NHibernate did not like this very much, complaining the 'IsDefault is already mapped' or similar which got us to thinking whether the IsDefault property belonged on a BaseEmailAddress type? After some debate we can to the conclusion that the IsDefault property is only relevent to the ClientEmailAddress type since it is only in the context of a list of email addresses that being the default email address makes any sense. So we moved the IsDefault property to the relevent class:
public abstract class BaseEmailAddress
{
public virtual string EmailAddress { get; set; }
}
public class ClientEmailAddress
{
private Client client;
public virtual Client Client
{
get { return this.client; }
private set { this.client = value; }
}
private bool isDefault = false;
public virtual bool IsDefault
{
get { return this.isDefault; }
set
{
if (value && this.client != null)
{
foreach (var clientEmailAddress in this.client.ClientEmailAddresses)
{
clientEmailAddress.IsDefault = false;
}
}
this.isDefault = value;
}
}
}
53cce46f-4add-4b84-a6da-6904e7252445|0|.0
We re-factored a few namespaces and split a few class libraries into multiple assemblies to enable some code re-use but then struggled to automap them using Fluent NHibernate. We quickly noticed that types were being loaded from the different assemblies, but not all of them. It seemed that those types in assembly B that inherited from types in assembly A were not being loaded which in fact was the vast majority of the types in assembly B. We also noticed that other types in assembly B who did not inherit from types in assembly A were also not being loaded. A quick inspection of the inheritance tree showed that only those types in assembly B who inherited from an abstract type were being loaded. In our case the solution was to simple ensure that all the subclasses were defined as abstract types.
// type defined in RealWebDevelopers.Models.dll
namespace RealWebDevelopers.Models
{
public abstract class BaseEntity
{
public int Id { get; set; }
}
}
// type defined in RealWebDevelopers.Models.Jobs.dll
namespace RealWebDevelopers.Models.Jobs
{
using RealWebDevelopers.Models;
public class Advertisement : BaseEntity
{
public string Name { get; set; }
}
}
namespace RealWebDevelopers.Data
{
using RealWebDevelopers.Models;
using RealWebDevelopers.Models.Jobs;
<summary>Loads multiple assemblies into the MappingConfiguration.</summary>
private void MapAssemblies(FluentNHibernate.Cfg.MappingConfiguration config)
{
config.AutoMappings.Add(
AutoMap.Assembly(Assembly.GetAssembly(typeof(BaseEntity)))
.Override<BaseEntity>(a =>
{
a.IgnoreProperty(e => e.OperationErrors);
a.IgnoreProperty(e => e.ValidationErrors);
})
.Conventions.AddFromAssemblyOf<EnumConvention>());
config.AutoMappings.Add(
AutoMap.Assembly(Assembly.GetAssembly(typeof(Advertisement)))
.Override<TokenTransaction>(n => n.Map(c => c.TransactionType).CustomType<int>())
.OverrideAll(a =>
{
a.IgnoreProperty("Display");
})
.Conventions.AddFromAssemblyOf<EnumConvention>());
}
}
301f2af1-9b29-475f-946b-8163497decae|0|.0
So, Country has a collection of Counties
public class Country
{
private IList<County> counties = new List<County>();
public virtual IList<County> Counties
{
get { return this.counties; }
set { this.counties = value; }
}
}
Therefore County has a reference to Country
public class County
{
public virtual Country Country { get; set; }
}
I was trying to create a new County and then add it to the Counties collection of a Country instance
public static void Something()
{
Country country = repository.Get<Country>(countryId);
County county = new County(){ Name = "Berkshire" };
country.Counties.Add(county); //this executes
repository.Save<Country>(country); //this bails
}
The solution was to test the parent Country's County collection within the Country setter of the County class
public class County
{
private Country country = null;
public virtual Country Country
{
get { return this.country; }
set
{
this.country = value;
if (!this.country.Counties.Contains(this))
{
this.country.Counties.Add(this);
}
}
}
}
Why? Well I don't really know, but it works. I guess that there is something going on within NHibernate and object tracking, but that is just a guess!
For a more fluent way of doing the same thing
public class County
{
public virtual Country Country { get; protected set; }
public virtual County WithCountry(Country country)
{
this.country = value;
if (!this.country.Counties.Contains(this))
{
this.country.Counties.Add(this);
}
return this;
}
}
7e6cbe0f-d795-41a8-a6d4-45167941cf0e|0|.0
Tags: asp.net, c#, fluent api, mvc, nhibernate, ninject, nhibernate.validator |
Categories: ASP.Net, C#, Fluent, MVC, NHibernate, Ninject, NHibernate,Validator
Posted by
Admin on
9/14/2011 4:26 PM |
Comments (0)
Eventually (after lots of google searches and putting together snippets from here, there and, everywhere) we now have Fluent NHibernate, NHibernate.Validator and Ninject all playing together.
First, we configure NHibernate using Fluent NHibernate passing the configuration to NHibernate.Validator so that it can configure the NHibernateSharedEngineProvider - this is important as Ninject will use the NHibernateSharedEngineProvider.
public static class Database
{
private static ISessionFactory sessionFactory = null;
public static ISessionFactory CreateSessionFactory()
{
var config = new AutoMappingConfiguration();
ValidatorEngine validatorEngine = null;
if (sessionFactory == null)
{
sessionFactory = Fluently
.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(c => c.Server("").Database("").TrustedConnection()))
.Mappings(
m => m.AutoMappings.Add(
AutoMap.AssemblyOf<EntityBase>(config)
.Override<EntityBase>(n => n.IgnoreProperty(c => c.ValidationErrors))
.Override<Country>(n => n.IgnoreProperty(c => c.DisplayName))
))
.ExposeConfiguration(c => {
validatorEngine = ConfigureNHibernateValidator(c);
BuildSchema(c);
})
.BuildSessionFactory();
}
return sessionFactory;
}
public static ValidatorEngine ConfigureNHibernateValidator(Configuration nhibernateConfiguration)
{
NHibernate.Validator.Cfg.Environment.SharedEngineProvider = new NHibernateSharedEngineProvider();
var validatorConfiguration = new NHibernate.Validator.Cfg.Loquacious.FluentConfiguration();
validatorConfiguration
.SetDefaultValidatorMode(ValidatorMode.UseAttribute)
.Register(Assembly.Load("models").ValidationDefinitions())
.IntegrateWithNHibernate
.ApplyingDDLConstraints()
.RegisteringListeners();
var validatorEngine = NHibernate.Validator.Cfg.Environment.SharedEngineProvider.GetEngine();
validatorEngine.Configure(validatorConfiguration);
ValidatorInitializer.Initialize(nhibernateConfiguration, validatorEngine);
return validatorEngine;
}
private static void BuildSchema(Configuration config)
{
new SchemaUpdate(config).Execute(false, true);
}
}
Then we configure Ninject
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IEntityManager>().To<EntityManager>().InRequestScope();
kernel.Bind<ISessionFactory>().ToMethod(x => Database.CreateSessionFactory()).InSingletonScope();
kernel.Bind<ISession>().ToMethod(x => kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
kernel.Bind<ISharedEngineProvider>().ToMethod(x => NHibernate.Validator.Cfg.Environment.SharedEngineProvider).InSingletonScope();
}
Then we inject both ISession and ISharedEngineProvider into our EntityManager via the .ctor. We then check the Entity is valid before saving.
public class EntityManager : IEntityManager
{
private ISession session = null;
private ISharedEngineProvider validator = null;
public EntityManager(ISession session, ISharedEngineProvider validator)
{
this.session = session;
this.validator = validator;
}
public T Save<T>(T item) where T : EntityBase
{
item.ValidationErrors.Clear();
ValidatorEngine ve = this.validator.GetEngine();
if (!ve.IsValid(item))
{
foreach (var invalidValue in ve.Validate(item))
{
item.ValidationErrors.Add(new ValidationError() { PropertyName = invalidValue.PropertyName, Message = invalidValue.Message });
}
}
else
{
try
{
this.session.SaveOrUpdate(item);
this.session.Flush();
}
catch (InvalidStateException ise)
{
item.ValidationErrors.Clear();
foreach (var invalidValue in ise.GetInvalidValues())
{
item.ValidationErrors.Add(new ValidationError() { PropertyName = invalidValue.PropertyName, Message = invalidValue.Message });
}
}
}
return item;
}
}
Note that within EntityManager we use ISharedEngineProvider.GetEngine() to get our pre-configured Validator. The try { ... } catch { ... } around this.Session.SaveOrUpdate(item) isn't really required but better to be safe than sorry!
57af47e3-5517-41a7-afa7-d9bd873729a0|0|.0
We tried to write a filter based on a DateTime? (nullable DateTime) property like
IList<Vacancy> vacancies = manager.Get<Vacancy>(v => v.StartDate.GetValueOrDefault(DateTime.MinValue) < DateTime.Today);
But NHibernate (or possibly NHibernate.Linq) threw a runtime error. It turns out that NHibernate(.Linq) handles all this for us so
IList<Vacancy> vacancies = manager.Get<Vacancy>(v => v.StartDate < DateTime.Today);
Works perfectly!
314c30d1-540c-4488-aa1f-2c11261b7992|0|.0
When we wrote NHibernate logical (virtual, 'soft') deletion we showed how to implement virtual deletion of entities (i.e. set IsDeleted = true) by cross-cutting concerns in NHibernate. Since then we've discovered that this raises an issue; when you retrieve a list of entites from NHibernate it doesn't understand that it shouldn't get entites where IsDeleted = true. The initial solution was to add a constraint to our entities (since all our entities subclass BusinessBase we only needed to do this in one place) by adding a Fluent NHibernate Where clause.
public BusinessBaseMap()
{
this.Where("IsDeleted = 0");
this.Id(a => a.Id);
this.Map(a => a.Created);
}Note the string passed to .Where() is SQL.
The constraint did stop deleted entites from being returned but is completely ignored for related collections so you might not get deleted ProductCategories but the Products collection will include deleted Products; not ideal. This caused a little re-think until we realized that this isn't a bug in NHibernate; it isn't the duty of any ORM tool to implement business logic like this - it is the job of the business manager class(es). Needless to say, we left the constraint in place; if NHibernate is going to perform this function for us out-of-the-box we're going to us it!
So back to issue; how to exclude deleted entities from being return in collections? We decided to change our entities collections from 'auto' properties to use backing fields with accessors where the get accessor applies the constraint and NHibernate populates the backing field.
private IList<EmailAddress> emailAddresses = new List<EmailAddress>();
public virtual IList<EmailAddress> EmailAddresses
{
get
{
return this.emailAddresses.Where(a => !a.IsDeleted);
}
}We didn't even need to change our mappings; NHibernate defaults to using a backing field with the name of the property with a lower-case first letter.
ffd0f9e2-d2d5-46a1-8f00-b643eb30a47e|0|.0
Tags: asp.net, c#, nhibernate, ninject, version binding, package manager console, nuget |
Categories: ASP.Net, C#, MVC, NHibernate, Ninject
Posted by
Admin on
6/13/2011 12:55 PM |
Comments (7)
We eventually managed to get Ninject,Fluent NHibernate, NHibernate.Linq and MVC3 to play nicely together. Each of these libraries has its issues when it comes to playing nicely mainly due to dependencies mis-matches or due to a lack of instruction.
Firstly, Ninject. We installed Ninject into our sample application using the Package Manager Console.
Install-Package Ninject
Install-Package Ninject.Web
Install-Package Ninject.MVC3
We missed Ninject.MVC3 first time around which cost us about two hours of head scratching so remember to install it.
Once all three Ninject libraries are installed into your application you should have a new generated file App_Start/NinjectMVC3.cs, this is where you load your Ninject Module(s) into your application and where the magic construction injection takes place for your controllers (amongst other things).
The next step seems to be widely documented on the net pointless if you're using NinjectMVC3; modifying your Global.asax file to enable Ninject to do its thing is no longer required and to configure NHibernate fluently.
public class MvcApplication : HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Person", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
}Next we need to configure Ninject so that it knows what concrete implementations of interfaces to inject. This is done by inheriting from NinjectModule and overriding the Load() method.
public override void Load()
{
this.Bind<IRepository>().To<NHRepository>().InRequestScope(); //.InSingletonScope(); <-- Singleton scope doesn't work with ISession in request scope!
this.Bind<ISessionFactory>().ToMethod(x => Database.CreateSessionFactory()).InSingletonScope();
this.Bind<ISession>().ToMethod(x => context.Kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
this.Bind<PersonController>().ToSelf();
}In this case whenever a constructor requires an implementation of IRepository the same an instance of NHRepository is supplied by the InSingletonScope modifier. We're also binding our NHibernate ISession to the GetCurrentSessionOpenSession() method of the ISessionFactory (which being static prevents re-initialization of SessionFactory - a very expensive operation) and binding the PersonController to itself.
Finally we need Ninject to load our module into the application when the application starts, this is done in NijnectMVC.cs by modifying the CreateKernel method.
private static IKernel CreateKernel()
{
var kernel = new StandardKernel(new MvcApplicationNinjectModule());
RegisterServices(kernel);
return kernel;
}As far as Ninject is concerned we're all sorted. We just need to create IRepository and implement this in NHRepository which is where NHibernate.Linq comes into the mix and then create our controller to call the repository.
We won't go into the details of IRepository and NHRepository other than to point out that Ninject injects our NHibernate Session into NHRepository in the constructor.
public interface IRepository
{
IQueryable<T> Get<T>();
IQueryable<T> Get<T, TKey>(params OrderByClause<T, TKey>[] orderBy);
IQueryable<T> Get<T>(int pageSize, int pageIndex, out int recordCount);
IQueryable<T> Get<T, TKey>(int pageSize, int pageIndex, out int recordCount, params OrderByClause<T, TKey>[] orderBy);
}public class NHRepository : IRepository
{
private ISession session = null;
public IQueryable<T> Get<T>()
{
return this.session.Query<T>();
}
public IQueryable<T> Get<T, TKey>(params OrderByClause<T, TKey>[] orderBy)
{
var query = this.session.Query<T>();
foreach (var order in orderBy)
{
switch (order.Direction)
{
case OrderByDirection.Descending:
query = query.OrderByDescending(order.Expression);
break;
default:
query = query.OrderBy(order.Expression);
break;
}
}
return query;
}
public IQueryable<T> Get<T>(int pageSize, int pageIndex, out int recordCount)
{
recordCount = this.session.Query<T>().Count();
return this.session.Query<T>().Skip(pageSize * pageIndex).Take(pageSize);
}
public IQueryable<T> Get<T, TKey>(int pageSize, int pageIndex, out int recordCount, params OrderByClause<T, TKey>[] orderBy)
{
recordCount = this.session.Query<T>().Count();
var query = this.session.Query<T>();
foreach (var order in orderBy)
{
switch (order.Direction)
{
case OrderByDirection.Descending:
query = query.OrderByDescending(order.Expression);
break;
default:
query = query.OrderBy(order.Expression);
break;
}
}
return query.Skip(pageSize * pageIndex).Take(pageSize);
}
public NHRepository(ISession session)
{
this.session = session;
}Finally we come to the controller which needs an instance of IRepository to fetch its data from.
public class PersonController : Controller
{
private IRepository repository;
public PersonController(IRepository repository)
{
this.repository = repository;
}
[HttpGet]
public ActionResult Index(int? pageIndex)
{
int actualPageIndex = pageIndex.GetValueOrDefault(0);
int recordCount = 0;
var people = this.repository.Get(10, actualPageIndex, out recordCount, new OrderByClause(c => c.DateOfBirth.Value, OrderByDirection.Descending), new OrderByClause(c => c.Surname, OrderByDirection.Ascending), new OrderByClause(c => c.Forename, OrderByDirection.Ascending));
int pageCount = (int)Math.Ceiling((double)recordCount / 10);
return View(new PagedListViewModel(people.ToList(), actualPageIndex, pageCount));
}
}Ninject is again doing its magic by injecting an (singleton) instance of NHRepository into our constructor for us.
So where were the issues? Well if you use the package manager to get all the Fluent NHibernate and NHibernate.Linq libraries you'll see that Fluent NHibernate uses version 3.1 of NHibernate but NHibernate.Linq is complied against version 2.1, and NHibernate.Linq is complied against version 3.5 of System.Xml.Linq whereas our application is using version 4.0. To fix these mis-matches we can configure our application to substitute any request for a dependancy with a newer version of the same library. This is done in web|app.config
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Xml.Linq" publicKeyToken="b77a5c561934e089" />
<bindingRedirect oldVersion="1.0.0.0-3.5.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" />
<bindingRedirect oldVersion="2.1.0.4000-2.1.2.4000" newVersion="3.1.0.4000" />
</dependentAssembly>
</assemblyBinding>
</runtime>
0f379187-9b9d-46db-ab50-dbf2cb116029|0|.0
Sometimes people just want to make sure that data isn't ever lost and this usually results in logical or virtual or soft database deletions using a flag IsDeleted. Invariably this is required across the full domain model and to keep things DRY is better implemented within the data access layer.
NHibernate allows us to intercept events with our own code (as all good frameworks should) and the Delete event seems like a good place to go.
public class LogicalDeleteEventListener : DefaultDeleteEventListener
{
protected override void DeleteEntity(
NHibernate.Event.IEventSource session,
object entity,
NHibernate.Engine.EntityEntry entityEntry,
bool isCascadeDeleteEnabled,
NHibernate.Persister.Entity.IEntityPersister persister,
Iesi.Collections.ISet transientEntities)
{
if (entity is IBusinessBase)
{
((IBusinessBase)entity).IsDeleted = true;
((IBusinessBase)entity).Updated = DateTime.Now;
CascadeBeforeDelete(session, persister, entity, entityEntry, transientEntities);
CascadeAfterDelete(session, persister, entity, transientEntities);
}
else
{
base.DeleteEntity(
session,
entity,
entityEntry,
isCascadeDeleteEnabled,
persister,
transientEntities);
}
}
}Then just apply this handler to your NHibernate config
config.SetListener(ListenerType.Delete, new LogicalDeleteEventListener());
And now anything implementing IBusinessBase will be logically deleted i.e. IsDeleted == true. Obviously (in this implementation) IBusinessBase needs to have both IsDeleted and Updated properties.
30b1adbe-284c-499c-ac0e-beb6dc8ea720|0|.0
Domain models ... remember to mark properties as virtual
public class Country : IBusinessBase
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList Territories { get; private set; }
}
public class Territory : IBusinessBase
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
// children
public virtual IList Districts { get; private set; }
// parent
public virtual Country Country { get; set; }
}
Fluent mapping
public class CountryMap : ClassMap<Country>
{
public CountryMap()
{
Id(c => c.Id);
Map(c => c.Name).Length(127).Not.Nullable();
// children
HasMany(c => c.Territories).Inverse().Cascade.All();
}
}
public class TerritoryMap : ClassMap<Territory>
{
public TerritoryMap()
{
Id(c => c.Id);
Map(c => c.Name).Length(127).Not.Nullable();
// children
HasMany(c => c.Districts).Inverse().Cascade.All();
// parent
References(c => c.Country);
}
}
Singleton repository
public sealed class Repository
{
private ISession session = null;
private static readonly Repository _instance = new Repository();
public static Repository Instance
{
get { return _instance; }
}
public IQueryable<T> GetAll<T>() where T : IBusinessBase
{
return session.Query<T>();
}
public void Save<T>(T item)
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(item);
transaction.Commit();
}
catch
{
transaction.Rollback();
}
}
}
private Repository()
{
session = Database.OpenSession();
}
}
And finally the database (NHibernate)
public static class Database
{
private static ISessionFactory sessionFactory = null;
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory == null)
{
sessionFactory = Fluently
.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.Server("SERVER").Database("DATABASE").Username("USERNAME").Password("PASSWORD")))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Repository>())
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
return sessionFactory;
}
}
private static void BuildSchema(Configuration config)
{
new SchemaUpdate(config).Execute(false, true);
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
b0224578-0510-427b-917f-788de79e07bf|0|.0