In the comments of my previous post - Descriptive State Enumeration - Maxim Tihobrazov asked me to show how to map a state pattern with NHibernate; and I am more than happy to oblige! NHibernate Mapping Options I certainly don't claim to be an expert on NHibernate, but I do use it on a daily basis and I've solved my fair share of problems - including how to map a State pattern with NHibernate. According the NHibernate inheritance documentation, there are three core ways of mapping this pattern: - Table per class hierarchy (I've always called it "record per class") - each subclass (state instance, in this case) is associated with a specific record in a table. All of the subclasses are found in the same table.
- Table per subclass (also called a "joined sub class") - each sub class has it's own table and is joined to the super class by a one to one relationship with it's primary key.
- Table per concrete class - each sub class has it's own table and is mapped by the specific class type, not by the abstraction.
My Choice - Table Per Class Hierarchy In my current set of applications, I've always used the table per class hierarchy option so that is what I will describe here. My reasons for choosing this option are simplicity in the mapping and more importantly - not having a need to store the records in their own tables. Each of my states can be very cleanly represented as a record in my state table. Setting Up Shop Let's consider the same coffee shop that I've used in both of my PTOM posts - in this case, we're dealing specifically with the Order class and it's OrderStatus state. It's necessary to note that when I am working with NHibernate and the state pattern, I actually do set up my state models a little different. First off - we need an Id for NHibernate to map to the table's primary key. Secondly, I like to keep the core information in the abstract base class and provide all the varying values via a constructor. Third, each of the inheriting classes needs to provide a default (no args) constructor. Forth, we need a way for NHibernate to know which class it should actually instantiate, when loading - a "descriminator". I like to use a simple string Name property for this. And lastly - I think (though I am likely wrong on this aspect) that the abstract base class needs to provide a default constructor. Fortunately, the constructors we need don't need to be public - they can be private. With all of that being said, here is realistic code base that I would use for my Order and OrderStatus model: public class Order { public int Id { get; set; } public OrderStatus Status { get; set; }} public abstract class OrderStatus { public static InProcess = new InProcessStatus(); public static Totaled = new TotaledStatus(); public static Tendered = new TenderedStatus(); public static Delivered = new DeliveredStatus(); public int Id { get; set; } public bool DisplayForFullfillment { get; } public string Name { get; set; } private OrderStatus() { } private OrderStatus(int id, bool display, string name) { Id = id; DisplayForFullfillment = display; Name = name; } } public class InProcessStatus: OrderStatus { private InProcessStatus(): base(1, false, "InProcess"); } public class TotaledStatus: OrderStatus { private TotaledStatus(): base(2, true, "Totaled"); } public class TenderedStatus: OrderStatus { private TenderedStatus(): base(3, true, "Tendered"); } public class DeliveredStatus: OrderStatus { private DeliveredStatus(): base(4, false, "Delivered"); }
The Fluent NHibernate Maps
I'm a huge fan of Fluent NHibernate. I've been using it since just after it was branched from the original ShadeTree code. And at one point, I had submitted so many patches that I was made a comitter on the project. So, it should be no surprise to anyone that I'm going to advocate using FluentNHibernate over the .hbm.xml mapping files. Truth be told, I don't even remember how to do the needed descriminators in hbm.xml files. There was some trick to the descriminator being the first specified items after the Id or something... it's just easier to do in FluentNHibernate, so I don't bother with hbm.xml files anymore.
The Order map is going to be as standard as any other map. You don't need to do anything special here. Just map the Id of the Order and the referenced OrderStatus. (I like to add a "CreateMap()" method that is called from the constructor, so that I can avoid the "virtual method call from a constructor" warning. But I also use the "treat warnings as errors" option for my C# projects).
public class OrderMap: ClassMap<Order> { public OrderMap() { CreateMap(); } public void CreateMap() { DefaultAccess.AsProperty(); WithTable("Orders"); Id(o => o.Id).GeneratedBy.Assigned(); } }
It's really the OrderStatus map that is special in this case. This is where we get into the details of the descriminator - telling NHibernate which specific instance to create, when loading the data from the database. In our case, we have added a "Name" field to our OrderStatus object and we will be explicitly using this property as the descriminator.
public class OrderStatusMap: ClassMap<OrderStatus> { public OrderStatusMap() { CreateMap(); } public void CreateMap() { DefaultAccess.AsProperty(); WithTable("OrderStates"); Id(s => s.Id).GeneratedBy.Assigned(); DiscriminateSubClassesOnColumn<string>("Name") .SubClass<InProcessStatus>() .IsIdentifiedBy(OrderStatus.InProcess.Name) .MapSubClassColumns(x => { }) .SubClass<TotaledStatus>() .IsIdentifiedBy(OrderStatus.Totaled.Name) .MapSubClassColumns(x => { }) .SubClass<TenderedStatus>() .IsIdentifiedBy(OrderStatus.Tendered.Name) .MapSubClassColumns(x => { }) .SubClass<DeliveredStatus>() .IsIdentifiedBy(OrderStatus.Delivered.Name) .MapSubClassColumns(x => { }) Map(s => s.Name); } }
The key to all of this is the DescriminateSubClassesOnColumn method. The generics <string> tells us what .NET Type is being used to identify the specific class instances that we are dealing with and the ("Name") parameter is the column name in the database that represents the specific instance.
Then, we have the specific class instances referenced by the ".Subclass<type>" calls. This is a fluent interface, so ".Subclass" is a method that gets called directly off the DescriminatoeSubClassesOnColumn method. The specific type we want NHibernate to know about is specified in the generics parameter: ".SubClass<InProcessStatus>". The "IsIdentifiedBy" is where we give NHibernate the knowledge of what value in the "Name" column maps to what specific class in our code. To prevent magic string syndrome from setting in, I like to use the actual enumeration pattern of my OrderStatus to specify the string name of the class.
And finally, we have to provide an ugly workaround for our maps via the "MapSubclassColumns" method. There is an implementation issue in FluentNHibernate currently, and because of this issue we are forced to call the "MapSubclassColumns" method, with an empty lambda expression. If we don't call this method, the sub class will not get registered and NHibernate will not know how to handle the data in question. (I am hoping to fix this issue at some time, and make it so we don't have to call that method. I just haven't had time recently.)
Wrapping Up The Map
The rest of the map (the one remaining "name" property being mapped, in this case) is a regular NHibernate map. You'll notice, though, that we never mapped the "DispalyForFullfillment" property. I explicitly choose to leave any and all "volatile" properties out of my state maps. By doing this, I am able to add, edit, and remove any properties or methods on the state objects that I need, without having to change the NHibernate mappings. Since the state objects I am dealing with don't change without recompiling the code anyway, I don't need the ability to define them in the database. However, if you do need or want the flexibility of defining your states in the database, you can map each individual property of the state. Just remember that you will have to modify both the code and the database, to make the changes complete.
I hope this quick look at mapping a state pattern with Fluent NHibernate will help to shed some light on the subject, for someone. I realize that I have only provided the FluentNHibernate mapping, though. I specifically chose to do this because it would be a serious chore for myself to create a code project with NHibernate set up so that I can actually verify my mapping xml is correct. However, the translation from FluentNHibernate back to standard .hbm.xml files should be fairly straightforward, with the help of the NHibernate Documentation. If anyone out there is willing to help out and send me the correct .hbm.xml mapping, I would be more than happy to add it to this post and credit you with the work.
_________________________________ Cross Posted From LostTechies.com
In my last post, I talked about the idea of encapsulation and using it to ensure that our business rules were enforced correctly. What I didn't talk about, though, was the second half of the conversation that my coworker and I had, concerning the patent -> consultation relationship. It turns out that we had the relationship wrong. That's not to say that patients don't have consultations, but that the logical model we were traveling with had an incorrect perspective and was causing us to create some very ugly workarounds in various parts of the system. What really stuck out in my mind, though, was not the idea that we had the model wrong, but how we came to the conclusion of the model being wrong. It has become apparent to me, upon reflection of the conversations and situation as a whole, that design smells are not always evidenced by design related activities, if ever. A Persistent Problem - Duplicate Redundancy After some initial coding of the patient -> consultation relationship, we start working on the persistence model via NHibernate. What we have is a patient with a collection of consultations - this is easy to map with NHibernate's one-to-many capabilities. We also have a CurrentConsultation property which needs to be mapped. This property is mapped to the same Consultation table, but only pull one specific consultation based on the business rules that state the current consultation is chronologically the most recent and has not ending date set. After some thought, we found that there were a few possibilities for handling the CurrentConsultation property in our current model: - Create a "CurrentConsultation" object that is mapped to the Consultation table and use a "where" class attribute in the NHibernate mapping that would limit the returned result
- Create a "CurrentConsultation" object that is mapped to a CurrentConsultation view and have the view coded to return the correct consultation object
- Add a CurrentConsultationId field to the Patient table, as a foreign key to the Consultations table, and map to the existing Consultation object
After some additional thought, though, we found that each of these solutions has a few significant problems that were going to cause a lot of trouble. Options 1 and 2 Both of these options have the problem of duplicating business rules into more than one language and location. We would either have the business rules of what constitutes the current consultation in the NHibernate mapping (the 'where' attribute) or in a database view, in addition to the already existing code. Changing the rule would mean changing a minimum of two locations where that rule is handled. This is a bad idea no matter how you look at it. Both of the options have also created a duplication of knowledge from the concept of a Consultation by creating a "CurrentConsultation" class and a separate NHibernate map for it. We would have the original Consultation class and the new CurrentConsultation class both representing the same data, making an artificial distinction in our code. Again, this is a bad idea. We don't want duplication of these logical concepts. We're also not dealing with a bounded context or any other logical separation of concerns at this point, so there is no need to separate the concept of a consultation into multiple classes. Option 3 This doesn't appear to have the duplication issue in code, but there is a potential for duplication of data. When we get down to the implementation of NHibernate, we could easily cause duplicate data in the consultation table by saving the current consultation class. We might be able to get around this by not cascading the saves of the current consultation property, but then we'd be forced to ensure the consultation collection was persisted prior to the patient so that we could update the current consultation object's id before saving the patient. Both of these problems sound like a serious pain to me. I'm betting it's possible, but I'm also betting that it would be a nightmare of trial and error to get it right and a lot more code than we should really have to write. Changing Perspectives As Joe Ocampo pointed out in the comments of my original post, we had a problem in our system that was really caused by our lack of correct perspective in the situation. Rather than forcing the idea of a patient being the root aggregate in this situation, causing us a lot of headache and frustration in trying to model our persistence layer, a simple change in how we looked at the situation helped us solve the persistence problem and greatly simplified how the application worked. Joe's comment (with some formatting added): "One thing I like to challenge developers with when I teach DDD is to flip the aggregate to determine if the model is sound. I know this is only an example but work with me here. You indicated you are dealing with a medical system. We can assume there are certain entities such as Patient, Consultations, Doctor and Practice. In your example you created a model where the patient is the aggregate root for consultations but what if the Doctor simply asked what consultations do I have today? In this paradigm the Practice is the aggregate root and Consultations are aggregate within where Patient is an aspect of the consultation. The code would look something like this. consultations = practiceService(IConsultationService).GetConsultationsFor(doctor);
This also allows the consultation service to encapsulate its own logic for creating a consultation for creating a consultation. You can’t get any closer than that 
consultationService.CreateConsultationFor(patient).with(doctor).at(date);
The point I am trying to make is be careful of aggregate roots. Once you go down that path it is really difficult to back the train up and break it apart."
Though our actual implementation was different, this was the same basic conclusion that we had come to - our perspective on the situation was simply wrong. When we stepped back from the problem and realized that the consultation was the primary focus of the situation, and that a nurse or doctor would be the primary user of that portion of the system, it became rather obvious that our aggregate was in dire need of rework.
A Reflective Perspective
What we ended up with was a Patient object that dealt with all of it's demographics information, billing information, etc, without a CurrentConsultation property or even a Consultations list. Then, on the the separated Consultation object, we added a child property of Patient. Once we realized that our Consultation object was the primary focus and made this distinction in our code, we also realized that the Patient object was carrying far too much information around the system. We found that we actually had two very distinct concepts of a patient, determined by two very distinct bounded contexts.
- In the 'Billing' context, we needed all of the address , billing, and other demographics information about the patient - who they are, where they live, what their insurance is, etc. The existing Patient class filled this need.
- In the 'Consultations' context, we did not need anything from the Billing context, except for the person's name and patient id. What we really care about in the consultations is medical information about the patient - their current prescriptions, allergies, past medical care, etc. So, we created a ' patient' class to represent these needs.
These changed allowed for a much more clearly defined model that was truly reflective of the systems needs. We could easily see the difference between a 'billing' patient and a 'medical' patient, and we were able to code each of these areas of the system without the concerns bleeding into each other. Essentially, we decoupled the system at a module level, not just at a class level.
We also found that the NHibernate mapping problems suddenly went away. Since the Consultation class had a child of Patient, it was a simple many-to-one mapping with no strange sequencing or duplicate data issues. In the screens that deal with the consultation directly, we load the consultation as the aggregate root and go from there. In the screens that need to show patient consultation history, we did a simple query and returned all of the consultations for the given patient. Again, we found a way to decouple our system - this time, at the persistence model.
Design Smells: Not Just A Design Problem
In the end, we were able to recognize a serious design smell in our system - not by the design itself, though. After all, the original code had encapsulated the needs quite well. But, as it turns out, it was a bad encapsulation at a higher level. It wasn't until we started working with the model we had created, specifically trying to persist the model, that we realized our design was not right.
This change was a huge breakthrough for us, not necessarily in the code or the system that was being built, but in how we look at our systems and our domain models. The realization that design smells are often evidenced not by the design itself, but by how the design is used in the infrastructure and other supporting roles of the system, has had a profound impact on how we look at system design. I'm now seeing areas of different systems that are encapsulated incorrectly, at a higher level than class design. Recognizing the problem is the first step - and we're now working to rearrange and invert these models to more accurately reflect reality.
Pay attention to the pain that your application, infrastructure and other supporting services are causing you. You may be staring at evidence of a design problem, without realizing it.
_________________________________ Cross Posted From LostTechies.com
Some coworkers were recently working on an object model for a simple security system. After some discussion with them, we came up with this basic model: A permission is defined as an activity that can be assigned to a user, or group, and can be allowed or disallowed. From a Domain Driven Design perspective, we're stating that the Permission is the aggregate root. The User object itself, while involved in this aggregate, is divorced from this aggregate's relational model - you can load and work with a User object without having to load or worry about the Permission hierarchy. The addition of the "UserGroup" is solely for the many-to-many relationship between User and Group mappings with NHibernate and is not actually part of the object model's code. Once we had this model in place, we wanted to have a simple query that allowed us to load a given permission object by activity name, for a user - whether the user was assigned directly or via a group. At a high level, this model and query should be fairly simple to work with. It turned out to be a massive learning curve in NHibernate, though. After much trial, error, and Google searching, we ended up with this NHibernate query code: ICriterion userIdMatches = Restrictions.Eq("Id", userId);ICriterion activityNameMatches = Restrictions.Eq("Name", action);ICriterion userIdAliasMatches = Restrictions.Eq("u.Id", userId); DetachedCriteria groupPermissionCriteria = DetachedCriteria.For<Permission>() .SetProjection(Projections.Property("Group")) .CreateCriteria("Group").CreateCriteria("Users").Add(userIdMatches);ICriterion groupSubquery = Subqueries.PropertyIn("Group", groupPermissionCriteria); DetachedCriteria permissionCriteria = DetachedCriteria.For<Permission>() .CreateAlias("User", "u", JoinType.LeftOuterJoin) .Add(Restrictions.Or(userIdAliasMatches, groupSubquery)); permissionCriteria.CreateCriteria("Activity").Add(activityNameMatches); ICriteria executableCriteria = permissionCriteria.GetExecutableCriteria(Session); result = executableCriteria.List<Permission>(); return result;
There were a lot of lessons learned and a lot of parts that eventually got put together. Here's a quick run-down of what we ended up with and why.
- The ICriterion's at the top of this code block are there to provide a little better readability in the real query code below.
- The groupPermissionCriteria is set up to find a permission object where the specified userId belongs to a Group that belongs to the Permission - i.e. find permissions where the user is assigned via a group. The learning curve from this perspective was the SetProjection call. Though we are not entirely sure what a projection is at this point, we did find out that it was necessary for us to set this projection so that the detached criteria could be used as a sub-query.
- The groupSubQuery is the conversion of the groupPermissionCriteria into an ICriterion so that we can do a logical Or with it in the primary query construction.
- The permissionCriteria object sets up the core criteria logic and ties together the group permission assignment with the user permission assignment.
- CreateAlias is used so that we can shorten the criteria for loading by the User assignment directly. The JoinType on the alias needs to be Left Outer Join so that we will return a proper Permission object even when there is no direct user assignment.
- After creating the alias, we can add an "Or" criterion to the query and specify that we want to match based on the user's direct assignment or a group's assignment.
- The last line of the criteria simply adds the Activity criteria to load by the activity name.
The resulting SQL will load the permission by Activity name AND (User assignment OR group assignment where the user is part of the group).
SELECT this_.PERMISSIONSID as PERMISSI1_0_3_, this_.IS_ALLOWED as IS2_0_3_, this_.ACTIVITYID as ACTIVITYID0_3_, this_.USERID as USERID0_3_, this_.GROUPID as GROUPID0_3_, activity1_.ACTIVITYID as ACTIVITYID4_0_, activity1_.ACTIVITY_NAME as ACTIVITY2_4_0_, activity1_.DESCRIPTION as DESCRIPT3_4_0_, activity1_.INACTIVE_DATE as INACTIVE4_4_0_, u2_.USERID as USERID3_1_, u2_.USER_NAME as USER2_3_1_, u2_.INACTIVE_DATE as INACTIVE3_3_1_, group6_.GROUPID as GROUPID1_2_, group6_.GROUP_NAME as GROUP2_1_2_, group6_.INACTIVE_DATE as INACTIVE3_1_2_ FROM PERMISSIONS this_ inner join ACTIVITY activity1_ on this_.ACTIVITYID=activity1_.ACTIVITYID inner join USERS u2_ on this_.USERID=u2_.USERID left outer join GROUPS group6_ on this_.GROUPID=group6_.GROUPID WHERE activity1_.ACTIVITY_NAME = :p1 and ( u2_.USERID = :p2 or this_.GROUPID = ( SELECT this_0_.GROUPID as y0_ FROM PERMISSIONS this_0_ inner join GROUPS group1_ on this_0_.GROUPID=group1_.GROUPID inner join USERS_GROUPS users5_ on group1_.GROUPID=users5_.GROUPID inner join USERS user2_ on users5_.USERID=user2_.USERID WHERE user2_.USERID = :p3 ) )
Using a sub-query to load based on the group is not the most optimal way of loading the permission for the group assignment. However, since all of the joins in the main query and the sub-query are done on primary and foreign keys in the tables, performance should not be an issue. The only real performance concern for this query is the activity name in the where statement. A simple unique constraint and index on the activity name, though, will solve that problem.
I'm currently working on a search screen for a shipment tracking system. At the bottom of this screen, there are 4 checkboxes that will determine whether or not we are supposed to display Imports, Exports, Air shipments, or Ocean shipments - in whatever combination the user wants: Down in the depths of the search process, I am creating a "finder" object, as Ayende talked about quite a while back. In this object, I have to account for the checkbox values here - whether or not the user wants to display whatever types of shipment (import or export) and/or the method of shipment (air or ocean). What gets really interesting is that when a box is un-checked, I should not show that particular type or method. A quick analysis of these 4 checkboxes will come up with the following variants that must be accounted for when building the query. - Show Imports Only
- Ocean and Air
- Ocean only
- Air Only
- Show Exports Only
- Ocean and Air
- Ocean only
- Air only
- Show Imports and Export
- Ocean and Air
- Ocean only
- Air only
- Show no imports or exports (returns no results)
- Show no ocean or air (returns no results)
All totaled up, that's 11 different query variants that have to be accounted for. The end result of my query build methods is the following: private void AddShipmentMethodCriteria(DetachedCriteria criteria) { ICriterion air = Restrictions.Eq("ShipmentMethod", "Air"); ICriterion ocean = Restrictions.Eq("ShipmentMethod", "Ocean"); if (searchCriteria.ViewAirShipments && searchCriteria.ViewOceanShipments) criteria.Add(Restrictions.Or(air, ocean)); else { if (searchCriteria.ViewAirShipments) criteria.Add(air); else criteria.Add(Restrictions.Not(air)); if (searchCriteria.ViewOceanShipments) criteria.Add(ocean); else criteria.Add(Restrictions.Not(ocean)); } } private void AddShipmentTypeCriteria(DetachedCriteria criteria) { ICriterion import = Restrictions.Eq("ShipmentType", "Import"); ICriterion export = Restrictions.Eq("ShipmentType", "Export"); if (searchCriteria.ViewImports && searchCriteria.ViewExports) criteria.Add(Restrictions.Or(import, export)); else { if (searchCriteria.ViewImports) criteria.Add(import); else criteria.Add(Restrictions.Not(import)); if (searchCriteria.ViewExports) criteria.Add(export); else criteria.Add(Restrictions.Not(export)); } }
By having the first If statement check for both 'Import' and 'Export' being requested, we can properly create our query to show both of them via the Restrictions.Or() criteria. Additionally, we have to account for either or both of them not being checked and explicitly call them out to say that we do not want to show whichever one is not selected. The same is true for the 'Air' and 'Ocean' shipment methods.
The end result is that the user can select or un-select whichever shipment methods and shipment types they want, resulting in the correct data being displayed.
...
On a side note, there's probably some abstraction that I could create where I pass in the ICriterions and the boolean flags to help reduce code redundancy but that's not the point of this post. I'm really trying to illustrate the complex logic used in creating the correct NHibernate ICriteria/DetachedCriteria, and the analysis that you need to undertake for what looks like the most simple of situations. After all, how hard could it be to create a query based on 4 check boxes? ... more difficult than you might imagine, at first. Take the time to analyze even simple scenarios like this.
I've been learning a lot lately - reading up on Agile, Lean Manufacturing, Lean Software Development; experimenting with NHibernate, exploring UnitOfWork concepts and generally trying to become a better software developer. In this process of learning, discussion with others, and application of knowledge to my environment, I've found that knowledge is very distinctly different than understanding. Let's look at my use of NHibernate as an example. 2 years ago, I knew the basics of what NHibernate could do for me - I spent a few weeks learning the very basics to see how it worked and whether or not I wanted to use it. 1 year ago, I actually started using NHibernate for a project and my knowledge of it quickly grew. I knew how to create the appropriate mapping files, configure NHibernate, etc. As this knowledge grew, I became more and more interested in what NHibernate could do and how it could be applied to many different applications and situations. I knew a lot about how NHibernate was built, how it worked, and what it's capabilities were. However, all of this knowledge was not a substitute for a real understanding of NHibernate. My knowledge of how it worked led me to some conclusions that I don't think are correct anymore - trying to apply NHibernate in situations where it really was not the right answer. I've seen the same problem occur multiple times in the last 6 months, and in the last 11+ years of my career. There's almost a recognizable pattern to the learning process: - Learning curve to gain working knowledge
- Knowledge growth and intimate familiarity
- Assumption that knowledge gained is a substitute for experience and understanding
- Attempts to apply knowledge incorrectly / in wrong situation
- Realization that knowledge is not understanding or experience
- Stepping back from knowledge to gain understanding of how/when to apply the knowledge
I've done this with Agile/Lean software development as well, recently. I've gained a significant academic knowledge of agile and it's processes and practices. Some of my knowledge is directly backed up by experience, so I believe I do have some understanding of the agile engineering practices. However, I've let my limited understanding mix itself into my academic knowledge a little too much. I've found myself in situations recently where I'm arguing a logical conclusion to a situation and applying it to another situation incorrectly. One of my coworkers likes to apply this adage to situations like this: If a hammer is the only tool you have, everything looks like a nail. I need to remember to step back from my raw knowledge - be it academic or real world use - and let common sense and experience interweave into understanding or the realization that I don't understand. I need to understand that just because I am currently holding a hammer, and thinking about hammers, that doesn't mean that the problem in front of me is a nail that needs to be hammered. The realization that knowledge is not a substitute for understanding can be a very distressing and/or humbling experience. The reality of learning, knowledge and understanding, though, is that I can have every last bit of knowledge on a subject - but without experience to guide me, I can't always understand the where/when/why/how of applying that knowledge appropriately.
In my previous post, I talked about my base repository class that I use, to abstract the NHibernate details away from the actual repository. Now that I have the Do and DoTransaction methods to further the abstraction, I thought it would be good to share my whole abstraction. using System; using NHibernate; using NHibernate.Cfg; namespace RepositoryBase { public abstract class Repository : IDisposable { #region Vars private static readonly Configuration _config; private static readonly ISessionFactory _factory; #endregion #region Constructor / Destructor static BaseDAL() { _config = new Configuration(); _config.Configure(typeof(BaseDAL).Assembly, "RepositoryBase.hibernate.cfg.xml"); _factory = _config.BuildSessionFactory(); } ~BaseDAL() { Dispose(); } #endregion #region Properties public ISession Session { get; private set; } public ITransaction Transaction { get; private set; } #endregion #region Methods protected void Do(Action unitOfWork) { try { OpenSession(); unitOfWork(); } finally { CloseSession(); } } protected void DoTransaction(Action unitOfWork) { try { OpenSession(); BeginTransaction(); if (unitOfWork != null) unitOfWork(); CommitTransaction(); } catch { RollbackTransaction(); throw; } finally { CloseSession(); } } #endregion #region Helper Methods private void OpenSession() { if (Session != null) return; Session = _factory.OpenSession(); Session.FlushMode = FlushMode.Auto; } private void CloseSession() { if (Session == null) return; if (Session.IsOpen) { Session.Close(); } Session.Dispose(); Session = null; } private void BeginTransaction() { ValidateSession(); Transaction = Session.BeginTransaction(); } private void CommitTransaction() { ValidateSession(); if (Transaction != null) Transaction.Commit(); CloseTransaction(); } private void RollbackTransaction() { if (Transaction != null) Transaction.Rollback(); CloseTransaction(); } private void ValidateSession() { if (Session == null) throw new ApplicationException("NHibernate Session Not Open."); } private void CloseTransaction() { if (Transaction == null) return; Transaction.Dispose(); Transaction = null; } #endregion #region IDisposable Members public void Dispose() { CloseTransaction(); CloseSession(); } #endregion } }
Note that I am using an embedded resource as my "hibernate.cfg.xml" location. Just change this line to use a file system resource, or whatever you want for your hibernate configuration.
I'm still wanting to re-abstract this into a set of objects that let's me be concerned with transactions and queries at a business level. One step at a time, though. The syntax that I would like to see, at the moment, would be something like this:
public void SomeBusinessValue() { Something something = DoSomething.BusinessRelated(); SomethingElse somethingElse = SomethingElse(); Repository.DoTransaction(() => { SomeRepository.Save(something); SomeOtherRepository.Delete(somethingElse); }); }
And if I get really ambitious, I may try to incorporate Castle.Windsor's Automatic Transaction Facility, so that I can have syntax like this:
[Transactional(Transaction.Requires)] public void SomeBusinessValue() { Something something = DoSomething.BusinessRelated(); SomethingElse somethingElse = SomethingElse(); SomeRepository.Save(something); SomeOtherRepository.Delete(somethingElse); }
Of course, the more I travel down this path, the more obvious it is how Rhino.Commons' Repository came along. Heh - I'm only about 2 steps away from completely re-doing Ayende's UnitOfWork.
In fact, I may try to re-use NHibernateQueryGenerator and go for this syntax:
[Transactional(Transaction.Requires)] public void SomeBusinessValue() { Something something = Repository<Something>.Find(Where.SomeProperty = someValue); Something something = DoSomething.BusinessRelated(); SomethingElse somethingElse = SomethingElse(); Repository<Something>.Save(something); Repository<SomethingElse>.Delete(somethingElse); }
Wouldn't that be fun... I really do like this syntax. In fact, I spent 9+ months working with it on a project because I love the simplicity of the syntax. In the end, though, I did not understand all of the underlying architecture and abstraction and Ayende set up (he includes the ability to plug in any DAL, including Castle.ActiveRecord, NHibernate, etc) and it caused headaches for me.
...
The major problem I have with this syntax, at the moment, is unit testing the Repository object. I either need to hide these details behind an IWhateverRepository interface (complete with Save, Delete and GetByWhatever methods) like I have been doing, or I I'll need to learn how to unit test NHibernate with in-memory database or something... We'll see where this leads.
(side note: how's that for a "ThoughtStream". )
For the last few months, I've had a very small base class that abstracts out the NHibernate configuration, session creation, etc. It works very well, but is very limited in what it can do. basically, every method in my actual repository implementation would have to open a new session, execute a criteria and close the session. A typical implementation would look like this: public ICollection<Invoice> GetAll() { ICollection<Invoice> invoices = null; try { OpenSession(); invoices = Session.CreateCriteria(typeof(Invoice)).List<Invoice>(); } finally { CloseSession(); } return invoices; }
That certainly is easy and keeps the code fairly clean, removes a lot of duplication, etc.
However, there is a significant limitation - I can't have any code re-use for multi-query scenarios, without duplicating code. In other words, if I want to load that list of invoices and then load some other collection from another repository, I have to use two different Sessions across two different repository implementations. This really becomes an issue when dealing with transactions - I want my entire change set to pass or fail in a single transaction. In my current abstraction, this can't be done.
Fortunately, NHibernate has the solution to my dilemma built right in - all I need to do is create my criteria objects without a session, and then I can execute any / all of them from any session that I want.
public ICollection<Invoice> GetAll() { ICollection<Invoice> invoices = null; try { DetachedCriteria criteria = DetachedCriteria.For<Invoice>(); OpenSession(); invoices = criteria.GetExecutableCriteria(Session).List<Invoice>(); } finally { CloseSession(); } return invoices; }
I don't have a complete abstraction of the separate execution, yet. However, a very basic implementation could look like this (idea stolen from Ray Houston):
protected void Do(Action unitOfWork) { try { OpenSession(); unitOfWork(); } finally { CloseSession(); } } public ICollection<Invoice> GetAll() { ICollection<Invoice> invoices = null; DetachedCriteria criteria = DetachedCriteria.For<Invoice>(); Do(() =>{ invoices = criteria.GetExecutableCriteria(Session).List<Invoice>(); }); return invoices; }
This simple abstraction provides a lot of benefit for us.
- Eliminates duplicate code (not calling OpenSession / Close Session from all repository methods)
- Removes ugly Try / Catch blocks from Repository methods
- Allows multiple Criteria to be executed from a single Session / Transaction
And most importantly - this gives us the ability to create a better abstraction of NHibernate, to support business level transactions, not just repository level transactions. Obviously, this simple example is not going to provide business level transactions. It does get us down the path, though.
This might be common knowledge for those that use NHibernate - but it's new to me, since I am not an NHibernate power-user, yet. Given this object model: public class Invoice { private int _id = 0; private string _invoiceNumber = string.Empty; private DateTime _invoiceDueDate = DateTime.MinValue; private ICollection<InvoiceDetail> _invoiceDetails = new HashedSet<InvoiceDetail>(); public int Id { get { return _id; } } public string InvoiceNumber { get { return _invoiceNumber; } set { _invoiceNumber = value; } } public DateTime InvoiceDueDate { get { return _invoiceDueDate; } set { _invoiceDueDate = value; } } public ICollection<InvoiceDetail> InvoiceDetails { get { return _invoiceDetails; } } public InvoiceDetail CreateDetail() { InvoiceDetail detail = new InvoiceDetail(); InvoiceDetails.Add(detail); return detail; } } public class InvoiceDetail { internal InvoiceDetail() { } private int _id = 0; private string _productName = string.Empty; private decimal _productCost = 0; private int _productQuantity = 0; public int Id { get { return _id; } } public string ProductName { get { return _productName; } set { _productName = value; } } public decimal ProductCost { get { return _productCost; } set { _productCost = value; } } public int ProductQuantity { get { return _productQuantity; } set { _productQuantity = value; } } }
We can load any invoice that has an Invoice Detail with a specific Product Name, using this NHibernate code:
invoices = Session.CreateCriteria(typeof(Invoice)) .CreateCriteria("InvoiceDetails") .Add(Expression.Eq("ProductName", productName)) .List<Invoice>();
Additionally, we can add paging to the result set, by including the SetFirstResult and SetMaxResult calls in the Criteria.
invoices = Session.CreateCriteria(typeof(Invoice)) .CreateCriteria("InvoiceDetails") .Add(Expression.Eq("ProductName", productName)) .SetFirstResult(pageSize * pageNumber) .SetMaxResults(pageSize - 1) .List<Invoice>();
Notice the use of pageSize and PageNumber to set the First Result value - this creates an index-by-zero page number that we want to view. For example, if our page size is 10 and we are on page zero, then the FirstResult is going to be 0 and the last result will be 9. 0 through 9 = 10 results. If we are on page 3, then the FirstResult is 30 and the MaxResult is 39...
Works pretty well... now, let's just hope that the actual underlying database implementation of this can take advantage of each DBMS' optimizations for doing this.
If you are considering the use of NHibernate, or are already using NHibernate, be sure that you always override the .Equals and .GetHashCode methods of your entities. NHibernate makes use of these methods extensively and you are likely to have strange issues if you don't override both of these correctly. Unfortunately, GetHashCode is one of those areas that is difficult to get right; but it needs to be done anyways. For more information, see: And one specific note on .Equals: be sure to check ReferencEquals as the last resort, if no other comparison is possible. I've learned these lessons the hard way; hopefully you won't have to.
In WCF, when using NetDataContractSerializer to enable .NET Remoting, DataContract objects are still serialized. Only ServiceContract objects are marshaled ByRef. The same setup is possible in native .NET Remoting, as well. It seems more likely to happen in WCF, though. Based on my experience, Remoting is usually all MarshalByRef or all Serialized - but that's just my experience. Either way, if you are serializing your object model, you need to be careful. Parent <-> Child Bidirection Relationships If you have a Parent<->Child bidirectional relationship in your DataContract objects, when you send the DataContract objects across WCF, they will be serialized/deserialized and the resulting hierarchy of objects will be: Parent->Child->CopyOfParent. This causes problems when using NHibernate to auto save/load your object tree. For example, this object hierarchy: Parent |--Child | |--Parent (reference to actual Parent) Will end up looking like this, after being serialized / deserialized: Parent |--Child | |--CopyOfParent (new object, independent of actual Parent) To fix this, you'll have to manually re-build your references, after the objects are deserialized: foreach(Child child in parent.Children) { child.Parent = parent; }
If you don't rebuild the hierarchy references like this, NHibernate will not save or update your child objects correctly. You will either get "Transient Instance" exceptions or you will end up with orphaned children records because they will not have their parent id set correctly.
Here's an example of what one of my data access methods looks like, in an app that remotes the data access layers via WCF (and hides most of the NHibernate code in a base class):
public void Save(FooBar fooBar) { try { foreach(Widget widget in fooBar.Widgets) { widget.FooBar = fooBar; } OpenSession(); BeginTransaction(); Session.SaveOrUpdate(fooBar); CommitTransaction(); } catch { RollbackTransaction(); throw; } finally { CloseSession(); } }
Extended Problem: Multi-Parented Children
Although I haven't run into this situation yet, I am assuming that the same problem will occur if you have multiple parents pointing to the same child. For example, if you have:
Parent |--Child1 | |--GrandChild1 (same reference as Child2's GrandChild) |--Child2 | |--GrandChild1 (same reference as Child1's GrandChild)
When you send this structure across WCF as a set of DataContract objects, I imagine that you will end up with this:
Parent |--Child1 | |--GrandChild1 (duplicate of Child2's GrandChild) |--Child2 | |--CopyOfGrandChild1 (duplicate of Child1's GrandChild)
If your intention is to have Child1 and Child2 reference the same record in the database, you will need to reconstruct the Child1 and Child2 references to GrandChild1, ensuring that both point to the same object. I can see the basic code as traversing the children and comparing each of the gradchildren's values, then picking one winner between the same values and resetting the references on the rest of them. Unfortunately, I think the solution for this scenario would likely be unique to each situation, due to the complexity of picking the correct reference.
Has anyone run into this situation? If so, how did you solve it?
|