6

i am writing a query which required 'WHERE IN' like clause in realm.xamarin linq query. below is the query i tried:

var IdsToMatch = observations.Select(x => x.Id)
var results = from d in realm.All<Observations>() where IdsToMatch.Any(p => p == d.Id) select d;

also tried this with Contains:

var results = from d in realm.All<Observations>() where IdsToMatch.Contains(d.Id) select d;

but this throws error:

The method 'Contains' is not supported
The method 'Any' is not supported

What is the solution for this?

EDIT :

here is the stack trace for the error:

UNHANDLED EXCEPTION: 06-07 15:04:55.097 I/MonoDroid(24526): System.NotSupportedException: The method 'Any' is not supported 06-07 15:04:55.097 I/MonoDroid(24526): at Realms.RealmResultsVisitor.VisitMethodCall (System.Linq.Expressions.MethodCallExpression m) [0x00596] in :0 06-07 15:04:55.097 I/MonoDroid(24526): at Realms.ExpressionVisitor.Visit (System.Linq.Expressions.Expression exp) [0x000ec] in :0 06-07 15:04:55.097 I/MonoDroid(24526): at Realms.RealmResultsVisitor.VisitMethodCall (System.Linq.Expressions.MethodCallExpression m) [0x0006a] in :0 06-07 15:04:55.097 I/MonoDroid(24526): at Realms.ExpressionVisitor.Visit (System.Linq.Expressions.Expression exp) [0x000ec] in :0 06-07 15:04:55.097 I/MonoDroid(24526): at Realms.RealmResults1[T].CreateResultsHandle () [0x00037] in <filename unknown>:0 06-07 15:04:55.097 I/MonoDroid(24526): at Realms.RealmResults1[T].get_ResultsHandle () [0x0000d] in :0 06-07 15:04:55.097 I/MonoDroid(24526): at Realms.RealmResults1[T].GetEnumerator () [0x00000] in <filename unknown>:0 06-07 15:04:55.097 I/MonoDroid(24526): at System.Collections.Generic.List1[T]..ctor (IEnumerable1 collection) [0x00073] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/collections/generic/list.cs:104 06-07 15:04:55.098 I/MonoDroid(24526): at System.Linq.Enumerable.ToList[TSource] (IEnumerable1 source) [0x00011] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/System.Core/System/Linq/Enumerable.cs:835 06-07 15:04:55.098 I/MonoDroid(24526): at VHS.MobileApp.Mediq.DataAccess.ObservationDatabase+<>c__DisplayClass13.b__8 () [0x00147] in d:\rwagh\mediq\Code\VHS.MobileApp.Mediq.DataAccess\ObservationDatabase.cs:65 06-07 15:04:55.098 I/MonoDroid(24526): at System.Threading.Tasks.Task1[TResult].InnerInvoke () [0x00012] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/threading/Tasks/Future.cs:686 06-07 15:04:55.098 I/MonoDroid(24526): at System.Threading.Tasks.Task.Execute () [0x00016] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/threading/Tasks/Task.cs:2523 06-07 15:04:55.098 I/MonoDroid(24526): --- End of stack trace from previous location where exception was thrown --- 06-07 15:04:55.098 I/MonoDroid(24526): at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 06-07 15:04:55.098 I/MonoDroid(24526): at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00047] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:201 06-07 15:04:55.098 I/MonoDroid(24526): at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:170 06-07 15:04:55.098 I/MonoDroid(24526): at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:142 06-07 15:04:55.098 I/MonoDroid(24526): at System.Runtime.CompilerServices.TaskAwaiter1[TResult].GetResult () [0x00000] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:372

EDIT 2 :

var IdsToMatch = observations.Select(x => x.Id)
var results = from d in realm.All<Observations>() where IdsToMatch.Any(p => p == d.Id) select d;

tried: var IdsToMatch = observations.Select(x => x.Id).AsQueryable() as per https://github.com/realm/realm-dotnet/blob/7187390529201ba843cd105fc6e3e11acb0c6217/Realm.Shared/linq/RealmResultsVisitor.cs#L121 which says

