1

I have a table of objects in the database, which each have a dictionary of key/value pairs as metadata associated with them. I need to be able to pass in to my function a list of search criteria and return results that match on each of these, including wildcards. Eg passing in

Name: "TEST%" Version: "1" Creator: "jamesk"

Should return all objects created by jamesk, with version 1 and name beginning with TEST.

My code to do this is as follows

itemsFound = (from h in _objectStoreRepository.GetHeaders()
       where
        (from i in h.Items
         where i.CreatedAt <= request.AsAtTime &&
               (from f in request.SearchFilters select f)
                   .All(f =>
                        (from mdp in i.MetaDataPairs
                         where mdp.Key.ToLower() == f.Key.ToLower() &&
                         mdp.Value.ToLower().Like(f.Value.ToLower())
                         select mdp).Any()
                    )          
          select i).Any()
         select h).ToList();

I would like to do it all in one query if possible but trying to get Linq to Entities to do everything in one go is proving tough. The above returns the following error.

ERROR: Failed getting objects. Error was Unable to create a constant value of type 'System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'. Only primitive types or enumeration types are supported in this context.

I think I understand the problem, I am trying to create a set to use in my LINQ to Entities query syntax from request.SearchFilters, which is a dictionary but it is saying it can only create a collection from primitive values.

I understand that, but I cannot see how I can then achieve what I need to do as I need to match on both the key and the value of the pair.

Can anyone suggest how to get this working?

UPDATE: I have gotten a little further, splitting out the KVP as below

itemsFound = (from h in _objectStoreRepository.GetHeaders()
           where
            (from i in h.Items
             where i.CreatedAt <= request.AsAtTime &&
               (from f in request.SearchFilters.Keys select f)
                   .All(k => 
                       (from mdp in t.TradeMetaDataPairs
                        where mdp.Key.ToLower() == k.ToLower() &&
                        mdp.Value.ToLower().Like(request.SearchFilters[k].ToLower())
                           select mdp).Any()
                         )                                                      
                      select t).Any()
                   select th).ToList();

Which appears to be accepted, however I now run into the problem that my extension method Like() isn't accepted.

I wrote this because I needed to simulate the SQL "like" query and there was no way to do this. Contains only simulates %x%, startswith only simulates x%, endswith only simulates %x and nothing simulates x%x. Besides I need them in a single statement. Why isn't there direct support for SQL LIKE in linq to entities? its a native SQL function, surely this should be supported :-/

UPDATE 2: Added my like method

 public static class LikeStringExtension
    {
        public static bool Like(this string toSearch, string toFind)
        {
            return new Regex(@"\A" + new Regex(@"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\").Replace(toFind, ch => @"\" + ch).Replace('_', '.').Replace("%", ".*") + @"\z", RegexOptions.Singleline).IsMatch(toSearch);
        }        
    }
NZJames
  • 4,963
  • 15
  • 50
  • 100
  • You could use SqlMethods.Like to implement this functionality. Try replace "mdp.Value.ToLower().Like(request.SearchFilters[k].ToLower())" with "SqlMethods.Like(mdp.Value.ToLower(),request.SearchFilters[k].ToLower())" . Also check these out https://msdn.microsoft.com/en-us/library/bb355235(v=vs.110).aspx and http://stackoverflow.com/questions/835790/how-to-do-sql-like-in-linq – jambonick Feb 02 '17 at 11:31
  • It's telling you that `KeyValuePair` can't be translated into SQL. That makes it pretty nasty to do this at the database only. – Gert Arnold Feb 02 '17 at 11:35
  • 1
    @NZJames hi - would love to help but i did not understand your question. " showing the schema would help conceptualise what data is there and how to get it out, and what your code is currently doing. – BenKoshy Feb 02 '17 at 11:57
  • I've tried the SqlMethods.Like and I get - Assert.IsFalse failed. An unexpected error occurred: LINQ to Entities does not recognize the method 'Boolean Like(System.String, System.String)' method, and this method cannot be translated into a store expression. – NZJames Feb 02 '17 at 13:05
  • If you are targeting SqlServer only, you could use [`Sqlfunctions.PatIndex`](https://msdn.microsoft.com/en-us/library/system.data.objects.sqlclient.sqlfunctions.patindex(v=vs.110).aspx). But then you'll get another exception that dictionary indexer is not supported. – Ivan Stoev Feb 02 '17 at 13:22
  • It's true actually, SqlMethods is supported only for Linq to Sql, not Linq to Entities. Can you show us your Like() extension method? – jambonick Feb 02 '17 at 13:42
  • Sure, added to original question above – NZJames Feb 02 '17 at 14:01
  • How do you think your `Like` method should be translated into SQL? You don't seem to realize that everything between `(from` and `select th)` is one expression that EF tries to convert into SQL. Even of .Net methods very few are supported, let alone your own methods. – Gert Arnold Feb 02 '17 at 21:13

0 Answers0