protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.DeclaringType == typeof(Queryable)) { if (node.Method.Name == nameof(Queryable.Where)) { Visit(node.Arguments[0]); var lambda = (LambdaExpression)StripQuotes(node.Arguments[1]); Visit(lambda.Body); return(node); } if (node.Method.Name == nameof(Queryable.OrderBy)) { Visit(node.Arguments[0]); AddSort((LambdaExpression)StripQuotes(node.Arguments[1]), true, true); return(node); } if (node.Method.Name == nameof(Queryable.OrderByDescending)) { Visit(node.Arguments[0]); AddSort((LambdaExpression)StripQuotes(node.Arguments[1]), true, false); return(node); } if (node.Method.Name == nameof(Queryable.ThenBy)) { Visit(node.Arguments[0]); AddSort((LambdaExpression)StripQuotes(node.Arguments[1]), false, true); return(node); } if (node.Method.Name == nameof(Queryable.ThenByDescending)) { Visit(node.Arguments[0]); AddSort((LambdaExpression)StripQuotes(node.Arguments[1]), false, false); return(node); } if (node.Method.Name == nameof(Queryable.Count)) { RecurseToWhereOrRunLambda(node); var foundCount = CoreQueryHandle.Count(); return(Expression.Constant(foundCount)); } if (node.Method.Name == nameof(Queryable.Any)) { RecurseToWhereOrRunLambda(node); var foundAny = CoreQueryHandle.TryFindDirect(_realm.SharedRealmHandle, out _); return(Expression.Constant(foundAny)); } if (node.Method.Name.StartsWith(nameof(Queryable.First))) { RecurseToWhereOrRunLambda(node); ObjectHandle firstObject; if (OptionalSortDescriptorBuilder == null) { CoreQueryHandle.TryFindDirect(_realm.SharedRealmHandle, out firstObject); } else { using (ResultsHandle rh = _realm.MakeResultsForQuery(CoreQueryHandle, OptionalSortDescriptorBuilder)) { rh.TryGetObjectAtIndex(0, out firstObject); } } if (firstObject != null) { return(Expression.Constant(_realm.MakeObject(_metadata, firstObject))); } if (node.Method.Name == nameof(Queryable.First)) { throw new InvalidOperationException("Sequence contains no matching element"); } Debug.Assert(node.Method.Name == nameof(Queryable.FirstOrDefault), $"The method {node.Method.Name} is not supported. We expected {nameof(Queryable.FirstOrDefault)}."); return(Expression.Constant(null)); } /* * // FIXME: See discussion in the test DefaultIfEmptyReturnsDefault * // kept because it shows part of what might be a viable implementation if can work out architectural issues * * if (m.Method.Name == nameof(Queryable.DefaultIfEmpty)) * { * RecurseToWhereOrRunLambda(m); * IntPtr firstObjectPtr = _coreQueryHandle.FindDirect(IntPtr.Zero); * if (firstObjectPtr != IntPtr.Zero) * return m; // as if just a "Where" * var innerType = m.Type.GetGenericArguments()[0]; * var listType = typeof(List<>).MakeGenericType(innerType); * var singleNullItemList = Activator.CreateInstance(listType); * ((IList)singleNullItemList).Add(null); * return Expression.Constant(singleNullItemList); * } */ if (node.Method.Name.StartsWith(nameof(Queryable.Single))) // same as unsorted First with extra checks { RecurseToWhereOrRunLambda(node); if (!CoreQueryHandle.TryFindDirect(_realm.SharedRealmHandle, out var firstObject)) { if (node.Method.Name == nameof(Queryable.Single)) { throw new InvalidOperationException("Sequence contains no matching element"); } Debug.Assert(node.Method.Name == nameof(Queryable.SingleOrDefault), $"The method {node.Method.Name} is not supported. We expected {nameof(Queryable.SingleOrDefault)}."); return(Expression.Constant(null)); } if (CoreQueryHandle.TryFindNext(firstObject, _realm.SharedRealmHandle, out _)) { throw new InvalidOperationException("Sequence contains more than one matching element"); } return(Expression.Constant(_realm.MakeObject(_metadata, firstObject))); } if (node.Method.Name.StartsWith(nameof(Queryable.Last))) { RecurseToWhereOrRunLambda(node); ObjectHandle lastObject = null; using (ResultsHandle rh = _realm.MakeResultsForQuery(CoreQueryHandle, OptionalSortDescriptorBuilder)) { var lastIndex = rh.Count() - 1; if (lastIndex >= 0) { rh.TryGetObjectAtIndex(lastIndex, out lastObject); } } if (lastObject != null) { return(Expression.Constant(_realm.MakeObject(_metadata, lastObject))); } if (node.Method.Name == nameof(Queryable.Last)) { throw new InvalidOperationException("Sequence contains no matching element"); } Debug.Assert(node.Method.Name == nameof(Queryable.LastOrDefault), $"The method {node.Method.Name} is not supported. We expected {nameof(Queryable.LastOrDefault)}."); return(Expression.Constant(null)); } if (node.Method.Name.StartsWith(nameof(Queryable.ElementAt))) { Visit(node.Arguments.First()); if (!TryExtractConstantValue(node.Arguments.Last(), out object argument) || argument.GetType() != typeof(int)) { throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a single integer constant argument or closure variable"); } ObjectHandle objectHandle; var index = (int)argument; if (OptionalSortDescriptorBuilder == null) { CoreQueryHandle.TryFindDirect(_realm.SharedRealmHandle, out objectHandle, (IntPtr)index); } else { using (var rh = _realm.MakeResultsForQuery(CoreQueryHandle, OptionalSortDescriptorBuilder)) { rh.TryGetObjectAtIndex(index, out objectHandle); } } if (objectHandle != null) { return(Expression.Constant(_realm.MakeObject(_metadata, objectHandle))); } if (node.Method.Name == nameof(Queryable.ElementAt)) { throw new ArgumentOutOfRangeException(); } Debug.Assert(node.Method.Name == nameof(Queryable.ElementAtOrDefault), $"The method {node.Method.Name} is not supported. We expected {nameof(Queryable.ElementAtOrDefault)}."); return(Expression.Constant(null)); } } if (node.Method.DeclaringType == typeof(string) || node.Method.DeclaringType == typeof(StringExtensions)) { QueryHandle.Operation <string> queryMethod = null; // For extension methods, member should be m.Arguments[0] as MemberExpression; MemberExpression member = null; // For extension methods, that should be 1 var stringArgumentIndex = 0; if (AreMethodsSame(node.Method, Methods.String.Contains.Value)) { queryMethod = (q, c, v) => q.StringContains(c, v, caseSensitive: true); } else if (AreMethodsSame(node.Method, Methods.String.ContainsStringComparison.Value)) { member = node.Arguments[0] as MemberExpression; stringArgumentIndex = 1; queryMethod = (q, c, v) => q.StringContains(c, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.StartsWith.Value)) { queryMethod = (q, c, v) => q.StringStartsWith(c, v, caseSensitive: true); } else if (AreMethodsSame(node.Method, Methods.String.StartsWithStringComparison.Value)) { queryMethod = (q, c, v) => q.StringStartsWith(c, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.EndsWith.Value)) { queryMethod = (q, c, v) => q.StringEndsWith(c, v, caseSensitive: true); } else if (AreMethodsSame(node.Method, Methods.String.EndsWithStringComparison.Value)) { queryMethod = (q, c, v) => q.StringEndsWith(c, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.IsNullOrEmpty.Value)) { member = node.Arguments.SingleOrDefault() as MemberExpression; if (member == null) { throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a RealmObject member"); } var columnName = GetColumnName(member, node.NodeType); var columnIndex = CoreQueryHandle.GetColumnIndex(columnName); CoreQueryHandle.GroupBegin(); CoreQueryHandle.NullEqual(columnIndex); CoreQueryHandle.Or(); CoreQueryHandle.StringEqual(columnIndex, string.Empty, caseSensitive: true); CoreQueryHandle.GroupEnd(); return(node); } else if (AreMethodsSame(node.Method, Methods.String.EqualsMethod.Value)) { queryMethod = (q, c, v) => q.StringEqual(c, v, caseSensitive: true); } else if (AreMethodsSame(node.Method, Methods.String.EqualsStringComparison.Value)) { queryMethod = (q, c, v) => q.StringEqual(c, v, GetComparisonCaseSensitive(node)); } else if (AreMethodsSame(node.Method, Methods.String.Like.Value)) { member = node.Arguments[0] as MemberExpression; stringArgumentIndex = 1; if (!TryExtractConstantValue(node.Arguments.Last(), out object caseSensitive) || !(caseSensitive is bool)) { throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a string and boolean constant arguments."); } queryMethod = (q, c, v) => q.StringLike(c, v, (bool)caseSensitive); } if (queryMethod != null) { member = member ?? node.Object as MemberExpression; if (member == null) { throw new NotSupportedException($"The method '{node.Method}' has to be invoked on a RealmObject member"); } var columnName = GetColumnName(member, node.NodeType); var columnIndex = CoreQueryHandle.GetColumnIndex(columnName); if (!TryExtractConstantValue(node.Arguments[stringArgumentIndex], out object argument) || (argument != null && argument.GetType() != typeof(string))) { throw new NotSupportedException($"The method '{node.Method}' has to be invoked with a single string constant argument or closure variable"); } queryMethod(CoreQueryHandle, columnIndex, (string)argument); return(node); } } throw new NotSupportedException($"The method '{node.Method.Name}' is not supported"); }