Continuing from my last post, I’ll demonstrate how to create a fluent interface so that you can do:
123456789101112 | varcustomer=Session.Query().InTransaction.Contacts.ByPosition("Developer").ThatHave.NoPhoto().And.TasksInProgress().And.TasksWith(Priority.High).FirstOrDefault();
|
First, let’s look at the ‘beginning’ of the fluent interface: the Query()
extension method.
123456789101112131415 | publicstaticclassQueryExtensions{publicstaticIQueriesQuery(thisSessionsession){returnnewQueries(session);}// If we're using XAF, do the same for ObjectSpace as wellpublicstaticIQueriesQuery(thisIObjectSpaceobjectSpace){varxpObjectSpace=objectSpaceasXPObjectSpace;varsession=xpObjectSpace.Session;returnnewQueries(session);}}
|
What does the Queries()
class look like?
12345678910111213141516171819202122232425262728293031323334353637383940 | publicinterfaceIQueries{IQueriesInTransaction{get;}IContactQueriesContacts{get;}// One for each queryable object type, e.g.,// IDepartmentQueries Departments { get; } // ITaskQueries Tasks { get; }// etc.}publicclassQueries:IQueries{publicQueries(Sessionsession){_Session=session;}privatereadonlySession_Session;privatebool_InTransaction;publicIQueriesInTransaction{get{_InTransaction=true;returnthis;}}privateIContactQueries_Contacts;publicIContactQueriesContacts{get{if(_Contacts==null)_Contacts=newContactQueries(_Session,_InTransaction);return_Contacts;}}}
|
If we ignore the InTransaction
property, it is just a container for the IContactQueries
. In your application, you would have a similar property for each queryable object type. A new ContactQueries
instance is created on demand taking into account the whether the InTransaction
property was visited earlier in the syntax.
Now, let’s look at the base classes.
12345678910111213141516171819202122232425 | publicinterfaceIQueries<T>:IEnumerable<T>,IFluentInterface{}publicclassQueries<T>:IQueries<T>{publicQueries(Sessionsession,boolinTransaction){_Session=session;Query=newXPQuery<T>(session,inTransaction);}privatereadonlySession_Session;protectedIQueryable<T>Query{get;set;}publicIEnumerator<T>GetEnumerator(){returnQuery.GetEnumerator();}IEnumeratorIEnumerable.GetEnumerator(){returnQuery.GetEnumerator();}}
|
So Queries<T>
wraps an XPQuery<T>
.
Side note: the inclusion of IFluentInterface
is a clever trick to improve Intellisense by hiding the System.Object
members such as ToString()
. See Daniel Cazzulino’s blog post.
And now we can implement the Contact
generic as follows:
12345678910111213141516171819202122232425262728293031 | publicinterfaceIContactQueries:IQueries<Contact>{IContactQueriesByDepartmentTitle(stringdepartmentTitle);IContactQueriesByPosition(stringposition);ContactByEmail(stringemail);}publicclassContactQueries:Queries<Contact>,IContactQueries,IContactThatHaveQueries{publicContactQueries(Sessionsession,boolinTransaction):base(session,inTransaction){}publicIContactQueriesByDepartmentTitle(stringdepartment){Query=Query.Where(p=>p.Department.Title==department);returnthis;}publicIContactQueriesByPosition(stringposition){Query=Query.Where(p=>p.Position.Title==position);returnthis;}publicContactByEmail(stringemail){returnQuery.SingleOrDefault(p=>p.Email==email);}}
|
There we go. Now we can use our fluent interface:
1 | varcontacts=session.Query().Contacts.ByPosition("Manager");
|
Much more readable. Also more maintainable because all queries are in one place and make use of good old LINQ. It’s also easier to test the queries because they are independent of the calling code.
See a sample implementation built against the DevExpress XAF MainDemo on GitHub.
![]()