1
\$\begingroup\$

I have this WCF service providing search functionality - to be used by our client in his web application. There can be any number of search parameters and possible nesting of AND/OR operations.

Could you please have a look and share some ideas on if I can make it more client-friendly?

  <con:Search>
     <con:clientID>xxx</con:clientID>
     <con:propertyQueries>
        <wil:PropertyQuery Id="0">
            <wil:PropertyID>DocumentType</wil:PropertyID>      
            <wil:Value>Statement</wil:Value>
            <wil:Operator>EQUALS</wil:Operator> 
        </wil:PropertyQuery>        
        <wil:PropertyQuery Id="1" AND="0">
            <wil:PropertyID>BANK</wil:PropertyID>      
            <wil:Value>HSBC</wil:Value>             
            <wil:Operator>EQUALS</wil:Operator> 
        </wil:PropertyQuery>        
        <wil:PropertyQuery Id="2" OR="1">
            <wil:PropertyID>BANK</wil:PropertyID>      
            <wil:Value>BARCLAYS</wil:Value>
            <wil:Operator>EQUALS</wil:Operator> 
        </wil:PropertyQuery>    
        <wil:PropertyQuery Id="3" AND="0">
            <wil:PropertyID>Date</wil:PropertyID>      
            <wil:Value Start="1">11/01/2014</wil:Value>
            <wil:Value End="1">11/02/2014</wil:Value>
            <wil:Operator>BETWEEN</wil:Operator>    
        </wil:PropertyQuery>            
    </con:propertyQueries>
  </con:Search>   

The values in AND and OR attribute indicates the Id of PropertyQuery to which the condition to be added. For example:

<wil:PropertyQuery Id="2" OR="1">

performs an OR operation between Property Queries with ID of 1, 2.

Any better ideas out there to improve the interface?

\$\endgroup\$
  • \$\begingroup\$ Can you add information like is this a search page, and the user customises the AND/OR scenarios for all permutations? Is this application used for the internet or intranet? Do you provide a calling proxy so that users do not call this service directly, meaning you have some control over the data coming in? \$\endgroup\$ – Dominic Zukiewicz Feb 12 '14 at 9:14
  • \$\begingroup\$ Any final solution with full source code sample application ? IMHO, better samples for minimize learning curve are real applications with full source code and good patterns \$\endgroup\$ – Kiquenet Sep 25 '14 at 8:32
1
\$\begingroup\$

The way you're composing expressions seems very unintuitive to me. I think that you should take advantage of the fact that you're trying to express (part of) the SQL syntax tree in the XML tree. So, AND should be a separate node and it should have its child expressions as child nodes. Something like (if I misunderstood how your AND/ORs work, that only proves my point):

<wil:combinedQuery operator="AND">
    <wil:combinedQuery operator="AND">
        <wil:PropertyQuery Id="0">
            <wil:PropertyID>DocumentType</wil:PropertyID>      
            <wil:Value>Statement</wil:Value>
            <wil:Operator>EQUALS</wil:Operator> 
        </wil:PropertyQuery>
        <wil:PropertyQuery Id="3">
            <wil:PropertyID>Date</wil:PropertyID>      
            <wil:Value Start="1">11/01/2014</wil:Value>
            <wil:Value End="1">11/02/2014</wil:Value>
            <wil:Operator>BETWEEN</wil:Operator>    
        </wil:PropertyQuery> 
    </wil:combinedQuery>
    <wil:combinedQuery operator="OR">
        <wil:PropertyQuery Id="1" AND="0">
            <wil:PropertyID>BANK</wil:PropertyID>      
            <wil:Value>HSBC</wil:Value>             
            <wil:Operator>EQUALS</wil:Operator> 
        </wil:PropertyQuery>        
        <wil:PropertyQuery Id="2" OR="1">
            <wil:PropertyID>BANK</wil:PropertyID>      
            <wil:Value>BARCLAYS</wil:Value>
            <wil:Operator>EQUALS</wil:Operator> 
        </wil:PropertyQuery>   
    </wil:combinedQuery>
</wil:combinedQuery>

Dates should be in the unambiguous ISO 8601 format, not in the ambiguous and confusing (mm/dd/yyyy, or is it dd/mm/yyyy?) format.


