;

Creating a persistence ignorant POCO application using various ORM solutions

Posted : Sunday, 14 November 2010 14:15:59

Some time ago I bought a copy of Julie Lermans excellent book Programming Entity Framework, I really liked the way that it has been implemented but the lack of support for POCOs was a bit of a let down. The restriction that all entities must inherit from EntityObject was enough of a reason for me not to use it. I often like to fiddle about with inheritance and move features around and the requirement that EntityObject be top of the inheritance chain was too prohibitive for me. With the release of Visual Studio 2010 and .Net 4 there has been a reasonable amount of publicity surrounding all the new features available but the one I was most keen to try was POCO support in EF4. Persistence ignorance is, for good reason, a goal that many developers strive for when building software systems and I decided to write this series of posts to detail my experiences of adding persistence to a simple (and persistence ignorant) application using 3 of the more popular ORM tools available for .Net namely EntityFramework, LinqToSql and NHibernate

In this, the first in series of posts, I will outline the simple application that will be used during the subsequent posts. The application itself is not intended as a production ready system and could without doubt be improved upon however for the purposes of this assay it is completely suitable. A fictional movie memorabilia company want to set up a new website where registered members are able to bid for famous items and props as featured in various movies over the years, the application itself is to be called "LightsCameraAuction".

So let’s get down to the nitty gritty, the model. After a brief period of jotting, scribbling, reflecting, musing, starting-over etc. I identified four business objects (entities) that are required in order to model this simplified application, each of these is listed below with a brief explanation.

  • ItemAn Item is any object of movie memorabilia for which an Auction is held.
  • BidderA Bidder is a customer or registered member of the site who is able to participate in an Auction.
  • BidA Bid is an offer made against an item by a Bidder. A Bid has a single reference to a Bidder and a single reference to an Item.
  • AuctionAn Auction is a process that runs for a fixed period of time. Each Auction has one Item. During the Auction, Bidders can submit one or more Bids for the Item with higher value Bids taking precedence. When the Auction ends, the Bid with the highest value becomes the winning Bid.

As well as the entities listed above there are a few other parts of the domain model for LightsCameraAuction: for each of the business objects I created a corresponding repository strongly typed to the relevant entity, abstracted into interfaces, to manage persistence of that entity. I also created a class called AuctionService which contains all the business logic for the domain model and acts as a facade by providing the public interface for the model. The final object in the domain model is an abstract class called LcaModule from which each implementation will create a subclass to acts as means by which different implementations can be selected easily (more on this later).

    6     public abstract class LcaModule : NinjectModule

    7     {

    8         public const string MetadataBindingParamName = "DataProvider";

    9 

   10         public abstract string MetadataBindingParamValue { get; }

   11     }

I created a new class library project called LcaModel and added all of the entities described above. I am using Ninject to manage dependency injection for this blog series.
NB Typically during the design phase of such a project I would use the CommonServiceLocator project but for the purposes of this essentially throw away app I feel that the extra layer of abstraction provided is unnecessary.

Most of the classes in this project are fairly simple but a couple of them warrant a more explanation Auction Service: AuctionService as stated, contains the business logic required to administer the Auction process. The constructor of this class takes an instance of each repository interface and stores each in a class level private member. The Auction service has various methods used to add and retrieve entities from the model. There are also two other methods of note. The first IsBidAvailable is listed below:

   44         public bool IsBidAvailable(Bidder bidder, Item item, double maxPrice, out double currentPrice)

   45         {

   46             var topBid = _bidRepository.GetTopBidForItem(item);

   47 

   48             currentPrice = topBid == null ? 0 : topBid.BidPrice;

   49 

   50             if (topBid == null || (topBid.Bidder.BidderId != bidder.BidderId && topBid.BidPrice < maxPrice))

   51                 return true;

   52             else

   53                 return false;

   54         }

This purpose of this method first is to find out the current highest bid for the item and then discover if the Bidder passed to this method is able to place a bid. If no bids have yet been placed then the output parameter currentPrice is set to 0. If no bids have yet been placed or the current winning bid was not placed by the bidder passed into the method and the current price is less than the Bidder's maximum price then the method returns true otherwise false.
The second notable method is called SubmitBid and is shown below:

   56         public bool SubmitBid(Item item, Bidder bidder, double bidPrice)

   57         {

   58             lock (item)

   59             {

   60                 var topBid = _bidRepository.GetTopBidForItem(item);

   61                 if (topBid != null && (topBid.BidPrice >= bidPrice || topBid.Bidder.Name == bidder.Name))

   62                 {

   63                     return false;

   64                 }

   65                 else

   66                 {

   67                     _bidRepository.AddBid(new Bid { BidPrice = bidPrice, Bidder = bidder, Item = item });

   68                     return true;

   69                 }

   70             }

   71         }

