Question

I'm using the following IEqualityComparer to strip special characters from a company name before comparison as follows:

 public class CompanyNameIgnoringSpaces : IEqualityComparer<LeadGridViewModel>
    {
        public bool Equals(LeadGridViewModel x, LeadGridViewModel y)
        {
            var delimiters = new[] {' ', '-', '*', '&', '!'};
            return delimiters.Aggregate(x.CompanyName ?? String.Empty, (c1, c2) => c1.Replace(c2, '\0')) 
                == delimiters.Aggregate(y.CompanyName ?? String.Empty, (c1, c2) => c1.Replace(c2, '\0'));
        }

        public int GetHashCode(LeadGridViewModel obj)
        {
            var delimiters = new[] {' ', '-', '*', '&', '!'};
            return delimiters.Aggregate(obj.CompanyName ?? String.Empty, (c1, c2) => c1.Replace(c2, '\0')).GetHashCode();
        }
    }

In order to call this when running a query, I use the following:

var results = result
                  .GroupBy(c => c, new CompanyNameIgnoringSpaces())
                  .Select(g => new LeadGridViewModel
                  {
                      LeadId = g.First().LeadId,
                      Qty = g.Count(),
                      CompanyName = g.Key.CompanyName,
                  }).OrderByDescending(x => x.Qty).ThenBy(x => x.CompanyName).ToList();

How would I use this comparer in a LINQ query in order to find all records which match the input string (company name)?

For example:

 public List<LeadGridViewModel> AllByName(string name, int company)
        {

            var result = (from t1 in db.Leads
                          where
                              t1.Company_ID == company && t1.Company_Name == name...

So instead of using t1.Company_Name == name, I'd like to use use the equality comparer for this to include the special character replacement.

Was it helpful?

Solution

  1. Using an IEqualityComparer in a linqToDatabase query: Unless you can encapsulate the logic in a form that the provider can translate then this will have to be done locally. From the route you are taking with this problem I assume the provider was not able to handle it. So we can do the following on the local side:

    1. Pull it all in:

      var results = result.ToArray()
            .GroupBy(c => c, new CompanyNameIgnoringSpaces())
            .Select(g => new LeadGridViewModel
            {
                LeadId = g.First().LeadId,
                Qty = g.Count(),
                CompanyName = g.Key.CompanyName,
            }).OrderByDescending(x => x.Qty).ThenBy(x => x.CompanyName).ToList();
      
    2. ToLookup()

      var results = result
            .ToLookup(c => c, new CompanyNameIgnoringSpaces())
            .Select(g => new LeadGridViewModel
            {
                LeadId = g.First().LeadId,
                Qty = g.Count(),
                CompanyName = g.Key.CompanyName,
            }).OrderByDescending(x => x.Qty).ThenBy(x => x.CompanyName).ToList();
      
    3. Stream the results

      var results = result.AsEnumerable()
            .GroupBy(c => c, new CompanyNameIgnoringSpaces())
            .Select(g => new LeadGridViewModel
            {
                LeadId = g.First().LeadId,
                Qty = g.Count(),
                CompanyName = g.Key.CompanyName,
            }).OrderByDescending(x => x.Qty).ThenBy(x => x.CompanyName).ToList();
      

    Note: Most variants of sql have a replace function so you could potentially hard-code the replaces into an expression and hence do this on the server side. If you wanted it to be reusable you would want something like the following:

    private static Expression<Func<LeadGridViewModel,String>> leadGridTransform = 
        (lead) => lead.CompanyName == null ? "", lead.CompanyName.Replace(' ','\0').Replace.... ;
    

    Which you would then be able to use in Queryable expressions:

    var results = result.GroupBy(leadGridTransform)....
    

    The ternary operator was used here as it avoids costly server side coalesce operations.

  2. Find all records for a certain string: Here you really want to invert the comparison and find all strings that will be equivalent to the given string then query for the company name being contained in them. So assuming that db.Leads is a table of LeadGridViewModel so we can use the comparer

    1. Implicit inversion:

      public List<LeadGridViewModel> AllByName(string name, int company)
      {
          var comparer = new CompanyNameIgnoringSpaces();
          var group = db.Leads.Select(lead => new LeadGridViewModel
                                      { CompanyName = lead.CompanyName })
                              .AsEnumerable()
                              .Where(lead => comparer.Equals(name, lead.CompanyName)
                              .ToArray();
      
          var result = db.Leads.Where(lead => lead.Company_ID == company)
                               .Where(lead => group.Contains(lead.CompanyName))...
      
    2. Explicit Inversion:

      private var delimiters = new[] {' ', '-', '*', '&', '!'};
      public List<LeadGridViewModel> AllByName(string name, int company)
      {
          var atomicName = name.Trim().Split(delimiters);
          IEnumerable<String> permutations = atomicName.Aggregate(
                    new String[] {""}, 
                    (accumulate,atom) => atom == "" ? accumulate.Join(
                           delimiters,str => true,chr => true,(str,chr) => str + atom + chr.ToString()) : accumelate)
                   .ToArray();
      
          var result = db.Leads.Where(lead => lead.Company_ID == company)
                               .Where(lead => permutations.Contains(lead.CompanyName))...
      

Notes: For the inversion of comparison both these methods have there own limitations. In the first implementation the bigger the databases the slower it builds the available grouping. For the second implementation the more delimiters the name has the more time it will take to build the permutations (exponentially so). If you do hard code the replaces then this can be done server side and hence avoid these limitations.

OTHER TIPS

You can't. The code used to call (and even the code used within) the IEqualityComparison is not translatable to SQL query because the code in IEqualityComparison is compiled and not built using Expression objects.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top