649
I Use This!
Moderate Activity

News

Analyzed 1 day ago. based on code collected 2 days ago.
Posted about 13 years ago by James Kovacs
[This article was originally published on my personal blog here. I hereby grant myself permission to re-publish it on NHForge.org.] [Code for this article is available on GitHub here.] NHibernate 3 introduces support for both the .NET 3.5 Client ... [More] Profile and .NET 4.0 Client Profile. This means that applications built with NHibernate can be deployed to client desktops without the full .NET Framework installed. Why hasn’t this been available all along? NHibernate 2.X was built to support .NET 2.0 and above. The Client Profile wasn’t introduced until after NHibernate 2.0 was released. Microsoft introduced the Client Profile in .NET 3.5 to reduce the size of the .NET Framework on client machines. (The reality is that end users don’t need ASP.NET, server-side WCF features, and MSBuild on their machines to run client-side applications.) So why didn’t NHibernate support the Client Profile once it was released? What was holding them back? What was holding NHibernate back from supporting the Client Profile immediately was a dependency on System.Web. Now why the heck would NHibernate depend on System.Web? There aren’t many places that NHibernate touches System.Web, but there are a few. The first is in supporting session-per-request semantics in web applications using Contextual Sessions. I won’t go into the details here, but once you configure cfg.CurrentSessionContext<T>() in Loquacious or hibernate.current_session_context_class in hibernate.cfg.xml, you can get the current session from your static session factory. (ASIDE: If none of the built-in ICurrentSessionContext classes suffices for your needs, it is very easy to implement your own.) var session = sessionFactory.GetCurrentSession(); The ManagedWebSessionContext and WebSessionContext classes can be used for session-per-request semantics and both store the current session in the HttpContext. Hence they need a reference to System.Web. So to support the Client Profile, the NHibernate team had to break this dependency on System.Web. They did this by accessing the HttpContext via a compiled dynamic method, which is evaluated at run-time. (A compiled dynamic method has much better performance than accessing properties through reflection.) Another more insidious dependency on System.Web was in the logging infrastructure. Before NHibernate 3, NHibernate took a hard dependency on log4net. If you wanted logging, you used log4net. Now here is the insidious part… log4net has a dependency on System.Web for its AspNetTraceAppender, which writes to the ASP.NET TraceContext. (You can access the ASP.NET TraceContext via http://example.com/trace.axd.) To break this dependency, NHibernate 3 introduces LoggerProvider and the IInternalLogger. If a logger is explicitly configured, it uses that one. Now for a bit of cleverness. If no logger is explicitly configured, and LoggerProvider is asked for an IInternalLogger, it checks the bin directory. If it finds log4net, it uses log4net. Otherwise it defaults to the NoLoggingLogger. (N.B. Out-of-the-box NHibernate 3 only supports log4net or no logging, though it isn’t too onerous support other logging frameworks by implementing an IInternalLogger adapter and some support classes.) I haven’t done an exhaustive search of the NHibernate 2.X codebase looking for other dependencies on System.Web, but those two give you an idea of why supporting the .NET Client Profile wasn’t as simple as recompiling NHibernate 2.X. The team had to break some dependencies on assemblies not include with the .NET Client Profile while not breaking backward compatibility. For most developers, supporting the .NET Client Profile is as simple as switching the Target Framework on their assemblies. One word of warning… If you’re using NHibernate Profiler (and you should be), the NHibernateProfiler.Appender doesn’t support the .NET Client Profile because it requires log4net. You can use a preprocessor directive around the initialization line of NHibernate Profiler and then define that conditional compilation constant (using #define ENABLE_NHPROF) to enable/disable profiling. You’ll also have to change the target framework to .NET 3.5 or .NET 4.0 temporarily for the profiling session so that your project compiles. #if ENABLE_NHPROF HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); #endif [Less]
Posted about 13 years ago by James Kovacs
[This article was originally published on my personal blog here. I hereby grant myself permission to re-publish it on NHForge.org.] [Code for this article is available on GitHub here.] Nothing gets an OO zealot hot under the collar the way the term ... [More] polymorphism does. You probably have three questions right now… What does polymorphism have to do with object-relational mapping? How does it relate to NHibernate? And why should I care? An ORM that supports polymorphic loading allows us to request one type of object, but potentially get an object of a derived type back. As an example, let's say we have the following simple inheritance hierarchy in our application: We can query for an Animal, but receive back an instance of Dog or Cat instead. var dog = session.Get<Animal>(dogId); NHibernate has supported this type of polymorphic loading behaviour for awhile, but the base class (or interface) had to be mapped. If it wasn’t, polymorphic loading would only work when querying with Criteria or LINQ. The following works for both NH 2.1.2 and NH3 regardless of whether the Animal base class is mapped or not. var animal = session.CreateCriteria<Animal>() .Add(Restrictions.IdEq(dogId)) .UniqueResult<Animal>(); // N.B. Use session.Linq<Animal>() in NH2.1.2 var query = from a in session.Query<Animal>() where a.Id == dogId select a; var animal = query.Single(); In NHibernate 2.1.2 and earlier, ISession.Get<T>(id) or ISession.Load<T>(id) would fail if T was an unmapped base class or interface. With NHibernate 3, these methods now work regardless of whether T is mapped or not.* // Works in NH3; works in NH2.1.2 only if Animal is mapped // In the sample code, works in NH3 for both Animal and UnmappedAnimal base classes // In NH2.1.2 and before, works for Animal (mapped), but not UnmappedAnimal var dog = session.Get<Animal>(dogId); var cat = session.Load<Animal>(catId); ASIDE: ISession.Get(id) returns null when the entity doesn’t exist in the database, whereas ISession.Load(id) throws an exception. Generally ISession.Load(id) is preferred if you know the entity should exist as NHibernate can return a proxy object that delays hitting the database until the last possible moment. ISession.Get(id) requires querying the database immediately because there is no way to return an object (e.g. a proxy), but later change it to null when accessed. In NHibernate 3, polymorphic loading works for Criteria, LINQ, and Get/Load. It has not been implemented for HQL. (If you want/need this feature, the NHibernate team is always willing to accept a feature request with patch.) // Criteria works in NH2.1.2 and NH3 var animal = session.CreateCriteria<UnmappedAnimal>() .Add(Restrictions.IdEq(dogId)) .UniqueResult<UnmappedAnimal>()); // LINQ works in NH2.1.2 and NH3 (NH2.1.2 uses session.Linq<T>()) var query = from a in session.Query<UnmappedAnimal>() where a.Id == dogId select a; var animal = query.Single(); // Get/Load works in NH3, but fails in NH2.1.2 and earlier var animal = session.Get<UnmappedAnimal>(dogId); // HQL fails for both NH2.1.2 and NH3 var animal = session.CreateQuery("from a in AbstractAnimal where a.id = :id") .SetParameter("id", dogId) .UniqueResult<UnmappedAnimal>()); * I should note one restriction on the generic parameter T when calling ISession.Get<T>(id) and ISession.Load<T>(). Polymorphic loading only works if there is a unique persister for T. Otherwise NHibernate throws a HibernateException, “Ambiguous persister for [T] implemented by more than one hierarchy”. What does this mean? Let’s say you have an unmapped abstract base class, such as Entity. (Entity is a class defined in our application, which includes properties common across all persistent entities, such as primary key, audit fields, and similar. It is not required by NHibernate, but often useful for extracting common domain code.) Consider the following contrived example: Note that the Animal inheritance hierarchy is mapped and so is Customer. If we try to execute the following code: var id = 42; var entity = session.Get<Entity>(id); We will get a HibernateException as mentioned above. We are asking NHibernate to load an Entity with an id of 42. But primary keys are only unique within a mapped inheritance hierarchy. So there could be a Cat (or Dog) with id of 42 and a Customer with id of 42! So NHibernate fails with a HibernateException since it has no way of returning a list of objects from Get/Load. If you really want to query across inheritance hierarchies, you can do so with Critera or LINQ where you return a list of objects. The following code will work: var id = 42; var entities = session.CreateCriteria<Entity>() .Add(Restrictions.IdEq(id)) .List<Entity>(); Here’s a NHibernate trick that makes for a good demo, but isn’t terribly practical in real applications… Retrieve a list of all entities in the database: var allEntities = session.CreateCriteria<object>() .List<object>(); Happy coding! [Less]
Posted over 13 years ago by Gareth Hayter
Visual NHibernate can be customized in many ways. Today we’ll look at the built-in template engine. You can easily create the exact output you need by tweaking the built-in templates or creating new custom templates. Visual NHibernate currently has ... [More] two built-in templates: The standard NHibernate template The new Sharp Architecture template. Template Editor The template editor enables you to specify how the generated files and folders should be laid out in the destination folder, WYSIWYG style. Script can be written to generate dynamic files, while static files such as images and DLLs can also be included in the output. The code editor has IntelliSense and syntax-highlighting, as well as live feedback about errors. Compile errors are underlined in red squiggly lines. They also appear in the errors grid as well as causing the title-bar to turn green or red, indicating errors. Find and replace can be accessed via the standard keyboard shortcuts Ctrl+F, Ctrl+Shift+F, Ctrl+H, Ctrl+Shift+H Laying out folders and files Add the first folder or file by clicking on the ‘Output Folder’ node in the treeview. Folders and files can have iterators. You can generate either a single file, or generate one file or folder per: Entity, Table, Column, Component, View. If you select an iterator then you will have access to the iterator in script via a lowercase name, for example: entity.Name, table.Name etc. File-names and folder-names can be dynamic and can contain script: Folders Add a folder by right-clicking on the parent folder. Dynamic files A dynamic file is one where the body is created dynamically by script. Add a file by right-clicking on the parent folder. Static files A static file is one that is just distributed as-is. It can be an image file, a readme file, a DLL etc. Static files need to be added to the Resources collection. To add a file to the Resources collection, select the Resources tab on the left, click the add button to select the file. Scripting Dynamic files output text. Create dynamic content by using either ASP-style delimiters <%...%> or T4-style delimiters <#...#> Select the delimiter style at the top right corner of the screen. Writing output Dynamic text can be inserted into the document in a number of ways: Mixing script and output text, similar to ASP Placeholders: <%=entity.Name%> Write(text) WriteLine(text) WriteFormat(formatText, args) WriteIf(boolean expression, trueText) WriteIf(boolean expression, trueText, falseText) Skipping files A dynamic file can be skipped via script by setting SkipThisFile = true and returning the reason. For example: if (!Project.Settings.UseFluentNHibernate){ SkipThisFile = true; return “Project.Settings.UseFluentNHibernate is false”;} Testing Visual NHibernate has a smooth edit-test cycle, giving immediate feedback on your template and scripts. It also enables you to test individual entities etc. Create your template script Select the ‘Test’ tab If the file has an iterator, select the iterator you want to test with. Example: if you’ve specified that one file should be created for each entity, then select an entity to test with. If the iterator is a table then a list of tables will be presented. Click the ‘Test’ button to run the test. The script will execute and display the generated text (file-body) or errors if they occur. Scripting API The API available for scripting exposes all details about the current project. You have full access to all properties or entities, table, relationships etc. The editor has full IntelliSense and the API is easily discoverable: Here’s a small portion of the API: CurrentFilePath: Gets or sets the path of the file being written. You can override the path specified in the filename editor via code. SkipThisFile: Set this to true to stop the current file from being generated. Return the reason for skipping. This reason will be displayed when testing this dynamic file. Project: Object containing all project-level variables, objects and settings. Project.Settings: The Settings object allows access to the values of all settings on the Settings tab. Project.Entities: A collection of all entities. Project.Components: A collection of all components. Project.Tables: A collection of all tables. Project.Views: A collection of all views. Project.OutputFolder: The path of the output folder selected by the user. Extend your existing NHibernate projects Visual NHibernate can reverse-engineer your existing NHibernate Visual Studio projects. Your existing code becomes the model. This enables you to create new code based on your existing code. Create a new template, import your existing code, generate new code. Conclusion Custom templates give you a tremendous amount of freedom and power. The best way to learn about the capabilities of templates is to inspect the built-in templates. Download Visual NHibernate, have a play, and tell us what you think. [Less]
Posted over 13 years ago by Jose Romaniello
My twitter friend @hotgazpacho is having a nightmare with a legacy database, which has the following rule: “0 represents the absence of an entity without an actual row in the database” It is pretty interesting how many times I’ve seen this ... [More] scenario on nhibernate forums. The first thing people do is to add “not-found=ignore” to every relationship, but not-found ignore is an evil, because NHibernate need to know if the row exist when lazy load. So not-found ignore is like a lazy loading killer. Also, with not-found=ignore doesn’t work when you insert or update, nhibernate will persist a null value instead of 0. We want to keep and follow the rule until no legacy applications use this database and we can fix the data, maybe never (or like the spanish saying “provisoriamente para siempre”). NHibernate is bad in many aspects, but the only thing we can’t blame is extensibility. We can tweak NHibernate to work in this scenario and in many more. First a test: [TestFixture] public class Fixture { private ISessionFactory sf; private Configuration configuration; [TestFixtureSetUp] public void SetUp() { configuration = new Configuration().Configure(); //export the schema var schemaExport = new SchemaExport(configuration); schemaExport.Execute(true, true ,false); sf = configuration.BuildSessionFactory(); } [TestFixtureTearDown] public void TearDown() { var schemaExport = new SchemaExport(configuration); schemaExport.Execute(true, true, true); } [Test] public void WhenInsertingAPersonWithNullCountryThenInsert0ValueInCountry() { int personId; using(var s = sf.OpenSession()) using(var tx = s.BeginTransaction()) { var p = new Person {Name = "tito"}; s.Save(p); tx.Commit(); personId = p.Id; } using(var s = sf.OpenSession()) { s.CreateSQLQuery("select CountryId from Person where id = :id") .SetInt32("id", personId) .UniqueResult<int?>() .Should().Be.EqualTo(0); } } [Test] public void WhenSelectingAPersonWithCountryWithIdEqualsTo0ThenCountryShouldBeNull() { int personId; using (var s = sf.OpenSession()) using (var tx = s.BeginTransaction()) { var p = new Person { Name = "tito" }; s.Save(p); tx.Commit(); personId = p.Id; } using (var s = sf.OpenSession()) using (s.BeginTransaction()) { s.Get<Person>(personId) .Country.Should().Be.Null(); } } } The first test persist a Person with null Country, and goes to the database to test if the CountryId is equals to 0. The second test, persist a Person with null Country, in other session executes Get<Person> and test if the Country is null. The mapping for person is trivial: <class name="Person"> <id name="Id"> <generator class="hilo"> <param name="max_lo">100</param> </generator> </id> <property name="Name" /> <many-to-one name="Country" class="Country" column="CountryId" foreign-key="PersonToCountry" /> </class> <database-object> <create> ALTER TABLE Person DROP CONSTRAINT PersonToCountry </create> <drop></drop> </database-object> Note: I am killing the constraint for this test, that is how the db must be on real life . The solution is pretty simple: public class NullableTuplizer : PocoEntityTuplizer { public NullableTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) : base(entityMetamodel, mappedEntity) { } public override object[] GetPropertyValuesToInsert( object entity, IDictionary mergeMap, ISessionImplementor session) { object[] values = base.GetPropertyValuesToInsert(entity, mergeMap, session); //dirty hack 1 for (int i = 0; i < values.Length; i++) { if (values[i ] == null && typeof (IEntity).IsAssignableFrom(getters[i ].ReturnType)) { values[i ] = ProxyFactory.GetProxy(0, null); } } return values; } public override object[] GetPropertyValues(object entity) { object[] values = base.GetPropertyValues(entity); //dirty hack 2 for (int i = 0; i < values.Length; i++) { if (values[i ] == null && typeof (IEntity).IsAssignableFrom(getters[i ].ReturnType)) { values[i ] = ProxyFactory.GetProxy(0, null); } } return values; } public override void SetPropertyValues(object entity, object[] values) { //dirty hack 3. for (int i = 0; i < values.Length; i++) { if (typeof (IEntity).IsAssignableFrom(getters[i ].ReturnType) && ((IEntity) values[i ]).Id == 0) { values[i ] = null; } } base.SetPropertyValues(entity, values); } } We lie to nhibernate three times: When getting the values for insert, we change a  “null” in the Country property, for a proxy of country with Id equals to 0. NHibernate assumes that such country exist and executes: When getting the values for update , we do the same than for the insert. When loading the values in the entity, we will get a nhibernate proxy or entity with Id = 0, we change this value to null. To register the NullableTuplizer for all the mappings: foreach (var persistentClass in configuration.ClassMappings) { persistentClass.AddTuplizer(EntityMode.Poco, typeof(NullableTuplizer).AssemblyQualifiedName); } [Less]
Posted over 13 years ago by James Kovacs
[Code for this article is available on GitHub here.] In this post, we’ll examine the ways that NHibernate supports the DateTime-related data types, including some new features introduced in NHibernate 2 and 3. Here is a quick summary for the ... [More] impatient. DbType .NET SQL Type DateTime System.DateTime datetime LocalDateTime System.DateTime datetime UtcDateTime System.DateTime datetime DateTimeOffset System.DateTimeOffset datetimeoffset DateTime2 System.DateTime datetime2 Date System.DateTime date Time System.DateTime time TimeAsTimeSpan System.TimeSpan time TimeSpan System.TimeSpan bigint (int64) Local/UTC Let’s take a look at a few DateTime-related problems that developers have run into in the past… public class DateTimeEntity { public DateTimeEntity() { CreationTime = DateTime.Now; } public virtual Guid Id { get; private set; } public virtual DateTime CreationTime { get; set; } } Note that CreationTime is initialized using DateTime.Now. The corresponding mapping file would be: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Nh3Hacking" assembly="Nh3Hacking"> <class name="DateTimeEntity"> <id name="Id"> <generator class="guid.comb" /> </id> <property name="CreationTime"/> </class> </hibernate-mapping> If we create an instance of our DateTimeEntity and reload it, we get: Original entity: Id: 09bead07-5a05-4459-a108-9e7501204918 CreationTime: 2011-01-24 5:29:36 PM (Local) Reloaded entity: Id: 09bead07-5a05-4459-a108-9e7501204918 CreationTime: 2011-01-24 5:29:36 PM (Unspecified) Note that I am outputting both CreationTime.ToString() and CreationTime.Kind. DateTime.Kind returns a DateTimeKind (surprising, huh?), which indicates whether this DateTime represents Local time or UTC time. We initialized the value with DateTime.Now, which is the local time. (If we wanted UTC time, we would use DateTime.UtcNow.) When the object is reloaded, the DateTimeKind is Unspecified. This is because the database does not store whether the DateTime value is Local or UTC. NHibernate has no way of knowing which one it is, hence Unspecified. NHibernate 3 includes two new DbTypes that allow us to resolve this ambiguity. In our mapping file, we can write: <property name="CreationTimeAsLocalDateTime" type="LocalDateTime"/> <property name="CreationTimeAsUtcDateTime" type="UtcDateTime"/> We are explicitly telling NHibernate whether the database stores Local or UTC times. Original entity: Id: 09bead07-5a05-4459-a108-9e7501204918 CreationTimeAsDateTime: 2011-01-24 5:29:36 PM (Local) CreationTimeAsLocalDateTime: 2011-01-24 5:29:36 PM (Local) CreationTimeAsUtcDateTime: 2011-01-25 12:29:36 AM (Utc) Reloaded entity: Id: 09bead07-5a05-4459-a108-9e7501204918 CreationTimeAsDateTime: 2011-01-24 5:29:36 PM (Unspecified) CreationTimeAsLocalDateTime: 2011-01-24 5:29:36 PM (Local) CreationTimeAsUtcDateTime: 2011-01-25 12:29:36 AM (Utc) This is strictly metadata and it is up to the developer to ensure that the proper DateTime is present in the property/field. For instance, if I initialize the entity as follows: public DateTimeEntity() { CreationTimeAsDateTime = DateTime.Now; CreationTimeAsLocalDateTime = DateTime.UtcNow; CreationTimeAsUtcDateTime = DateTime.Now; } Note that the LocalDateTime property contains a UTC DateTime and the UTC property contains a Local DateTime. The results are: Original entity: Id: 4579d245-46f3-4c3f-893b-9e750124a90b CreationTimeAsDateTime: 2011-01-24 5:45:32 PM (Local) CreationTimeAsLocalDateTime: 2011-01-25 12:45:32 AM (Utc) CreationTimeAsUtcDateTime: 2011-01-24 5:45:32 PM (Local) Reloaded entity: Id: 4579d245-46f3-4c3f-893b-9e750124a90b CreationTimeAsDateTime: 2011-01-24 5:45:32 PM (Unspecified) CreationTimeAsLocalDateTime: 2011-01-25 12:45:32 AM (Local) CreationTimeAsUtcDateTime: 2011-01-24 5:45:32 PM (Utc) Notice that NHibernate did not perform any conversions or throw an exception when saving/loading a DateTime value with the wrong DateTimeKind. (It could be argued that NHibernate should throw an exception when asked to save a Local DateTime and the property is mapped as a UtcDateTime.) It is up to the developer to ensure that the proper kind of DateTime is in the appropriate field/property. System.DateTimeOffset One problem that LocalDateTime and UtcDateTime does not solve is the offset problem. If you have a DateTime and its Kind is Local, all you know is that it is a Local DateTime. You do not know if that Local DateTime is Mountain (MST), Eastern (EST), Pacific (PST), etc. You do not know whether it has been corrected for daylight savings time. All you know is that it is a Local DateTime. You have to assume that the local time is based on the time zone of the current computer. Although this is often a reasonable assumption, it’s not always. (Consider for example that you’re collecting log files from a distributed system and servers reside in multiple time zones.) The problem is that System.DateTime class does not contain a place to record the timezone offset. Microsoft solved this problem starting in .NET 3.5 by introducing the System.DateTimeOffset class. It looks a lot like System.DateTime, but does include the timezone offset rather than the DateTimeKind. So we can just use System.DateTimeOffset in our applications rather than System.DateTime. Except… Date/time types in SQL databases do not have anywhere to store the timezone offset. The notable exception is SQL Server 2008, which introduced the datetimeoffset type. NHibernate 2 introduced support for System.DateTimeOffset, but only for SQL Server 2008 onwards. (If you’re using SQL Server 2005 or earlier or another database server, you’ll have to implement your own IUserType to store System.DateTimeOffset in two separate columns – one for the DateTime and the other for the timezone offset.) The additional code in DateTimeEntity.cs looks like this: public virtual DateTimeOffset CreationTimeAsDateTimeOffset { get; set; } The mapping file just needs the new property added: <property name="CreationTimeAsDateTimeOffset"/> Note that I don’t need to specify the type in the mapping as NHibernate can infer it from the property type in DateTimeEntity. The resulting output is: Original entity: Id: 95aa6c15-86f5-4398-aa9e-9e7600ae4580 CreationTimeAsDateTime: 2011-01-25 10:34:30 AM (Local) CreationTimeAsLocalDateTime: 2011-01-25 10:34:30 AM (Local) CreationTimeAsUtcDateTime: 2011-01-25 5:34:30 PM (Utc) CreationTimeAsDateTimeOffset: 2011-01-25 10:34:30 AM -07:00 Reloaded entity: Id: 95aa6c15-86f5-4398-aa9e-9e7600ae4580 CreationTimeAsDateTime: 2011-01-25 10:34:30 AM (Unspecified) CreationTimeAsLocalDateTime: 2011-01-25 10:34:30 AM (Local) CreationTimeAsUtcDateTime: 2011-01-25 5:34:30 PM (Utc) CreationTimeAsDateTimeOffset: 2011-01-25 10:34:30 AM -07:00 Support for DateTime2, Date, and Time Let’s look at some C# and the corresponding mapping file for these types: public virtual DateTime CreationTimeAsDateTime2 { get; set; } public virtual DateTime CreationTimeAsDate { get; set; } public virtual DateTime CreationTimeAsTime { get; set; } public virtual TimeSpan CreationTimeAsTimeAsTimeSpan { get; set; } public virtual TimeSpan CreationTimeAsTimeSpan { get; set; } Modifications to the hbm.xml: <property name="CreationTimeAsDateTime2" type="DateTime2"/> <property name="CreationTimeAsDate" type="Date"/> <property name="CreationTimeAsTime" type="Time"/> <property name="CreationTimeAsTimeAsTimeSpan" type="TimeAsTimeSpan"/> <property name="CreationTimeAsTimeSpan"/> We’ll examine each of these in turn… DbType.DateTime2 is a higher precision, wider range version of DbType.DateTime. DbType.DateTime maps to the datetime (or smalldatetime) SQL type, which has a range of 1753-01-01 to 9999-12-31. DbType.DateTime2 maps to the datetime2 SQL type, which has a range of 0001-01-01 to 9999-12-31. (Precision can be as high as 1/10 of a microsecond with a datetime2(7).) One of the niceties of DateTime2 is that an uninitialized DateTime struct (which has a value of 0001-01-01 12:00:00 AM (Unspecified)) does not cause a SqlTypeException with a SqlDateTime underflow. DbType.Date does just what it advertises. It represents a Date without a Time component. It is stored in the database as only a date. .NET does not have a Date type and NHibernate represents it via a DateTime with the time portion set to 12:00:00 AM. I personally prefer to define my own Date class, which has no time component, and create an IUserType to handle the mapping. My custom Date class can handle the time truncation and provide a more natural programing model for my domain, but that’s a post for another day. Time-related DbTypes stores just the time, but no date. In .NET, there is no Time class and so NHibernate uses a DateTime with the date component set to 1753-01-01, the minimum value for a SQL datetime or a System.TimeSpan – depending on the DbType that we choose. DbType.Time stores a System.DateTime in a time SQL type. DbType.TimeAsTimeSpan stores a System.TimeSpan as a time SQL type. DbType.TimeSpan stores a Syste.TimeSpan as a 64-bit integer (bigint) SQL type. As I mentioned for DbType.Date, I am more inclined to write my own Time class and custom IUserType to achieve a better programming model than relying on the .NET constructs of System.DateTime and System.TimeSpan. (I typically use System.DateTime or System.TimeSpan as a field in my custom Date or Time class for storing the data, but provide my own API for consistently working with the data.) WARNING: Not all databases support all date/time SQL types. So before choosing .NET and SQL types for your entities, make sure that they’re available in all databases that you plan to support. Now we’ll take a look at these date/time types in action: Original entity: Id: 6b2fb9ff-8036-4c17-b9ef-9e7600bf37e3 CreationTimeAsDateTime: 2011-01-25 11:36:12 AM (Local) CreationTimeAsLocalDateTime: 2011-01-25 11:36:12 AM (Local) CreationTimeAsUtcDateTime: 2011-01-25 6:36:12 PM (Utc) CreationTimeAsDateTimeOffset: 2011-01-25 11:36:12 AM -07:00 CreationTimeAsDateTime2: 2011-01-25 11:36:12 AM (Local) CreationTimeAsDate: 2011-01-25 11:36:12 AM (Local) CreationTimeAsTime: 2011-01-25 11:36:12 AM (Local) CreationTimeAsTimeAsTimeSpan: 11:36:12.2688265 CreationTimeAsTimeSpan: 11:36:12.2688265 Reloaded entity: Id: 6b2fb9ff-8036-4c17-b9ef-9e7600bf37e3 CreationTimeAsDateTime: 2011-01-25 11:36:12 AM (Unspecified) CreationTimeAsLocalDateTime: 2011-01-25 11:36:12 AM (Local) CreationTimeAsUtcDateTime: 2011-01-25 6:36:12 PM (Utc) CreationTimeAsDateTimeOffset: 2011-01-25 11:36:12 AM -07:00 CreationTimeAsDateTime2: 2011-01-25 11:36:12 AM (Unspecified) CreationTimeAsDate: 2011-01-25 12:00:00 AM (Unspecified) CreationTimeAsTime: 1753-01-01 11:36:12 AM (Unspecified) CreationTimeAsTimeAsTimeSpan: 11:36:12.2700000 CreationTimeAsTimeSpan: 11:36:12.2688265 Summary As you have seen, NHibernate has a wide variety of options for mapping date/time-related types to and from the database. The right choice is highly dependent on your application and database server. I hope that this post has given you a few more tricks up your sleeve for effectively mapping date/time-related types using NHibernate. [Less]
Posted over 13 years ago by James Kovacs
[This article was originally published on my personal blog here. I hereby grant myself permission to re-publish it on NHForge.org.] [Code for this article is available on GitHub here.] One of the new features in NHibernate 3 is the addition of a ... [More] fluent API for configuring NHibernate through code. Fluent NHibernate has provided a fluent configuration API for awhile, but now we have an option built into NHibernate itself. (Personally I prefer the new Loquacious API to Fluent NHibernate’s configuration API as I find Loquacious more discoverable. Given that Fluent NHibernate is built on top of NHibernate, you can always use Loquacious with Fluent NHibernate too. N.B. I still really like Fluent NHibernate’s ClassMap<T>, automapping capabilities, and PersistenceSpecification<T>. So don’t take my preference regarding fluent configuration as a denouncement of Fluent NHibernate.) The fluent configuration API built into NHibernate is called Loquacious configuration and exists as a set of extensions methods on NHibernate.Cfg.Configuration. You can access these extension methods by importing in the NHibernate.Cfg.Loquacious namespace. var cfg = new Configuration(); cfg.Proxy(p => p.ProxyFactoryFactory<ProxyFactoryFactory>()) .DataBaseIntegration(db => { db.ConnectionStringName = "scratch"; db.Dialect<MsSql2008Dialect>(); db.BatchSize = 500; }) .AddAssembly(typeof(Blog).Assembly) .SessionFactory().GenerateStatistics(); On the second line, we configure the ProxyFactoryFactory, which is responsible for generating the proxies needed for lazy loading. The ProxyFactoryFactory type parameter (stuff between the <>) is in the NHibernate.ByteCode.Castle namespace. (I have a reference to the NHibernate.ByteCode.Castle assembly too.) So we’re using Castle to generate our proxies. We could also use LinFu or Spring. Setting db.ConnectionStringName causes NHibernate to read the connection string from the <connectionStrings/> config section of the [App|Web].config. This keeps your connection strings in an easily managed location without being baked into your code. You can perform the same trick in XML-based configuration by using the connection.connection_string_name property instead of the more commonly used connection.connection_string. Configuring BatchSize turns on update batching in databases, which support it. (Support is limited to SqlClient and OracleDataClient currently and relies on features of these drivers.) Updating batching allows NHibernate to group together multiple, related INSERT, UPDATE, or DELETE statements in a single round-trip to the database. This setting isn’t strictly necessary, but can give you a nice performance boost with DML statements. The value of 500 represents the maximum number of DML statements in one batch. The choice of 500 is arbitrary and should be tuned for your application. The assembly that we are adding is the one that contains our hbm.xml files as embedded resources. This allows NHibernate to find and parse our mapping metadata. If you have your metadata located in multiple files, you can call cfg.AddAssembly() multiple times. The last call, cfg.SessionFactory().GenerateStatistics(), causes NHibernate to output additional information about entities, collections, connections, transactions, sessions, second-level cache, and more. Although not required, it does provide additional useful information about NHibernate’s performance. Notice that there is no need to call cfg.Configure(). cfg.Configure() is used to read in configuration values from [App|Web].config (from the hibernate-configuration config section) or from hibernate.cfg.xml. If we’ve not using XML configuration, cfg.Configure() is not required. Loquacious and XML-based configuration are not mutually exclusive. We can combine the two techniques to allow overrides or provide default values – it all comes down to the order of the Loquacious configuration code and the call to cfg.Configure(). var cfg = new Configuration(); cfg.Configure(); cfg.Proxy(p => p.ProxyFactoryFactory<ProxyFactoryFactory>()) .SessionFactory().GenerateStatistics(); Note the cfg.Configure() on the second line. We read in the standard XML-based configuration and then force the use of a particular ProxyFactoryFactory and generation of statistics via Loquacious configuration. If instead we make the call to cfg.Configure() after the Loquacious configuration, the Loquacious configuration provides default values, but we can override any and all values using XML-based configuration. var cfg = new Configuration(); cfg.Proxy(p => p.ProxyFactoryFactory<ProxyFactoryFactory>()) .DataBaseIntegration(db => { db.ConnectionStringName = "scratch"; db.Dialect<MsSql2008Dialect>(); db.BatchSize = 500; }) .AddAssembly(typeof(Blog).Assembly) .SessionFactory().GenerateStatistics(); cfg.Configure(); You can always mix and match the techniques by doing some Loquacious configuration before and som after the call to cfg.Configure(). WARNING: If you call cfg.Configure(), you need to have <hibernate-configuration/> in your [App|Web].config or a hibernate.cfg.xml file. If you don’t, you’ll throw a HibernateConfigException. They can contain an empty root element, but it needs to be there. Another option would be to check whether File.Exists(‘hibernate.cfg.xml’) before calling cfg.Configure(). So there you have it. The new Loquacious configuration API in NHibernate 3. This introduction was not meant as a definitive reference, but as a jumping off point. I would recommend that you explore other extension methods in the NHibernate.Cfg.Loquacious namespace as they provide the means to configure the 2nd-leve cache, current session context, custom LINQ functions, and more. Anything you can do in XML-based configuration can now be accomplished with Loquacious or the existing methods on NHibernate.Cfg.Configuration. So get out there and start coding – XML is now optional… [Less]
Posted over 13 years ago by Jose Romaniello
I can’t count with one hand, how many times I’ve seen this mapping in my few years using nhibernate: <class name="Profile"> <composite-id> <key-many-to-one name="User" column="UserId" class="User" /> ... [More] </composite-id> <!-- properties --> </class> The explanation that comes when you ask to someone why he did such thing, is almost the following: Well… It is the only way you can use a many-to-one as a primary key. There are two things that you can see in that quote: The developer found a workaround (and sometimes he is proud) He wants a many to one!   First of all, it is not a many-to-one, because you CAN’T have many profiles for one user. It is not a many-to-one, so you may try to find a better way to tell nhibernate what you want. On the other hand, it is not a composite-id if you only have one thing. This kind of relationship is named “one-to-one” because you have only one profile for only one user. As you can read in the reference documentation ( here ) : There are two varieties of one-to-one association: primary key associations unique foreign key associations If we want to have such schema (UserId as the PK) we are talking about the first one. The mapping is as follow: <class name="UserProfile" > <id column="Id"> <generator class="foreign"> <param name="property">User</param> </generator> </id> <one-to-one name="User" class="User" constrained="true"/> </class> And the class is pretty easy too: public class UserProfile { public int Id { get; set; } public User User { get; set; } //other properties } This tell nhibernate to use the id from User when inserting a Profile. There rest is any other class. If you want to get an UserProfile, without getting the user, you can execute: session.Get<UserProfile>(userId). As I said, it works as any other kind of class. [Less]
Posted over 13 years ago by diegose
There's a question that seems to appear at least once a month in StackOverflow or the NH users group: How can I add properties to a many-to-many relationship? The user of course means adding columns to the "junction" table used to store the ... [More] many-to-many relationship, and being able to populate them without changing his object model. That makes some sense from a relational perspective (a table is just a table after all), but not from an OOP one: a relationship does not have properties. The easiest solution, of course, is to map the relationship as an entity, with regular one-to-many collections from both sides. This would be the end of it... if it weren't for the fact that it's not what the user wants. If you dig a little further, you'll find that, in most of his use cases, the additional properties don't matter. They are used for auditing purposes, activation/deactivation, etc. So, how can we code such a model? Answer: using LINQ-to-objects. Let's consider a typical Users - Roles relationship (a user has many roles, a role is applied by many users). Step 1: Create the entities 1 public class User 2 { 3 public User() 4 { 5 _UserRoles = new List<UserRole>(); 6 } 7 8 public virtual string Name { get; set; } 9 10 ICollection<UserRole> _UserRoles; 11 protected internal virtual ICollection<UserRole> UserRoles 12 { 13 get { return _UserRoles; } 14 } 15 } 16 17 public class Role 18 { 19 public Role() 20 { 21 _UserRoles = new List<UserRole>(); 22 } 23 24 public virtual string Description { get; set; } 25 26 ICollection<UserRole> _UserRoles; 27 protected internal virtual ICollection<UserRole> UserRoles 28 { 29 get { return _UserRoles; } 30 } 31 } 32 33 public class UserRole 34 { 35 public virtual User User { get; set; } 36 public virtual Role Role { get; set; } 37 public virtual DateTime AssignedDate { get; set; } 38 } Step 2: Map them (only one side shown; the other is exactly the same) 1 <class name="User"> 2 <id ...>...</id> 3 <property name="Name" /> 4 <bag name="UserRoles" access="nosetter.pascalcase-underscore" 5 inverse="true" cascade="all,delete-orphan"> 6 <key column="UserId" on-delete="cascade" /> 7 <one-to-many class="UserRole" /> 8 </bag> 9 </class> 10 <class name="Role"> 11 <id ...>...</id> 12 <property name="Description" /> 13 <bag name="UserRoles" access="nosetter.pascalcase-underscore" 14 inverse="true" cascade="all,delete-orphan"> 15 <key column="RoleId" on-delete="cascade" /> 16 <one-to-many class="UserRole" /> 17 </bag> 18 </class> 19 <class name="UserRole"> 20 <id ...>...</id> 21 <many-to-one name="UserId" /> 22 <many-to-one name="RoleId" /> 23 <property name="AssignedDate" /> 24 </class> As far as NHibernate is concerned, that is all there is. Now let's make it usable. Step 3: Add the projection and method (one side shown) 1 public class User 2 { 3 public virtual IEnumerable<Role> Roles 4 { 5 get { return from ur in UserRoles select ur.Role; } 6 } 7 8 public virtual void Add(Role role) 9 { 10 var userRole = new UserRole 11 { 12 User = this, 13 Role = role, 14 AssignedDate = DateTime.Now 15 }; 16 UserRoles.Add(userRole); 17 role.UserRoles.Add(userRole); 18 } 19 20 public virtual void Remove(Role role) 21 { 22 var userRole = UserRoles.Single(r => r.Role == role); 23 UserRoles.Remove(userRole); 24 role.UserRoles.Remove(userRole); 25 } 26 } Voilà! That's all you need to use it. Note that I made the UserRoles collection protected internal. If you have code that actually needs to manipulate it, you can expose it. One small catch: you can't use the Roles projection in queries, because NHibernate knows nothing about it. Still, this should be enough for the expected use cases.   Originally published in my blog, http://sessionfactory.blogspot.com/ [Less]
Posted over 13 years ago by Jose Romaniello
I’m currently working in a project with a legacy database. The database use a convention, where every table has his own identifier (one-column) and it must be generated by a sequence. The name of the sequence follows a convention, first 26 ... [More] characters of the Table name + the subfix “seq”. For instance, the sequence for the table Nationality is NATIONALITY_SEQ. I take advantage of the convention as follows: I’ve mapped everything as “native”. I wrote a new id generator as follows: public class SequenceByConvention : SequenceGenerator { public override void Configure(IType type, IDictionary<string, string> parms, Dialect dialect) { parms["sequence"] = GetSequenceNameFromTableName(parms["target_table"]); base.Configure(type, parms, dialect); } private static string GetSequenceNameFromTableName(string tableName) { return tableName.Substring(0, Math.Min(26, tableName.Length)) + "_SEQ"; } } And finally this is the dialect for this project: public class MyDialect : Oracle10gDialect { public override System.Type NativeIdentifierGeneratorClass { get { return typeof (SequenceByConvention); } } } [Less]
Posted over 13 years ago by John Davidson
This post is the second one about the NHibernate Bootstrapper. The first is located here. The first post set up the project structure, introduced the generic DAO, and demonstrated the SessionPerRequest implementation in an IHttpModule. This post will ... [More] factor the reference to NHibernate out of the web application project and cover some unit testing techniques.  Programmers that are not familiar with SOLID should review the Wikipedia page and the references there. The first post noted that the version of NHibernate Bootstrapper presented there was not suitable for use in anything other than a demonstration program. The version of the solution discussed in this post is suitable for use in a small-scale system where there no more than 15 classes involved. The version following this post should be suitable for even the largest deployments, though there will be at least one additional post that refines the capabilities of an enterprise ready solution. The project sources are in a zip file located here and are updated to use the NHibernate 3.0.0 GA release. PresentationManager The solution in the first post used NHibernate references in the web application project. In this version of the solution those references have been moved to the presenter project. Now the solution is taken on the characteristics of the Model-View-Presenter (MVP) , discussed by Martin Fowler here, and later refined into a Supervising Presenter and Passive View. The solution employed here follows the Passive View pattern, where the view has no direct interaction with the model. The solution builds on 2 Code Project articles, originally released in Jul 2006. The first reference used is Model View Presenter with ASP.Net by Bill McCafferty and the second is Advancing the Model-View-Presenter Pattern – Fixing the Common Problems by Acoustic. There are a number of reasons for using the MVP pattern, but the most important of them is the enabling of testing. The second half of this post will show how it becomes possible to test the code behind of an aspx page. In the first post the BusinessServices project, that would hold the presenters, was empty. Now there is a Presentation Manager and a Presenter classes. The PresentationManager is able to register the view of the ASP.Net page and associate it with the correct presenter. It is also remarkable in that it automatically instantiates the correct presenter for use by the ASP.Net page. This is done in the LoadPresenter method. The auto-instantiation is how ASP.Net pages are able to function with only a reference to the PresenterTypeAttribute in the web application project.     PresentationManager.cs using System; using Infrastructure;   namespace BusinessServices {     public static class PresentationManager     {         public static T RegisterView<T>(Type presenterType, IView myView) where T : Presenter         {             return RegisterView<T>(presenterType, myView, null);         }           public static T RegisterView<T>(Type presenterType, IView view, IHttpSessionProvider httpSession) where T : Presenter         {             return (LoadPresenter(presenterType, view, httpSession)) as T;         }           public static void RegisterView(Type presenterType, IView view)         {             RegisterView(presenterType, view, null);         }           public static void RegisterView(Type presenterType, IView view, IHttpSessionProvider httpSession)         {             LoadPresenter(presenterType, view, httpSession);         }           private static Object LoadPresenter(Type presenterType, IView view, IHttpSessionProvider httpSession)         {             int arraySize = ((httpSession == null) ? 1 : 2);             Object[] constructorParams = new Object[arraySize];               constructorParams[0] = view;               if (arraySize.Equals(2))             {                 constructorParams[1] = httpSession;             }             return Activator.CreateInstance(presenterType, constructorParams);         }       } }       The PresenterTypeAttribute is what each page uses to drive SelfRegister. This is the mechanism that ties the individual web pages to the appropriate presenter in an automated fashion. This is one aspect of a poor man’s Inversion of Control without requiring a separate container to hold the various dependencies.   PresenterTypeAttribute.cs using System; using System.Collections.Generic; using System.Linq; using System.Text;   namespace BusinessServices {     //[AttributeUsage(AttributeTargets.All, Inherited = true)]     public class PresenterTypeAttribute : Attribute     {         private Type _presenterType;           public PresenterTypeAttribute(Type presenterType)         {             _presenterType = presenterType;         }           public Type PresenterType         {             get { return _presenterType; }             set { _presenterType = value; }         }     } }   Revised Web Application Project The code behind for the web page has been revised to work with a presenter. You will note that a large amount of code that was in the original code behind file has now been commented out, as it has been revised slightly and moved to the the PersonPresenter class. The code behind file is now left with just event declarations, a number of properties and the occasional method for working with gridview or dropdown controls and the Page_Load event. All that remains in the code behind are methods and properties that are referencing System.Web, while the various presenter classes have no reference to System.Web. It is important to note that the removal of the reference to System.Web in the presenter classes is what enables a high degree of code coverage in the Unit Tests. Default.aspx.cs (Part 1) using System; using System.Collections.Generic; using System.Web.UI; using System.Web.UI.WebControls; using BusinessServices; using BusinessServices.Interfaces; using BusinessServices.Presenters; using DataServices.Person;   namespace WebNHibernate {     [PresenterType(typeof(PersonPresenter))]     public partial class _Default : BasePage, IPersonView     {           public event GridViewBtnEvent OnEditCommand;         public event GridViewBtnEvent OnDeleteCommand;         public event EmptyBtnEvent OnRefreshPersonGrid;         public event EmptyBtnEvent OnSaveEditPerson;         public event EmptyBtnEvent OnClearEditPerson;         //added event for presenter         public event EmptyEvent OnPageLoadNoPostback;           //private ISession m_session = null;           protected void Page_Load(object sender, EventArgs e)         {             //added for the presenter             base.SelfRegister(this);               //OnEditCommand += new GridViewBtnEvent(_view_OnEditCommand);             //OnDeleteCommand += new GridViewBtnEvent(_view_OnDeleteCommand);             //OnRefreshPersonGrid += new EmptyBtnEvent(_view_OnRefreshPersonGrid);             //OnSaveEditPerson += new EmptyBtnEvent(_view_OnSaveEditPerson);             //OnClearEditPerson += new EmptyBtnEvent(_view_OnClearEditPerson);               //if (m_session == null)             //    m_session = SessionManager.SessionFactory.GetCurrentSession();               if (!Page.IsPostBack)             {                 //added line below for presenter                 OnPageLoadNoPostback();                 //IList<PersonDto> gvData = Get_PersonData();                 //Fill_gvPerson(gvData);             }                     } Default.aspx.cs (Part 2) protected void gvPerson_OnRowCommand(object sender, GridViewCommandEventArgs e) {     Guid id = new Guid(gvPerson.DataKeys[Convert.ToInt32(e.CommandArgument)].Value.ToString());     if (e.CommandName == "EditRow")         OnEditCommand(id);     else if (e.CommandName == "DeleteRow")         OnDeleteCommand(id); }   //public void _view_OnEditCommand(Guid id) //{ //    if (m_session == null) //        m_session = SessionManager.SessionFactory.GetCurrentSession();   //    PersonDAOImpl dao = new PersonDAOImpl(m_session); //    Person pers = dao.GetByID(id);   //    txtPersonIdValue = pers.Id.ToString(); //    txtFirstNameValue = pers.FirstName; //    txtLastNameValue = pers.LastName; //    txtEmailValue = pers.Email; //    txtUserIdValue = pers.UserID;   //    pers = null; //    dao = null; //}   //public void _view_OnDeleteCommand(Guid id) //{ //    if (m_session == null) //        m_session = SessionManager.SessionFactory.GetCurrentSession();   //    PersonDAOImpl dao = new PersonDAOImpl(m_session); //    using (var tx = m_session.BeginTransaction()) //    { //        dao.Delete(id); //        tx.Commit(); //    }   //    dao = null; //    _view_OnRefreshPersonGrid(); //} Default.aspx.cs (Part 3) //public void _view_OnRefreshPersonGrid() //{ //    IList<PersonDto> gvData = Get_PersonData(); //    Fill_gvPerson(gvData); //}   //public void _view_OnSaveEditPerson() //{   //    if (m_session == null) //        m_session = SessionManager.SessionFactory.GetCurrentSession();   //    Guid editId = new Guid(); //    Person editPers = new Person();   //    if (!string.IsNullOrEmpty(txtPersonIdValue)) //        editId = new Guid(txtPersonIdValue);   //    PersonDAOImpl dao = new PersonDAOImpl(m_session); //    using (var tx = m_session.BeginTransaction()) //    { //        if (editId.ToString().Length == 36) //            editPers = dao.GetByID(editId); //        editPers.FirstName = txtFirstNameValue; //        editPers.LastName = txtLastNameValue; //        editPers.Email = txtEmailValue; //        editPers.UserID = txtUserIdValue;   //        editPers = dao.Save(editPers); //        tx.Commit(); //    }   //    editPers = null; //    dao = null; //    _view_OnRefreshPersonGrid(); //    _view_OnClearEditPerson(); //}   //public void _view_OnClearEditPerson() //{ //    txtPersonIdValue = null; //    txtFirstNameValue = null; //    txtLastNameValue = null; //    txtEmailValue = null; //    txtUserIdValue = null; //} Default.aspx.cs (Part 4) protected void btnRefresh_Click(object sender, EventArgs e) {     OnRefreshPersonGrid(); }   protected void btnSave_Click(object sender, EventArgs e) {     OnSaveEditPerson(); }   protected void btnClear_Click(object sender, EventArgs e) {     OnClearEditPerson(); }   public void Fill_gvPerson(IList<PersonDto> data) {     gvPerson.DataSource = data;     gvPerson.DataBind(); }   //public IList<PersonDto> Get_PersonData() //{ //    IList<PersonDto> retVal = null;   //    if (m_session == null) //        m_session = SessionManager.SessionFactory.GetCurrentSession();   //    ICriteria crit = m_session.CreateCriteria(typeof(Person)); //    PersonDAOImpl dao = new PersonDAOImpl(m_session);   //    IList<Person> people = dao.GetByCriteria(crit); //    retVal = (from person in people //              select new PersonDto //                  { //                      PersonID = person.Id, //                      FirstName = person.FirstName, //                      LastName = person.LastName, //                      Email = person.Email, //                      UserID = person.UserID //                  }).ToList<PersonDto>(); //    crit = null; //    dao = null; //    people = null;   //    return retVal; //} Default.aspx.cs (Part 5)         public string txtPersonIdValue         {             get { return txtPersonID.Text; }             set { txtPersonID.Text = value; }         }           public string txtFirstNameValue         {             get { return txtFirstName.Text; }             set { txtFirstName.Text = value; }         }           public string txtLastNameValue         {             get { return txtLastName.Text; }             set { txtLastName.Text = value; }         }           public string txtEmailValue         {             get { return txtEmail.Text; }             set { txtEmail.Text = value; }         }           public string txtUserIdValue         {             get { return txtUserID.Text; }             set { txtUserID.Text = value; }         }       } } BasePage The web project has had a BasePage class added to reuse element and methods common to more than just a single web page. This includes the SelfRegister method and various properties for RequestString, RequestUrl and IsPostBack. These are sample functions which have common utility throughout all the web pages. It is here that additional utility methods and functions with similar commonality would be added. BasePage.cs using System; using System.Collections.Specialized; using BusinessServices;   namespace WebNHibernate {     public class BasePage : System.Web.UI.Page, IView     {           private string _requestUrl;           protected T RegisterView<T>() where T : Presenter         {             return PresentationManager.RegisterView<T>(typeof(T), this, new HttpSessionProvider());         }           protected void SelfRegister(System.Web.UI.Page page)         {             if (page != null && page is IView)             {                 object[] attributes = page.GetType().GetCustomAttributes(typeof(PresenterTypeAttribute), true);                   if (attributes != null && attributes.Length > 0)                 {                     foreach (Attribute viewAttribute in attributes)                     {                         if (viewAttribute is PresenterTypeAttribute)                         {                             PresentationManager.RegisterView((viewAttribute as PresenterTypeAttribute).PresenterType,                                 page as IView, new HttpSessionProvider());                             break;                         }                     }                 }             }         }           public NameValueCollection RequestString         {             get { return Request.QueryString; }         }           public string RequestUrl         {             get { return Request.RawUrl;  }             set { _requestUrl = value; }         }           public bool IsPostback         {             get { return this.IsPostBack; }         }     } } PersonPresenter The PersonPresenter class now inherits the code that was commented out in the code behind file. It must also setup event listeners for events that will be raised from the web page. It is these event listeners that improve the testability of the solution, as now this functionality can be unit tested separate from any System.Web dependency. At this point the various presenters have a reference to NHibernate and work directly with the data access layer. The next iteration of the bootstrapper will refactor the presenter and place a data services layer between the presenter and the data access layer. The presenter will then no longer reference NHibernate. PersonPresenter.cs (Part 1) using System; using System.Collections.Generic; using System.Linq; using NHibernate; using NHibernateDAO; using NHibernateDAO.DAOImplementations; using DataServices.Person; using DomainModel.Person; using Infrastructure; using BusinessServices.Interfaces;   namespace BusinessServices.Presenters {     public class PersonPresenter : Presenter     {         private ISession m_session = null;             public PersonPresenter(IPersonView view)             : this(view, null)         { }           public PersonPresenter(IPersonView view, IHttpSessionProvider httpSession)             : base(view, httpSession)         {             IPersonView _personView = null;             _personView = base.GetView<IPersonView>();             _personView.OnEditCommand += new GridViewBtnEvent(_view_OnEditCommand);             _personView.OnDeleteCommand += new GridViewBtnEvent(_view_OnDeleteCommand);             _personView.OnRefreshPersonGrid += new EmptyBtnEvent(_view_OnRefreshPersonGrid);             _personView.OnSaveEditPerson += new EmptyBtnEvent(_view_OnSaveEditPerson);             _personView.OnClearEditPerson += new EmptyBtnEvent(_view_OnClearEditPerson);             _personView.OnPageLoadNoPostback += new EmptyEvent(_personView_OnPageLoadNoPostback);         }             public void _personView_OnPageLoadNoPostback()         {             IPersonView _personView = base.GetView<IPersonView>();             IList<PersonDto> gvData = Get_PersonData();             _personView.Fill_gvPerson(gvData);         } PersonPresenter.cs (Part 2) public void _view_OnEditCommand(Guid id) {     IPersonView _personView = base.GetView<IPersonView>();     m_session = SessionManager.SessionFactory.GetCurrentSession();       PersonDAOImpl dao = new PersonDAOImpl(m_session);     DomainModel.Person.Person pers = dao.GetByID(id);       _personView.txtPersonIdValue = pers.Id.ToString();     _personView.txtFirstNameValue = pers.FirstName;     _personView.txtLastNameValue = pers.LastName;     _personView.txtEmailValue = pers.Email;     _personView.txtUserIdValue = pers.UserID;       pers = null;     dao = null; }   public void _view_OnDeleteCommand(Guid id) {     IPersonView _personView = base.GetView<IPersonView>();     m_session = SessionManager.SessionFactory.GetCurrentSession();       PersonDAOImpl dao = new PersonDAOImpl(m_session);     using (var tx = m_session.BeginTransaction())     {         dao.Delete(id);         tx.Commit();     }       dao = null;     _view_OnRefreshPersonGrid(); }   public void _view_OnRefreshPersonGrid() {     IPersonView _personView = base.GetView<IPersonView>();     IList<PersonDto> gvData = Get_PersonData();     _personView.Fill_gvPerson(gvData); } PersonPresenter.cs (Part 3) public void _view_OnSaveEditPerson() {     IPersonView _personView = base.GetView<IPersonView>();     m_session = SessionManager.SessionFactory.GetCurrentSession();       Guid editId = new Guid();     Person editPers = new Person();       if (!string.IsNullOrEmpty(_personView.txtPersonIdValue))         editId = new Guid(_personView.txtPersonIdValue);       PersonDAOImpl dao = new PersonDAOImpl(m_session);     using (var tx = m_session.BeginTransaction())     {             if ((editId != null) && (!editId.Equals(System.Guid.Empty))) //was a bug here                 editPers = dao.GetByID(editId);             editPers.FirstName = _personView.txtFirstNameValue;             editPers.LastName = _personView.txtLastNameValue;             editPers.Email = _personView.txtEmailValue;             editPers.UserID = _personView.txtUserIdValue;               editPers = dao.Save(editPers);             tx.Commit();     }       editPers = null;     dao = null;     _view_OnRefreshPersonGrid();     _view_OnClearEditPerson(); }   public void _view_OnClearEditPerson() {     IPersonView _personView = base.GetView<IPersonView>();       _personView.txtPersonIdValue = null;     _personView.txtFirstNameValue = null;     _personView.txtLastNameValue = null;     _personView.txtEmailValue = null;     _personView.txtUserIdValue = null; } PersonPresenter.cs (Part 4)         public IList<PersonDto> Get_PersonData()         {             IPersonView _personView = base.GetView<IPersonView>();             IList<PersonDto> retVal = null;               m_session = SessionManager.SessionFactory.GetCurrentSession();               ICriteria crit = m_session.CreateCriteria(typeof(Person));             PersonDAOImpl dao = new PersonDAOImpl(m_session);               IList<Person> people = dao.GetByCriteria(crit);             retVal = (from person in people                       select new PersonDto                       {                           PersonID = person.Id,                           FirstName = person.FirstName,                           LastName = person.LastName,                           Email = person.Email,                           UserID = person.UserID                       }).ToList<PersonDto>();             crit = null;             dao = null;             people = null;               return retVal;         }           public IPersonView personView         {             get { return base.GetView<IPersonView>(); }         }       } }   The interface for the PersonView web page has also had to be revised. Here the methods that are commented out have had the implementations moved from the web page to the presenter. One new event has been added for the presenter. IPersonView.cs using System.Collections.Generic; using DataServices.Person;   namespace BusinessServices.Interfaces {       public interface IPersonView : IView     {           event GridViewBtnEvent OnEditCommand;         event GridViewBtnEvent OnDeleteCommand;         event EmptyBtnEvent OnRefreshPersonGrid;         event EmptyBtnEvent OnSaveEditPerson;         event EmptyBtnEvent OnClearEditPerson;         //the event below had to be added for the presenter         event EmptyEvent OnPageLoadNoPostback;           //void _view_OnEditCommand(Guid id);         //void _view_OnDeleteCommand(Guid id);         //void _view_OnRefreshPersonGrid();         //void _view_OnSaveEditPerson();         //void _view_OnClearEditPerson();           string txtPersonIdValue { get; set; }         string txtFirstNameValue { get; set; }         string txtLastNameValue { get; set; }         string txtEmailValue { get; set; }         string txtUserIdValue { get; set; }           void Fill_gvPerson(IList<PersonDto> data);         //IList<PersonDto> Get_PersonData();         } } Data Access Objects Improvements The data access objects have been expanded to include support for NHibernate LINQ, which is now part of the core. Also support for selection of an unique object has been included, rather than always returning an IList. This means that there has been an update to the IRead.cs file as shown below.   IRead.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using DomainModel; using NHibernate;   namespace NHibernateDAO {     public interface IRead<TEntity> where TEntity : Entity     {         TEntity GetByID(Guid ID);         IList<TEntity> GetByCriteria(ICriteria criteria);         TEntity GetUniqByCriteria(ICriteria criteria);         IList<TEntity> GetByQueryable(IQueryable<TEntity> queryable);         TEntity GetUniqByQueryable(IQueryable<TEntity> queryable);     } }     The implementation file for the data access objects has been revised to include the implementation details for LINQ and unique object support.   GenericDAOImpl.cs (Pt 1) using System; using System.Collections.Generic; using System.Linq; using System.Text; using DomainModel; using NHibernate;   namespace NHibernateDAO {     public class GenericDAOImpl<TEntity> : IRead<TEntity>, ISave<TEntity> where TEntity : Entity     {         public GenericDAOImpl(ISession Session)         {             m_Session = Session;         }           protected readonly ISession m_Session;           public TEntity GetByID(Guid ID)         {             if (!m_Session.Transaction.IsActive)             {                 TEntity retval;                 using (var tx = m_Session.BeginTransaction())                 {                     retval = m_Session.Get<TEntity>(ID);                     tx.Commit();                     return retval;                 }             }             else             {                 return m_Session.Get<TEntity>(ID);             }         }     GenericDAOImpl.cs (Pt 2) public IList<TEntity> GetByCriteria(ICriteria criteria) {     if (!m_Session.Transaction.IsActive)     {         IList<TEntity> retval;         using (var tx = m_Session.BeginTransaction())         {             retval = criteria.List<TEntity>();             tx.Commit();             return retval;         }     }     else     {         return criteria.List<TEntity>();     } }   public TEntity GetUniqByCriteria(ICriteria criteria) {     if (!m_Session.Transaction.IsActive)     {         TEntity retval;         using (var tx = m_Session.BeginTransaction())         {             retval = criteria.UniqueResult<TEntity>();             tx.Commit();             return retval;         }     }     else     {         return criteria.UniqueResult<TEntity>();     } }     GenericDAOImpl.cs (Pt 3) public IList<TEntity> GetByQueryable(IQueryable<TEntity> queryable) {     if (!m_Session.Transaction.IsActive)     {         IList<TEntity> retval;         using (var tx = m_Session.BeginTransaction())         {             retval = queryable.ToList<TEntity>();             tx.Commit();             return retval;         }     }     else     {         return queryable.ToList<TEntity>();     } }   public TEntity GetUniqByQueryable(IQueryable<TEntity> queryable) {     if (!m_Session.Transaction.IsActive)     {         TEntity retval;         using (var tx = m_Session.BeginTransaction())         {             retval = queryable.Single<TEntity>();             tx.Commit();             return retval;         }     }     else     {         return queryable.Single<TEntity>();     } }       GenericDAOImpl.cs (Pt 4)         public TEntity Save(TEntity entity)         {             if (!m_Session.Transaction.IsActive)             {                 using (var tx = m_Session.BeginTransaction())                 {                     m_Session.SaveOrUpdate(entity);                     tx.Commit();                 }             }             else             {                 m_Session.SaveOrUpdate(entity);             }             return entity;         }       } }     Unit Test The unit testing capabilities of the solution have been expanded. It starts with a fake PersonView class in the unit test project that includes functionality similar to that in the web page, but implemented without any reference to System.Web PersonView.cs (Part 1) using System; using System.Collections.Generic; using BusinessServices; using BusinessServices.Interfaces; using BusinessServices.Presenters; using DataServices.Person;   namespace BootstrapperUnitTests.Views {     [PresenterType(typeof(PersonPresenter))]     public class PersonView : BaseView, IPersonView     {         private bool blnRegistered = false;           private string _txtPersonID;         private string _txtFirstName;         private string _txtLastName;         private string _txtEmail;         private string _txtUserID;         private static bool postBack;           private IList<PersonDto> _gvPerson;           public event GridViewBtnEvent OnEditCommand;         public event GridViewBtnEvent OnDeleteCommand;         public event EmptyBtnEvent OnRefreshPersonGrid;         public event EmptyBtnEvent OnSaveEditPerson;         public event EmptyBtnEvent OnClearEditPerson;         public event EmptyEvent OnPageLoadNoPostback;           public PersonView()         {             postBack = false;             base.SelfRegister(this);             blnRegistered = true;         }           /// <summary>         /// This is used by the test subsystem to avoid the self-registry action and allow normal         /// object creation.         /// </summary>         /// <param name="noRegister"></param>         public PersonView(bool noRegister)         {             blnRegistered = false;             postBack = false;         } PersonView.cs (Part 2) public void Fill_gvPerson(IList<PersonDto> data) {     _gvPerson = data; }   protected internal IList<PersonDto> GvPerson {     get { return _gvPerson; } }   public void FireEvent_OnEditCommand(Guid id) {     OnEditCommand(id); }   public void FireEvent_OnDeleteCommand(Guid id) {     OnDeleteCommand(id); }   public void FireEvent_OnRefreshPersonGrid() {     OnRefreshPersonGrid(); }   public void FireEvent_OnSaveEditPerson() {     if (_txtUserID != null)     {         OnSaveEditPerson();     } }   public void FireEvent_OnClearEditPerson() {     OnClearEditPerson(); }   public void FireEvent_OnPageLoadNoPostback() {     OnPageLoadNoPostback(); }   PersonView.cs (Part 3)         public string txtPersonIdValue         {             get { return _txtPersonID; }             set { _txtPersonID = value; }         }           public string txtFirstNameValue         {             get { return _txtFirstName; }             set { _txtFirstName = value; }         }           public string txtLastNameValue         {             get { return _txtLastName; }             set { _txtLastName = value; }         }           public string txtEmailValue         {             get { return _txtEmail; }             set { _txtEmail = value; }         }           public string txtUserIdValue         {             get { return _txtUserID; }             set { _txtUserID = value; }         }           bool IView.IsPostback         {             get             {                 if (!postBack)                 {                     postBack = true;                     return false;                 }                 else                     return true;             }         }     } }   BasePage.cs must also be copied to the unit test project, and it also no longer references System.Web. It is renamed to BaseView.cs in the actual solution. You will note that the property IsPostBack throws a NotImplementedException, but it could be set to a read/write property and provide values through an external property setter in the tests. BaseView.cs using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text; using BusinessServices;   namespace BootstrapperUnitTests {     public class BaseView : IView     {         private string _requestUrl;         private NameValueCollection _queryString = new NameValueCollection();                    protected T RegisterView<T>() where T : Presenter         {             return PresentationManager.RegisterView<T>(typeof(T), this, null);         }           protected void SelfRegister(IView page)         {             if (page != null && page is IView)             {                 object[] attributes = page.GetType().GetCustomAttributes(typeof(PresenterTypeAttribute), true);                   if (attributes != null && attributes.Length > 0)                 {                     foreach (Attribute viewAttribute in attributes)                     {                         if (viewAttribute is PresenterTypeAttribute)                         {                             PresentationManager.RegisterView((viewAttribute as PresenterTypeAttribute).PresenterType,                                 page as IView, null);                             break;                         }                     }                 }             }         }           public NameValueCollection RequestString         {             get { return _queryString; }         }           public string RequestUrl         {             get { return _requestUrl; }             set { _requestUrl = value; }         }           public bool IsPostback         {             get { throw new NotImplementedException(); }         }     } }     The tests themselves have been expanded and now show a considerably increased code coverage. In doing so the tests are now a combination of unit tests and integration tests, but I would argue that this is desirable as it increases the overall reliability of the solution, which is the purpose of automated tests. PersonPresenterTests.cs (Pt 1) using System; using System.Collections.Generic; using NUnit.Framework; using DomainModel.Person; using NHibernate; using NHibernate.Context; using NHibernateDAO; using NHibernateDAO.DAOImplementations; using BootstrapperUnitTests.Views; using BootstrapperUnitTests.TestData; using BusinessServices.Interfaces; using BusinessServices.Presenters; using DataServices.Person;   namespace BootstrapperUnitTests.PresenterTests {     [TestFixture]     public class PersonPresenterTests     {         private ISession m_session;           [TestFixtureSetUp]         public void TestFixtureSetup()         {             var session = SessionManager.SessionFactory.OpenSession();             CallSessionContext.Bind(session);         }           [TestFixtureTearDown]         public void TestFixtureTeardown()         {             PersonTestData ptd = new PersonTestData();             ptd.RemoveAllPersonEntries();               var session = CallSessionContext.Unbind(SessionManager.SessionFactory);             if (session != null)             {                 if (session.Transaction != null && session.Transaction.IsActive)                 {                     session.Transaction.Rollback();                 }                 else                     session.Flush();                   session.Close();             }               session.Dispose();         } PersonPresenterTests.cs (Pt 2) [Test] public void View_OnEditCommandTest() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(1);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       PersonPresenter _pp =new PersonPresenter(new PersonView(false));       daoPerson = null;     m_session = null;       _pp._view_OnEditCommand(people[0].Id);     IPersonView _pv = _pp.personView;     Assert.IsTrue(new Guid(_pv.txtPersonIdValue).Equals(people[0].Id));     Assert.IsTrue(_pv.txtUserIdValue.Equals(people[0].UserID));       ptd = null;     _pv = null;     _pp = null; } PersonPresenterTests.cs (Pt 3) [Test] public void FireEvent_OnEditCommandTest() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(1);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       IPersonView _pv = new PersonView();     PersonView _pvc = _pv as PersonView;       daoPerson = null;     m_session = null;       _pvc.FireEvent_OnEditCommand(people[0].Id);     Assert.IsTrue(new Guid(_pv.txtPersonIdValue).Equals(people[0].Id));     Assert.IsTrue(_pv.txtUserIdValue.Equals(people[0].UserID));       ptd = null;     _pv = null;     _pvc = null; } PersonPresenterTests.cs (Pt 4) [Test] public void View_OnDeleteCommandTest() {     PersonTestData ptd = new PersonTestData();     ptd.RemoveAllPersonEntries();     ptd.CreatePersonEntries(1);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       PersonPresenter _pp = new PersonPresenter(new PersonView(false));     IPersonView _pv = _pp.personView;       daoPerson = null;     m_session = null;       _pp._view_OnDeleteCommand(people[0].Id);       m_session = SessionManager.SessionFactory.GetCurrentSession();     daoPerson = new PersonDAOImpl(m_session);       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.AreEqual(people.Count, 0);     }       ptd = null;     _pv = null;     _pp = null; } PersonPresenterTests.cs (Pt 5) [Test] public void FireEvent_OnDeleteCommandTest() {     PersonTestData ptd = new PersonTestData();     ptd.RemoveAllPersonEntries();     ptd.CreatePersonEntries(1);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       IPersonView _pv = new PersonView();     PersonView _pvc = _pv as PersonView;       daoPerson = null;     m_session = null;       _pvc.FireEvent_OnDeleteCommand(people[0].Id);       m_session = SessionManager.SessionFactory.GetCurrentSession();     daoPerson = new PersonDAOImpl(m_session);       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.AreEqual(people.Count, 0);     }       ptd = null;     _pv = null;     _pvc = null; } PersonPresenterTests.cs (Pt 6)         [Test]         public void View_OnClearEditPerson()         {             PersonPresenter _pp = new PersonPresenter(new PersonView(false));             IPersonView _pv = _pp.personView;               _pv.txtFirstNameValue = "testData";               _pp._view_OnClearEditPerson();             Assert.IsNullOrEmpty(_pv.txtFirstNameValue);               _pv = null;             _pp = null;         }           [Test]         public void FireEvent_OnClearEditPerson()         {             IPersonView _pv = new PersonView();             PersonView _pvc = _pv as PersonView;               _pv.txtFirstNameValue = "testData";               _pvc.FireEvent_OnClearEditPerson();             Assert.IsNullOrEmpty(_pv.txtFirstNameValue);               _pv = null;             _pvc = null;         }           [Test]         public void Get_PersonDataTest()         {             PersonTestData ptd = new PersonTestData();             ptd.CreatePersonEntries(3);               m_session = SessionManager.SessionFactory.GetCurrentSession();             ICriteria crit = m_session.CreateCriteria(typeof(Person));             PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);               IList<Person> people;               using (var tx = m_session.BeginTransaction())             {                 people = daoPerson.GetByCriteria(crit);                 tx.Commit();                 Assert.GreaterOrEqual(people.Count, 1);             }               PersonPresenter _pp = new PersonPresenter(new PersonView(false));             Assert.IsInstanceOf<PersonPresenter>(_pp);               daoPerson = null;             m_session = null;               IList<PersonDto> pers = _pp.Get_PersonData();             Assert.GreaterOrEqual(pers.Count, 3);               ptd = null;               _pp = null;         } PersonPresenterTests.cs (Pt 7) [Test] public void FireEvent_OnPageLoadNoPostbackTest() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       IPersonView _pv = new PersonView();     PersonView _pvc = _pv as PersonView;       people = null;     daoPerson = null;     m_session = null;       _pvc.FireEvent_OnPageLoadNoPostback();     Assert.GreaterOrEqual(_pvc.GvPerson.Count, 3);       ptd = null;     _pv = null;     _pvc = null; } PersonPresenterTests.cs (Pt 8) [Test] public void View_OnRefreshPersonGridTest() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       PersonPresenter _pp = new PersonPresenter(new PersonView(false));     IPersonView _pv = _pp.personView;     PersonView _pvc = _pv as PersonView;       people = null;     daoPerson = null;     m_session = null;       _pvc.FireEvent_OnRefreshPersonGrid();     Assert.GreaterOrEqual(_pvc.GvPerson.Count, 3);       ptd = null;     _pp = null;     _pv = null;     _pvc = null; } PersonPresenter.cs (Pt 9) [Test] public void FireEvent_OnRefreshPersonGridTest() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       IPersonView _pv = new PersonView();     PersonView _pvc = _pv as PersonView;       people = null;     daoPerson = null;     m_session = null;       _pvc.FireEvent_OnRefreshPersonGrid();     Assert.GreaterOrEqual(_pvc.GvPerson.Count, 3);       ptd = null;       _pv = null;     _pvc = null; }                                                       PersonPresenterTests.cs (Pt10) [Test] public void View_OnSaveEditPersonNewTest() {     PersonTestData ptd = new PersonTestData();     ptd.RemoveAllPersonEntries();       PersonPresenter _pp = new PersonPresenter(new PersonView(false));     IPersonView _pv = _pp.personView;       _pv.txtPersonIdValue = "";     _pv.txtFirstNameValue = "Mary";     _pv.txtLastNameValue = "Johnston";     _pv.txtEmailValue = "some@email";     _pv.txtUserIdValue = "mj1";       _pp._view_OnSaveEditPerson();       _pv = null;     _pp = null;       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       people = null;     daoPerson = null;     m_session = null;       ptd = null; } PersonPresneterTests.cs (Pt11) [Test] public void FireEvent_OnSaveEditPersonNewTest() {     PersonTestData ptd = new PersonTestData();     ptd.RemoveAllPersonEntries();       IPersonView _pv = new PersonView();       _pv.txtPersonIdValue = "";     _pv.txtFirstNameValue = "Mary";     _pv.txtLastNameValue = "Johnston";     _pv.txtEmailValue = "some@email";     _pv.txtUserIdValue = "mj1";       PersonView _pvc = _pv as PersonView;     _pvc.FireEvent_OnSaveEditPerson();       _pv = null;     _pvc = null;       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       people = null;     daoPerson = null;     m_session = null;       ptd = null; } PersonPresenterTests.cs (Pt12) [Test] public void View_OnSaveEditPersonExistingTest() {     PersonTestData ptd = new PersonTestData();     ptd.RemoveAllPersonEntries();     ptd.CreatePersonEntries(1);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       IList<Person> people;       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       PersonPresenter _pp = new PersonPresenter(new PersonView(false));     IPersonView _pv = _pp.personView;       _pv.txtPersonIdValue = people[0].Id.ToString();     _pv.txtFirstNameValue = people[0].FirstName;     _pv.txtLastNameValue = people[0].LastName;     _pv.txtEmailValue = people[0].Email;     _pv.txtUserIdValue = "differentUser";       _pp._view_OnSaveEditPerson();       _pv = null;     _pp = null;       m_session = SessionManager.SessionFactory.GetCurrentSession();     crit = m_session.CreateCriteria(typeof(Person));     daoPerson = new PersonDAOImpl(m_session);       using (var tx = m_session.BeginTransaction())     {         people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       people = null;     daoPerson = null;     m_session = null;       ptd = null; } PersonPresenterTests.cs (Pt13)         [Test]         public void FireEvent_OnSaveEditPersonExistingTest()         {             PersonTestData ptd = new PersonTestData();             ptd.RemoveAllPersonEntries();             ptd.CreatePersonEntries(1);               m_session = SessionManager.SessionFactory.GetCurrentSession();             ICriteria crit = m_session.CreateCriteria(typeof(Person));             PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);               IList<Person> people;               using (var tx = m_session.BeginTransaction())             {                 people = daoPerson.GetByCriteria(crit);                 tx.Commit();                 Assert.GreaterOrEqual(people.Count, 1);             }               IPersonView _pv = new PersonView();               _pv.txtPersonIdValue = people[0].Id.ToString();             _pv.txtFirstNameValue = people[0].FirstName;             _pv.txtLastNameValue = people[0].LastName;             _pv.txtEmailValue = people[0].Email;             _pv.txtUserIdValue = "differentUser";               PersonView _pvc = _pv as PersonView;             _pvc.FireEvent_OnSaveEditPerson();               _pv = null;             _pvc = null;               m_session = SessionManager.SessionFactory.GetCurrentSession();             crit = m_session.CreateCriteria(typeof(Person));             daoPerson = new PersonDAOImpl(m_session);               using (var tx = m_session.BeginTransaction())             {                 people = daoPerson.GetByCriteria(crit);                 tx.Commit();                 Assert.GreaterOrEqual(people.Count, 1);             }               people = null;             daoPerson = null;             m_session = null;               ptd = null;         }       } } Unit tests have also been included for an unique object and also for the added NHibernate LINQ capabilities PersonDAOImplTest.cs (Pt 1) using System; using System.Collections.Generic; using System.Linq; using System.Text; using DomainModel.Person; using NHibernateDAO; using NHibernateDAO.DAOImplementations; using NUnit.Framework; using NHibernate; using NHibernate.Context; using NHibernate.Linq; using BootstrapperUnitTests.TestData;   namespace BootstrapperUnitTests {     [TestFixture]     public class PersonDAOImplTests     {           private ISession m_session;           [TestFixtureSetUp]         public void TestFixtureSetup()         {             var session = SessionManager.SessionFactory.OpenSession();             CallSessionContext.Bind(session);         }           [TestFixtureTearDown]         public void TestFixtureTeardown()         {             PersonTestData ptd = new PersonTestData();             ptd.RemoveAllPersonEntries();               var session = CallSessionContext.Unbind(SessionManager.SessionFactory);             if (session != null)             {                 if (session.Transaction != null && session.Transaction.IsActive)                 {                     session.Transaction.Rollback();                 }                 else                     session.Flush();                   session.Close();             }               session.Dispose();         }     PersonDAOImplTests.cs (Pt 2) [Test] public void PersonSaveTestWithTx() {     m_session = SessionManager.SessionFactory.GetCurrentSession();     Person _person = new Person();     _person.FirstName = "John";     _person.LastName = "Davidson";     _person.Email = "[email protected]";     _person.UserID = "jwd";     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);          using (var tx = m_session.BeginTransaction())     {         Person newPerson = daoPerson.Save(_person);         tx.Commit();         Assert.AreEqual(_person, newPerson);     }       _person = null;     daoPerson = null;     m_session = null; }   [Test] public void PersonCriteriaQueryTestWithTx() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       using (var tx = m_session.BeginTransaction())     {         IList<Person> people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 3);     }       daoPerson = null;     m_session = null;       ptd.RemoveAllPersonEntries();     ptd = null; } PersonDAOImplTest.cs (Pt 3) [Test] public void PersonDeleteTestWithTx() {     m_session = SessionManager.SessionFactory.GetCurrentSession();     m_session.FlushMode = FlushMode.Commit;     Person _person = new Person();     _person.FirstName = "John";     _person.LastName = "Davidson";     _person.Email = "[email protected]";     _person.UserID = "jwd";     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       Person newPerson;     using (var tx = m_session.BeginTransaction())     {         newPerson = daoPerson.Save(_person);         tx.Commit();         Assert.AreEqual(_person, newPerson);         Console.WriteLine("Person ID: {0}", newPerson.Id);     }       ICriteria crit = m_session.CreateCriteria(typeof(Person));          Int32 pers = 0;     using (var tx = m_session.BeginTransaction())     {         IList<Person> people = daoPerson.GetByCriteria(crit);         tx.Commit();         Assert.Greater(people.Count, 0);         pers = people.Count;         Console.WriteLine("Count After Add: {0}", pers);     }       using (var tx = m_session.BeginTransaction())     {         daoPerson.Delete(newPerson);         tx.Commit();         newPerson = null;     }       using (var tx = m_session.BeginTransaction())     {         IList<Person> people = daoPerson.GetByCriteria(crit);         tx.Commit();         Console.WriteLine("Count after Delete: {0}", people.Count);           Assert.IsTrue(pers.Equals(people.Count + 1));     }       _person = null;     daoPerson = null;     m_session = null; } PersonDAOImplTests.cs (Pt 4) [Test] public void PersonSaveTestWithoutTx() {     m_session = SessionManager.SessionFactory.GetCurrentSession();     Person _person = new Person();     _person.FirstName = "John";     _person.LastName = "Davidson";     _person.Email = "[email protected]";     _person.UserID = "jwd";     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);     Person newPerson = daoPerson.Save(_person);       Assert.AreEqual(_person, newPerson);     _person = null;     newPerson = null;     daoPerson = null;     m_session = null; }   [Test] public void PersonCriteriaQueryTestWithoutTx() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       m_session = SessionManager.SessionFactory.GetCurrentSession();     ICriteria crit = m_session.CreateCriteria(typeof(Person));     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);     IList<Person> people = daoPerson.GetByCriteria(crit);       Assert.GreaterOrEqual(people.Count, 3);     people = null;     daoPerson = null;     m_session = null;       ptd.RemoveAllPersonEntries();     ptd = null; } PersonDAOImplTest.cs (Pt 5) [Test] public void PersonLinqQueryTestWithTx() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       m_session = SessionManager.SessionFactory.GetCurrentSession();     IQueryable<Person> qry = from p in m_session.Query<Person>() select p;     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       using (ITransaction tx = m_session.BeginTransaction())     {         IList<Person> people = daoPerson.GetByQueryable(qry);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 3);     }       daoPerson = null;     m_session = null;       ptd.RemoveAllPersonEntries();     ptd = null; }   [Test] public void PersonLinqQueryWhereTestWithTx() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       string userid = "jwd2";       m_session = SessionManager.SessionFactory.GetCurrentSession();     IQueryable<Person> qry = from p in m_session.Query<Person>() where p.UserID == userid select p;     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);       using (ITransaction tx = m_session.BeginTransaction())     {         IList<Person> people = daoPerson.GetByQueryable(qry);         tx.Commit();         Assert.GreaterOrEqual(people.Count, 1);     }       daoPerson = null;     m_session = null;       ptd.RemoveAllPersonEntries();     ptd = null; } PersonDAOImplTests.cs (Pt 6) [Test] public void PersonLinqQueryTestWithoutTx() {     PersonTestData ptd = new PersonTestData();     ptd.CreatePersonEntries(3);       m_session = SessionManager.SessionFactory.GetCurrentSession();     IQueryable<Person> qry = from p in m_session.Query<Person>() select p;     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);     IList<Person> people = daoPerson.GetByQueryable(qry);       Assert.GreaterOrEqual(people.Count, 3);     people = null;     daoPerson = null;     m_session = null;       ptd.RemoveAllPersonEntries();     ptd = null; } PersonDAOImplTests.cs (Pt 7)         [Test]         public void PersonDeleteTestWithoutTx()         {             m_session = SessionManager.SessionFactory.GetCurrentSession();             m_session.FlushMode = FlushMode.Commit;             Person _person = new Person();             _person.FirstName = "John";             _person.LastName = "Davidson";             _person.Email = "[email protected]";             _person.UserID = "jwd";             PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);             Person newPerson = daoPerson.Save(_person);               Assert.AreEqual(_person, newPerson);             Console.WriteLine("Person ID: {0}", newPerson.Id);               ICriteria crit = m_session.CreateCriteria(typeof(Person));             IList<Person> people = daoPerson.GetByCriteria(crit);               Assert.Greater(people.Count, 0);             Int32 pers = people.Count;             Console.WriteLine("Count After Add: {0}", pers);               people = null;             daoPerson.Delete(newPerson);             newPerson = null;             people = daoPerson.GetByCriteria(crit);             Console.WriteLine("Count after Delete: {0}", people.Count);               Assert.IsTrue(pers.Equals(people.Count + 1));               people = null;             _person = null;             daoPerson = null;             m_session = null;         }     } } Summary       This solution retains all the functionality as was included in the original solution, but it has now been placed in an architecture that improves testability and simplifies the creation of functionality as each element now has a clear project where it belongs within the solution. The next evolution of the bootstrapper will investigate data service usage and more complex mappings. However this version of the bootstrapper is now appropriate to use for simple projects with NHibernate where the domain model is relatively simple.     If you remember back to the code behind file discussion there were still methods that remained for working with the gridview and drop down controls on the web page. These could be moved up to the presenter if these controls were redesigned as composite controls in a separate project in the solution so that they were no longer inherited through System.Web. [Less]