This method is called any time a Bidder attempts to place a bid for an item and much like the IsBidAvailable() method will check that the current highest bidder is not the same one making the bid and that the bid price is greater than the current winning bid. This logic is processed inside a synchronised block to prevent concurrency issues whereby another Bidder might attempt to access the bids collection of the desired Item at the same time as this Bid is being placed.

After creating the model, I added another class library called LcaModel.InMemory to the project. As you can probably guess the purpose of this project is to provide an in-memory implementation of LcaModel. The InMemory version of the model contains an implementation of each repository interface which uses a class-level List instance strongly typed to the relevant entity to store the entities (e.g. InMemoryBidderRepository implements IBidderRepository and contains a List<Bidder> etc). The last piece of the jigsaw required to wire up the InMemory version is the implementation of the LcaModule base class:

    7     public class InMemoryModule : LcaModule

    8     {

    9 

   10         public override void Load()

   11         {

   12             Bind<AuctionService>().ToSelf().WithMetadata(MetadataBindingParamName, MetadataBindingParamValue);

   13             Bind<IItemRepository>().To<InMemoryItemRepository>().When(c => c.ParentContext.Binding.Metadata.Get(MetadataBindingParamName, "") == MetadataBindingParamValue);

   14             Bind<IBidRepository>().To<InMemoryBidRepository>().When(c => c.ParentContext.Binding.Metadata.Get(MetadataBindingParamName, "") == MetadataBindingParamValue);

   15             Bind<IBidderRepository>().To<InMemoryBidderRepository>().When(c => c.ParentContext.Binding.Metadata.Get(MetadataBindingParamName, "") == MetadataBindingParamValue);

   16             Bind<IAuctionRepository>().To<InMemoryAuctionRepository>().When(c => c.ParentContext.Binding.Metadata.Get(MetadataBindingParamName, "") == MetadataBindingParamValue);

   17         }

   18 

   19         public override string MetadataBindingParamValue

   20         {

   21             get

   22             {

   23                 return "InMemory";

   24             }

   25         }

   26     }

As you can see this class inherits from LcaModule and implements two abstract members.

  1. The first, MetadataBindingParamValue, is a string property getter and returns a name that will be used by Ninject to identify this implementation.
  2. The second overriden member Load as actually overridden from the base class of LcaModule, NinjectModule, this method which tells Ninject that whenever an implementation of one of the classes in LcaModel is requested with the metadata parameter called "DataProvider" that matches MetadataBindingParamValue, Ninject should supply the InMemory implementation of that Interface. Note that line 12 of the listing above Binds AuctionService to itself, doing this forces Ninject to call the constructor using implementations of each of the types for which it has a binding.