internal override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable)) { ...

but that didn't work too.

SushiHangover
  • 73,120
  • 10
  • 106
  • 165
RohitWagh
  • 1,999
  • 3
  • 22
  • 43
  • 1
    My guess is that the expression visitor does not support those methods with the external enumerable you're using. You will either have to: - Get all records without the Where clause (use .ToLisT()) and then use the Contains on those in-memory results (probably not a good idea) Or - Loop through each Id in your enumerable and query for those Ids one-by-one. Although. Have you tried `p => p.Id.In(IdsToMatch)` – Kana Ki Jun 07 '16 at 10:01
  • @AndyJames "Id" is a primitive type (Int/String), so "In" is not a function available on it. – RohitWagh Jun 07 '16 at 12:24
  • A lot of frameworks offer `In` as a generic extension method, so, it would be valid for object. However, if you don't have that defined anywhere, especially not within Realms, the expression visitor just won't know what that is and die anyway. Probably not an option in that case. – Kana Ki Jun 07 '16 at 12:38
  • I have added a Realm issue for this https://github.com/realm/realm-dotnet/issues/604 but please supply more info as it's not totally clear from your sample what you are doing. Is IdsToMatch just a list of IDs? – Andy Dent Jun 08 '16 at 09:24
  • I suspect you are trying to do something like an IN query in SQL terms, see http://stackoverflow.com/questions/2334327/what-is-the-linq-equivalent-to-the-sql-in-operator – Andy Dent Jun 08 '16 at 09:27
  • @AndyDent i tried those solutions but none work. also the accepted answer would not work for me as i have random numbers in the list on which Contains will be called and this is specific to realm.xamarin. – RohitWagh Jun 08 '16 at 09:30
  • What is the declaration for IdsToMatch? Please supply more code so I can understand what you're trying to do. – Andy Dent Jun 08 '16 at 09:54
  • @AndyDent check EDIT 2. – RohitWagh Jun 08 '16 at 10:04
  • See my solution below showing a workaround that you can use right now by building an expression tree. Apologies for my misunderstanding how you were using Contains - your code looks like valid LINQ. See https://blogs.msdn.microsoft.com/alexj/2009/03/25/tip-8-how-to-write-where-in-style-queries-using-linq-to-entities/ as a nice explanation for anyone else trying to understand this apparently-backward way to specify an IN search. – Andy Dent Jun 08 '16 at 14:14

1 Answers1

7

OK, that was a lot of fun. The answer (for right now) is to build a LINQ Expression Tree.

Realm supports LINQ - we parse the Expression Tree (OK, some bits are missing but the basics are there and we're improving regularly - we will add Contains).

So, any time you need to build up a completely arbitrary query expression at runtime you can do so with the power of LINQ.

The example below may appear a bit complicated but the point is that it uses a standard Expression tree so you can use any other code which builds such trees.

class HasIntKey : RealmObject {
    public int UserKey { get; set; }
}


private void FindingManyMatches()
{
    using (var theRealm = Realm.GetInstance ("TestingManyKeys.realm")) {
        // create some sample data
        theRealm.Write (() => {
            for (int i = 1; i < 10000; i++) {
                var obj = theRealm.CreateObject<HasIntKey> ();
                obj.UserKey = i;
            }
        });
        var allInts = theRealm.All<HasIntKey>();
        Console.WriteLine ($"Created {allInts.Count()} objects");

        var idsToMatch = new[] {42,1003,400,57, 6009};

        // technique for how to search for many matches
        // use Expression Trees to dynamically build the LINQ statement
        // see https://msdn.microsoft.com/en-us/library/mt654267.aspx
        ParameterExpression pe = Expression.Parameter (typeof(HasIntKey), "p");

        // building an expression like:
        // allInts.Where (p => p.UserKey == idsToMatch[0] || p.UserKey == idsToMatch[1]...);
        Expression chainedByOr = null;
        // left side of the == will be the same for each clause we add in the loop
        Expression left = Expression.Property(pe, typeof(HasIntKey).GetProperty("UserKey"));  
        foreach (int anId in idsToMatch) {
            // Create an expression tree that represents the expression 'p.UserKey == idsToMatch[n]'.
            Expression right = Expression.Constant(anId);
            Expression anotherEqual = Expression.Equal(left, right);
            if (chainedByOr == null)
                chainedByOr = anotherEqual;
            else 
                chainedByOr = Expression.OrElse (chainedByOr, anotherEqual);
        }
        MethodCallExpression whereCallExpression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { allInts.ElementType },
            allInts.Expression,
            Expression.Lambda<Func<HasIntKey, bool>>(chainedByOr, new ParameterExpression[] { pe }));

        // Create an executable query from the expression tree.
        IQueryable<HasIntKey> results = allInts.Provider.CreateQuery<HasIntKey>(whereCallExpression);

        // now we have our results, display some details!
        Console.WriteLine ($"Found {results.Count()} objects");

        // Enumerate the results.
        foreach (HasIntKey anObj in results)
            Console.WriteLine($"Found key {anObj.UserKey}");
    }
}
Andy Dent
  • 17,578
  • 6
  • 88
  • 115
  • FYI: Doing this with Realm version="0.77.1" results in `Method 'Realms.RealmResults`1.get_Provider' not found.` Trying to use Realm-net as a local Graph Data cache but it seems that 0.77.1 is really broken in terms of any boolean expressions fail due to `rhs` constant convert failures – SushiHangover Aug 04 '16 at 23:09
  • Is this still the solution to missing Contains functionality? – OneBigQuestion Mar 17 '21 at 16:22
  • I'm pretty sure almost nothing has changed in the Realm LINQ query handling since I left. https://docs.mongodb.com/realm-legacy/docs/dotnet/latest/api/linqsupport.html seems the same. BUT https://docs.mongodb.com/realm-legacy/docs/dotnet/latest/api/CHANGELOG.html#300-2018-04-16 talks about Filter which has never been documented and I think the string query language in https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md alows for CONTAINS – Andy Dent Mar 19 '21 at 06:19