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.
![]()