With a simple in-memory implementation of the model in place next I added a new Console Application project to the solution called LightCameraAuction. This is the entry point of this application and its purpose is to first select the appropriate implementation of LcaModel based on a command line switch and then create some data and launch an Auction process. The Console application contains another class I called AutoBidder to act as a person may during the course of the auction by checking the current winning price and placing bids etc. The AutoBidder class contains an instance of a Bidder entity, a reference to the item for which this auction is being held and a maximum price that this Bidder is prepared to pay. There is also an enumeration to represent different personalities, for example a Bidder with a FrequentChecker personality will check the current winning bid very often and therefore will be more likely to win the auction provided the winning bid is not higher than their maximum bid price. If the winning bid does exceed the AutoBidder's maximum price then the Bidder leaves the Auction. For each AutoBidder created a new thread is created to simulate independent Bidder activity. The AutoBidder class also outputs message to the console window as various events happen such as making a bid etc. The AutoBidder exists purely as a means to simulate the auction process so I wont explain it however it is available in the accompanying download at the end of this post. The code for the console application’s Program class is shown below.

   12     class Program

   13     {

   14         static void Main(string[] args)

   15         {

   16             using (IKernel kernel = new StandardKernel())

   17             {

   18                 //load all NinjectModules in bin directory

   19                 kernel.Load("*.dll");

   20 

   21                 //default to in-memory implementation

   22                 string modelProvider = args.Length != 1 ? "InMemory" : args[0];

   23                 try

   24                 {

   25                     //get the relevant implementation of LcaModel using modelProvider

   26                     AuctionService service = kernel.Get<AuctionService>(m => m.Has(LcaModule.MetadataBindingParamName)

   27                                                                         && m.Get<string>(LcaModule.MetadataBindingParamName) == modelProvider);

   28 

   29                     //create some bidders (AutoBidders)

   30                     Bidder b1 = service.RegisterNewBidder("Fred");

   31                     Bidder b2 = service.RegisterNewBidder("Barney");

   32                     Bidder b3 = service.RegisterNewBidder("Wilma");

   33 

   34                     //create Auction for the Rosebud Sledge from Citizen Kane

   35                     var auction = service.CreateAuction("Rosebud sledge", new TimeSpan(0, 0, 4));

   36 

   37                     //make the bidders dance

   38                     AutoBidder.CreateBidder(service, b1, new Dictionary<Item, double> {

   39                         { auction.AuctionItem, 30.4}

   40                     }, AutoBidder.BidderType.OcassionalChecker);

   41                     AutoBidder.CreateBidder(service, b2, new Dictionary<Item, double> {

   42                         { auction.AuctionItem, 23.4}

   43                     }, AutoBidder.BidderType.FrequentChecker);

   44                     AutoBidder.CreateBidder(service, b3, new Dictionary<Item, double> {

   45                         { auction.AuctionItem, 2.4}

   46                     });

   47 

   48                     //start the aution

   49                     Console.WriteLine("Starting Auction");

   50                     auction.StartAuction();

   51 

   52                     //once the above methods returns, the auction is complete

   53                     service.EndAuction(auction);

   54                 }

   55                 catch (Exception ex)

   56                 {

   57                     //Ninject exeception thrown if line 26 finds no suitable bindings

   58                     if (ex.Message == "Sequence contains no elements")

   59                     {

   60                         //provide some feedback, eg list available bindings

   61                         var possibleProviders = new StringBuilder();

   62                         var providers = (from p in kernel.GetBindings(typeof(AuctionService))

   63                                          select p.Metadata.Get<string>(LcaModule.MetadataBindingParamName));

   64 

   65                         foreach (var prv in providers)

   66                         {

   67                             possibleProviders.AppendFormat("\"{0}\" ", prv);

   68                         }

   69 

   70                         Console.WriteLine(string.Format("Invalid data provider specified, possible values are {0}", possibleProviders.ToString()));

   71                     }

   72                 }

   73             }

   74         }

   75     }

This code is for the most part self-explanatory so I’ll only give a brief overview. The first actual real line of code creates an instance of the Ninject kernel wrapped in a using block - doing this isn&apos;t essential but good practice as it ensure that lifecycle reliant functions such as Dispose will be called appropriately. Next the code tells the Ninject Kernel to scan all the dlls in the bin directory via reflection and look for any instances of NinjectModule that they may contain. So far we have only one, the InMemoryModule we just created. next we look for a single command line parameter passed into the method and store it in a local variable called modeProvider. If none is found the variable defaults to "InMemory". The next and quite important section of code is wrapped inside a try-catch block. Line 26 asks the Ninject Kernel for an implementation of AuctionService that has a metadata parameter name matching the local variable modelProvider. This is what allows the console application to be totally ignorant of what implementation it’s going to use, all the calling code knows is that it has an AuctionService, the rest is taken care of by Ninject. If no matching bindings are found (eg none have the correct metadata parameter) then an exception is thrown. This is caught in the enclosing try-catch block and a cursory check of the exception message (line 58) ascertains if the reason for the exception is down to a lack of suitable bindings, if so a list of suitable bindings is fed back to the user. After the instance of AuctionService is created, the bidders are automated and the auction started. Once the auction ends, the winning bid is displayed to the console window. An example is shown below, obviously due to the random behaviour of AutoBidder yours will likely differ.

As you'll see from the looking at the console application, there is no reference to the in-memory implementation of the model. What this means is that the console application is indeed totally ignorant of the implementation being used. A downside of this lack of a reference means that on compilation the LcaModel.InMemory.dll will not be copied into the bin directory of the console application and therefore no suitable bindings would be found when trying to run it. To fix this you could copy the InMemory dll manually each time you change it but quite frankly I found that a bit of faff so I added a post-build event to the LcaModule.InMemory project to copy itself to the bin directory of the console application.

That concludes the first post. I've covered the model, how I’m going to use DI to allow easy switching of model providers, created a program to simulate the auction process and created a simple in memory implementation of the model. In the next post I'll hook up this application to actually persist this data to a database. Incidentally as you may note from the screen shot above, Barney checked the auction price far more frequently than Fred and consequently won the auction despite having a lower maximum bid than Fred - if you take nothing else from this post, if you want to win an online auction, keep checking your bids!

  • (This will not appear on the site)