I don't like much the way you're representing BETWEEN. I think that Start and End should be in elements (or attributes) with those names.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks a lot for your reply. I think apart from being unintuitive my solution was not foolproof because I was not always sure what the end-user actually wants-ending up myself being manipulating in wrong way - so your reply does help. Could you please give an example for how you will prefer to have values for Start and End date defined. \$\endgroup\$ – user1552869 Feb 12 '14 at 9:55
  • \$\begingroup\$ Apart from Between, I have other operators as: Equals, NotEquals, StartsWith, EndsWith etc... All other operators apart from Between need one value. So, one option could be to have same node<Value> declared twice then my app will figure out lower and upper range after comparing the two values. \$\endgroup\$ – user1552869 Feb 12 '14 at 9:59
  • \$\begingroup\$ do you have any comments on how Start and End date should be defined? I do not want to add separate elements ValueFrom and ValueTo just for Between operator. \$\endgroup\$ – user1552869 Feb 12 '14 at 10:53
  • \$\begingroup\$ @user1552869 I'm not sure what would be a good solution for that. \$\endgroup\$ – svick Feb 12 '14 at 11:22
1
\$\begingroup\$

Without knowing its intended audience (Intranet / Internet), I'm assuming Intranet.

In my opinion, you are giving a lot of flexibility to this service in one method call, which isn't immediately clear how a user would structure a call to this. The caller has to have knowledge of a "propertyId" which doesn't make too much sense on a glance. I hope your API documentation describes this well, and how the user would retrieve what these IDs are.

So here is my assumption of the coded interface:

public interface ISearchService
{
    List<object> Search(string clientId, IEnumerable<PropertyQuery> propertyQueries);
}

// Element
public class PropertyQuery
{
   // Attribute
   public string PropertyID { get; set; }

   // Array
   public IEnumerable<Value> Values { get; set; }

   // Attribute
   public byte AND { get; set; }

   // Attribute
   public byte OR { get; set; }

   // Element
   public string Operator { get; set; }
}

// Element
public class Value
{
   // Attribute
   public int Id { get; set; }

   // Attribute
   public byte Start { get; set; }

   // Attribute
   public byte End { get; set; }

   // Text
   public string Text { get; set; }
}

To make this a little simpler, I would add a single class that takes all parameters and a class returning the results, following the Request/Response pattern. This means that the interface can change over time, and should not affect backwards compatibility.

public interface ISearchService
{
   // Gets the search criteria for the screen
   SearchCriteriaResponse GetSearchCriterias(SearchCriteriaRequest request);

   // Searches by the search criteria
   SearchResponse SearchBy(SearchRequest searchCriteria);
}

// Base elements for a response
public abstract class ResponseBase
{
   public bool Success { get; set; }
   public string Message { get ; set; }
}

public class SearchCriteriaResponse : ResponseBase
{
   public string[] DocumentTypes { get; set; }
   public string[] BankName { get; set; }
}

public class SearchResponse : ResponseBase
{
   public IEnumerable<SearchResult> Results { get; set; }
}

public class SearchRequest
{
   public string ClientId { get; set; }
   public string[] BankNamesToMatchExactly { get; set; }
   public string[] BankNamesToPartiallyExactly { get; set; }
   public string[] DocumentTypesToMatchExactly { get; set; }
   public string[] DocumentTypesToPartiallyExactly { get; set; }
   public DateTime? StartDate { get; set; }
   public DateTime? EndDate { get;set; }
   public bool CheckBetweenDates { get; set; }
}

This interface is a more clean on its approach. Its slightly larger, but it makes the ORing and ANDing much more obvious. It also makes the code cleaner at the service side as each of these properties have well-named properties and should be easy to implement:

public List<SearchResponse> SearchBy(SearchRequest searchCriteria)
{
   IQueryable dbQuery = DbContext.BankNames;

   foreach(var bankName in searchCriteria.BankNamesToMatchExactly)
   {
      dbQuery = dbQuery.Where(bn => bn.BankName == bankName);
   }

   foreach(var bankName in searchCriteria.BankNamesToMatchPartially)
   {
      dbQuery = dbQuery.Where(bn => bn.BankName.Contains(bankName);
   }

   // etc..
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Any final solution with full source code sample application ? IMHO, better samples for minimize learning curve are real applications with full source code and good patterns \$\endgroup\$ – Kiquenet Sep 25 '14 at 8:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.