S#arp Architecture
Navigation
Main Page
Random Page
Create a new Page
All Pages
Categories
Administration
File Management
Login/Logout
Language Selection
Your Profile
Create Account
Quick Search
Advanced Search »
Back
History
Tutorial 5: Retrieving Results via a Custom Repository
As requested, the requirements of this tutorial stipulate that the user may provide a filter to retrieve staff members matching their first name, last name, and/or employee number. The customized nature of this query won’t allow us to use the out of the box Repository very easily. Accordingly, we’re going to have to extend the Repository with a custom method. So first off, the test: <ol> <li> Go to Northwind.Tests/Northwind.Data and add a new class called StaffMemberRepositoryTests; this will contain a single test to prove that our currently nonexistent, custom Repository method is working as expected: {{{{ using NUnit.Framework; using SharpArch.Testing.NUnit.NHibernate; using Northwind.Core.DataInterfaces; using Northwind.Core; using System.Collections.Generic; using NUnit.Framework.SyntaxHelpers; using Northwind.Data; namespace Tests.Northwind.Data { <nowiki>[TestFixture]</nowiki> <nowiki>[Category("DB Tests")]</nowiki> public class StaffMemberRepositoryTests : RepositoryTestsBase { protected override void LoadTestData() { AddStaffMember("thistest_Filter", "Mike", "Park"); AddStaffMember("ABC123", "_test_FiLtEr_", "Vance"); AddStaffMember("GHI789", "Lynette", "Knackstedt"); AddStaffMember("DEF456", "Gerry", "Lundquistest_filtER"); } <nowiki>[Test]</nowiki> public void CanLoadStaffMembersMatchingFilter() { List<StaffMember> matchingStaffMembers = staffMemberRepository .FindAllMatching("TEST_FiLtEr"); Assert.That(matchingStaffMembers.Count, Is.EqualTo(3)); } private void AddStaffMember(string employeeNumber, string firstName, string lastName) { StaffMember staffMember = new StaffMember(employeeNumber) { FirstName = firstName, LastName = lastName }; staffMemberRepository.SaveOrUpdate(staffMember); FlushSessionAndEvict(staffMember); } private IStaffMemberRepository staffMemberRepository = new StaffMemberRepository(); } } }}}} The benefit of inheriting from {{RepositoryTestsBase}} is twofold: during setup, an in-memory SQLite database is created and the NHibernate session factory is initialized. Within the {{LoadTestData}}, you can then insert data into this in-memory database which will be used by the unit tests. Note that after each staff member is inserted into the database, it is flushed and evicted. The flush commits the new staff member to the database and the eviction removes the staff memory from NHibernate’s radar. In this way, when you retrieve a staff member from the database, NHibernate makes a trip to the database without pulling the object from cache. Also notice, at the top of the test class, an attribute has been included to indicate that this test is within the "DB Tests" category. Categorizing this test enables you to disable the running of these unit tests within NUnit. Why would you want to do that? (Well, we disabled them just a bit ago in order to focus a bit longer on the domain before moving on to the data layer.) Typically, you’d disable this category of tests because unit tests which connect to a database are slow, taking a few seconds to load and run. This doesn’t sound too bad until you have dozens or hundreds of DB tests to run. If all the tests take more than a few seconds to run, then developers stop running them. And once developers stop running them, tests start breaking and the quality of the code degrades. So by turning the DB tests off while running the domain logic tests allows a developer to run all of the fast tests very often and run the time consuming tests when appropriate. The continuous integration server will also be running all of the longer running tests every time a change is checked in, so they’ll get checked sooner rather than later anyway…assuming you’re a good coder who checks in changes frequently with no broken unit tests! Testing against SQLite greatly improves the performance, but it’s still nice to be able to turn them off in some situations. Finally note, in the unit test, we’re asserting that we expect three matching staff members to be returned from the Repository method. </li> <li> Since we’ve already created {{IStaffMemberRepository}}, but haven’t created the {{StaffMemberRepository}} concrete implementation, this unit test will not compile. At this point, add a public {{StaffMemberRepository}} class to Northwind.Data and have it inherit from {{Repository<StaffMember>}} and implement {{IStaffMemberRepository}}. To get the solution to compile, you’ll need to also implement the {{FindAllMatching}} method from {{IStaffMemberRepository}}, but just throw a {{NotImplementedException}} for now. {{{{ using Northwind.Core; using SharpArch.Data.NHibernate; using Northwind.Core.DataInterfaces; using System.Collections.Generic; using System; namespace Northwind.Data { public class StaffMemberRepository : Repository<StaffMember>, IStaffMemberRepository { public List<StaffMember> FindAllMatching(string filter) { throw new NotImplementedException(); } } } }}}} </li> <li> Now back to NUnit and run the unit tests to see our {{NotImplementedException}} faithfully being raised within the {{CanLoadStaffMembersMatchingFilter}} test. </li> <li> To get the test to pass, we now need to fill in the details of our custom Repository method. As was done in a previous step, a new class, called {{StaffMemberRepository}} was added to Northwind.Data which implements {{IStaffMemberRepository}}. Use the following to build out the remaining details of this class: {{{{ using Northwind.Core.DataInterfaces; using Northwind.Core; using System.Collections.Generic; using NHibernate; using SharpArch.Data.NHibernate; using NHibernate.Criterion; namespace Northwind.Data { public class StaffMemberRepository : Repository<StaffMember>, IStaffMemberRepository { public List<StaffMember> FindAllMatching(string filter) { ICriteria criteria = Session.CreateCriteria(typeof(StaffMember)) .Add( Expression.Or(Expression.InsensitiveLike("EmployeeNumber", filter, MatchMode.Anywhere), Expression.Or(Expression.InsensitiveLike("FirstName", filter, MatchMode.Anywhere), Expression.InsensitiveLike("LastName", filter, MatchMode.Anywhere))) ) .AddOrder( new NHibernate.Criterion.Order("LastName", true) ); return criteria.List<StaffMember>() as List<StaffMember>; } } } }}}} The above code implements and inherits from two objects: {{IStaffMemberRepository}} which we’ve already seen, and {{Repository<StaffMember>}} which comes from SharpArch.Data.NHibernate. The Repository base class implements {{IRepository}} and provides most of the basic Repository CRUD functionality that we’ll ever need. The benefit to inheriting from this base implementation in our custom Repository is that we only need to provide details for our custom method, {{FindAllMatching(string filter)}}. '''Writing a custom Repository is usually the exception than the norm; more often than not, Repository will provide all the data access functionality we need for a given Entity'''. But you still have a few options to choose from when the need arises; in fact, you have three options when it comes to a using a data repository: <ul> <li>You can use the concrete “out of the box” repository {{SharpArch.Data.NHibernate.Repository}}, implementing {{SharpArch.Core.PersistenceSupport.IRepository}}, which includes {{Get, GetAll, GetByProperties, GetUniqueByProperties, SaveOrUpdate}}, and {{Delete}} - this will likely be the most common scenario. This also serves as a good base class for your own custom repositories.</li> <li>You can use the more NHibernate specific interface {{SharpArch.Core.PersistenceSupport.NHibernate.INHibernateRepository}} which includes NHibernate specific additions to {{IRepository}}, including Get with a lock mode, Load with and without a lock mode, {{GetByExample, GetUniqueByExample, Save}} (for assigned IDs), {{Update}} (also for assigned IDs), and {{Evict}}. The concrete implementation to be used with this is {{SharpArch.Data.NHibernate.NHibernateRepository}}. But since controllers only depend on the repository interfaces, and not the underlying repository, the controllers’ layer sees them as different objects. Accordingly, for the benefit of keeping your layers loosely coupled to NHibernate, you should try to avoid using this more NHibernate specific interface if possible. But if you have an object with an assigned ID, it’s unavoidable as you’ll need the separate Save and Update methods.</li> <li>Alternatively, you can extend {{IRepository}} and the {{Repository}} class with your own custom methods as we’re about to do.</li> </ul> </li> <li> While the above discusses the concrete implementations that are available for out of the box usage, it should be clarified that there are exactly four interfaces which are the basis for all repository objects (including your own custom repositories): </li> <ul> <li>{{SharpArch.Core.IRepositoryWithTypedId<T, IdT>}}: This provides exposure to very basic generic repository methods; e.g., {{Get()}} and {{SaveOrUpdate()}}. It accepts the domain object domain, and the type of the domain object's ID property to communicate with the database.</li> <li>{{SharpArch.Core.IRepository<T>}}: This is nothing more than {{IRepositoryWithTypedId}} with a default ID type of int. This is expected to be used almost exclusively and certainly more than any other repository.</li> <li>{{SharpArch.Core.NHibernate.INHibernateRepositoryWithTypedId<T, IdT>}}: This extends the basic repository methods with other methods which are specific to NHibernate; e.g., {{GetByExample, Save, Update}}, etc. This would be a good interface to add {{GetByPage}}, and other non-trivial repository methods which have some insight about the internals of NHibernate. As a general rule, the use of this repository should be avoided unless absolutely necessary; e.g., if you're using a domain object with an assigned ID (ack!).</li> <li>{{SharpArch.Core.NHibernate.INHibernateRepository<T>}}: Like {{IRepository<T>}}, this is nothing more than {{INHibernateRepositoryWithTypedId}} with a default ID type of int. And similarly to {{NHibernateRepositoryWithTypedId}}, the use of this repository should be avoided unless absolutely necessary.</li> </ul> <li> In the {{StaffMemberRepository}} code that you just wrote (or copy and pasted anyway), the querying mechanism uses Hibernate Query Language (HQL); for more information about using HQL, see the NHibernate reference documentation at [http://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html|http://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html]. My only (very big) complaint with HQL is that string literals are used instead of strongly typed references; a few good solutions exist to solve this problem: <ul> <li>LINQ to NHibernate: This is still a work in progress, but it’s coming along very nicely. You can read more about this LINQ provider at [http://www.hookedonlinq.com/LINQToNHibernate.ashx|http://www.hookedonlinq.com/LINQToNHibernate.ashx]. For your convenience, NHibernate.Linq.dll has been included in the VS project template’s /lib folder and added to YourProject.Data as a reference. ''But, I would not recommend using it in production code until the NHibernate team has released a more mature version''.</li> </li>Typesafe ICriteria using Lambda Expressions: [http://nhforge.org/blogs/nhibernate/archive/2009/01/07/typesafe-icriteria-using-lambda-expressions.aspx|http://nhforge.org/blogs/nhibernate/archive/2009/01/07/typesafe-icriteria-using-lambda-expressions.aspx]. This is useful as it gives you the expressiveness of HQL, which is quite powerful, while still being strongly typed.</li> <li>NHibernate Query Generator (which is now deprecated): [http://www.ayende.com/Blog/archive/7186.aspx|http://www.ayende.com/Blog/archive/7186.aspx]</li> </ul> </li> <li> With this table in place, and after a very long winded interlude, you should now be able to run all of the unit tests and see them pass. The {{CanLoadStaffMembersMatchingFilter()}} test successfully connected to the SQLite database, created the database using schema information from our mapping classes, populated the StaffMembers table with test data, ran the provided filtering query, hydrated the objects with NHibernate’s help, and returned the results to the unit test as a strongly typed listing of StaffMember objects. No datasets ...no ADO.NET ...no connection object management…no stored procedures…no thousands of lines of auto-generated data access code!</li> <li> There’s nothing to refactor at this point, but a five minute break is certainly warranted! </li> </ol> ((( [TutorialIndex|Index] | Back: [Tutorial4WireupPersistenceSupport|Wiring Up Persistence Support] | Next: [Tutorial6ShowResultsInView|Showing the Results in a View] )))
ScrewTurn Wiki
version 2.0.36. Some of the icons created by
FamFamFam
.