static void Main(string[] args) { var xxx = ResultTranslator <AllResultsWrapper> .GetTranslator() .Translate(r => r.FirstResult) .Translate(r => r.SecondResult) .Run(); Console.ReadLine(); }
void IInitializable.Initialize() { ResultEntitySubject .OnNext( ResultEntityFactory .Create( ScoreEntity.Current.Value, PlayerPrefs.GetString(Constant.PlayerPrefsKey.LastPlayerName, string.Empty), DateTime.Now ) ); ResultEntitySubject.OnCompleted(); GameResultHandler.RenderResult(ResultTranslator.Translate(ResultEntitySubject.Value)); GameResultHandler.UpdatePlayerNameAsObservable().Subscribe(ResultEntitySubject.Value.UpdatePlayerName); }
public ODataExpression Translate(Expression linq, out IQueryable rootQuery, out ResultTranslator resultTranslator) { this._isInsideQuery = false; this._rootQuery = null; this._resultTranslator = null; this._memberAndParameterTranslator = new MemberAndParameterTranslator(this); // normalize away ODataRow constructs var normalized = ODataEntity.Normalize(linq); var translated = this.TranslateInternal(normalized); if (translated.Kind == ODataExpressionKind.Query) { var referencedPaths = this._memberAndParameterTranslator.GetReferencedMemberPathsInFinalProjection(); if (referencedPaths != null) { var selectColumns = referencedPaths.Select(p => p.Aggregate(default(ODataMemberAccessExpression), (e, m) => ODataExpression.MemberAccess(e, (PropertyInfo)m))) .Select(ma => ODataExpression.SelectColumn(ma, allColumns: ma.Type == ODataExpressionType.Complex)); translated = ((ODataQueryExpression)translated).Update(select: selectColumns); } } rootQuery = this._rootQuery; var projection = this._memberAndParameterTranslator.GetFinalProjection(); var finalTranslator = this._resultTranslator ?? ((values, count) => values); if (projection != null) { var selectMethod = Helpers.GetMethod((IEnumerable <object> e) => e.Select(o => o)) .GetGenericMethodDefinition() .MakeGenericMethod(projection.Type.GetGenericArguments(typeof(Func <,>))); // restores any ODataRow constructs that were normalized away, since we need to be able to compile and run the projection // (i. e. fake ODataRow property accesses don't run when compiled) var denormalizedProjection = (LambdaExpression)ODataEntity.Denormalize(projection); Func <object, object> queryTranslator = enumerable => selectMethod.Invoke(null, new[] { enumerable, denormalizedProjection.Compile() }); resultTranslator = (values, count) => finalTranslator((IEnumerable)queryTranslator(values), count); } else { resultTranslator = finalTranslator; } return(translated); }
public ODataExpression Translate(Expression linq, out IQueryable rootQuery, out ResultTranslator resultTranslator) { this._isInsideQuery = false; this._rootQuery = null; this._resultTranslator = null; this._memberAndParameterTranslator = new MemberAndParameterTranslator(this); // normalize away ODataRow constructs var normalized = ODataEntity.Normalize(linq); var translated = this.TranslateInternal(normalized); if (translated.Kind == ODataExpressionKind.Query) { var referencedPaths = this._memberAndParameterTranslator.GetReferencedMemberPathsInFinalProjection(); if (referencedPaths != null) { var selectColumns = referencedPaths.Select(p => p.Aggregate(default(ODataMemberAccessExpression), (e, m) => ODataExpression.MemberAccess(e, (PropertyInfo)m))) .Select(ma => ODataExpression.SelectColumn(ma, allColumns: ma.Type == ODataExpressionType.Complex)); translated = ((ODataQueryExpression)translated).Update(select: selectColumns); } } rootQuery = this._rootQuery; var projection = this._memberAndParameterTranslator.GetFinalProjection(); var finalTranslator = this._resultTranslator ?? ((values, count) => values); if (projection != null) { var selectMethod = Helpers.GetMethod((IEnumerable<object> e) => e.Select(o => o)) .GetGenericMethodDefinition() .MakeGenericMethod(projection.Type.GetGenericArguments(typeof(Func<,>))); // restores any ODataRow constructs that were normalized away, since we need to be able to compile and run the projection // (i. e. fake ODataRow property accesses don't run when compiled) var denormalizedProjection = (LambdaExpression)ODataEntity.Denormalize(projection); Func<object, object> queryTranslator = enumerable => selectMethod.Invoke(null, new[] { enumerable, denormalizedProjection.Compile() }); resultTranslator = (values, count) => finalTranslator((IEnumerable)queryTranslator(values), count); } else { resultTranslator = finalTranslator; } return translated; }
private ODataExpression TranslateCall(MethodCallExpression call) { // query operators if (call.Method.DeclaringType == typeof(Queryable)) { if (this._isInsideQuery) { throw new ODataCompileException("OData does not support nested query structures!"); } // normalize overloads of query operators bool changedAllToAny; var normalized = QueryOperatorCanonicalizer.Canonicalize(call, changedAllToAny: out changedAllToAny); if (normalized != call) { // TODO FUTURE better exception message here since expression being translated won't match? var result = this.TranslateInternal(normalized); if (changedAllToAny) { var innerResultTranslator = this._resultTranslator; Throw<InvalidOperationException>.If(innerResultTranslator == null, "Sanity check: expected non-null result translator"); this._resultTranslator = (values, count) => !((bool)innerResultTranslator(values, count)); } return result; } // handle "normal" query methods // translate the source query. If the source is a constant, that's the "root" of the query tree // e. g. that might be the OData equivalent of a DbSet or ObjectQuery constant var source = (ODataQueryExpression)this.TranslateInternal(call.Arguments[0]); switch (call.Method.Name) { case "Where": // not the index version if (call.Arguments[1].Type.IsGenericOfType(typeof(Func<,,>))) { goto default; } var predicate = this.TranslateInsideQuery(call.Arguments[1]); if (source.OrderBy.Count > 0 || source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a filter after applying an OrderBy, Take, or Skip operation"); } return source.Update(filter: source.Filter != null ? ODataExpression.BinaryOp(source.Filter, ODataBinaryOp.And, predicate) : predicate); case "OrderBy": case "OrderByDescending": case "ThenBy": case "ThenByDescending": if (call.Arguments.Count != 2) { goto default; } var sortKeyExpression = this.TranslateInsideQuery(call.Arguments[1]); var sortKey = ODataExpression.SortKey(sortKeyExpression, descending: call.Method.Name.EndsWith("Descending")); if (source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a sort after applying Take or Skip operations"); } return source.Update( orderBy: call.Method.Name.StartsWith("Then") ? source.OrderBy.Concat(sortKey.Enumerate()) : sortKey.Enumerate() ); case "Skip": object skip; Throw<InvalidOperationException>.If(!TryGetValueFast(call.Arguments[1], out skip), "Could not get value"); if (source.Top.HasValue) { throw new ODataCompileException("Cannot apply a skip after applying a Take operation"); } return source.Update(skip: source.Skip + (int)skip); // not right case "Take": object take; Throw<InvalidOperationException>.If(!TryGetValueFast(call.Arguments[1], out take), "Could not get value"); return Take(source, (int)take); case "Select": /* * MA: select is tricky in OData, since it doesn't just conform to the OData $select system query option. * Instead, you could have an intermediate select in your query, and it would be nice to be able to support that. * In that case, OData still forces us to select original columns, but we can store away the projection expressions * to re-apply later. We also need to make sure to store how to map each projected property, so that we can inline these * projections. * * For example, imagine you have the query q.Select(x => new { a = x.B + 2 }).Where(t => t.a > 5). * In OData, this would translate to "$filter=B + 2 > 5&$select=B". We can then do the final projection to the anonymous * type in memory on the client side */ // we don't support select with index if (!call.Arguments[1].Type.GetGenericArguments(typeof(Expression<>)).Single().IsGenericOfType(typeof(Func<,>))) { goto default; } // unquote and extract the lambda var projection = ((LambdaExpression)((UnaryExpression)call.Arguments[1]).Operand); // register the projection this._isInsideQuery = true; this._memberAndParameterTranslator.RegisterProjection(projection); this._isInsideQuery = false; // return the source, since the projection doesn't actually affect the returned expression // until the very end when we can use it to determine which columns to $select return source; case "Any": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Any()); return Take(source, 1); case "Count": this._resultTranslator = (values, count) => Math.Min(count.Value - source.Skip, source.Top ?? int.MaxValue); // top 0 is so that we don't have any wasted payload of data that we won't look at return source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0); case "LongCount": this._resultTranslator = (values, count) => (long)Math.Min(count.Value - source.Skip, source.Top ?? int.MaxValue); // top 0 is so that we don't have any wasted payload of data that we won't look at return source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0); case "First": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.First()); return Take(source, 1); case "FirstOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.FirstOrDefault()); return Take(source, 1); case "Single": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Single());; return Take(source, 2); case "SingleOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.SingleOrDefault());; return Take(source, 2); default: throw new ODataCompileException("Query operator " + call.Method + " is not supported in OData"); } } // other OData methods // Enumerable/Collection contains (e. g. "IN"). This gets handled before the odata functions because // the thisExpression can't be translated normally. Since Contains() is declared on a bunch of different collection // types, we basically check that (1) there are either 2 arguments (static method) or an instance + 1 argument // (2) that the container argument/instance is an in-memory "constant", (3) that the collection object has an IEnumerable<T> // element type, and (4) that the test argument is of that element type // Finally, we translate the IN clause to a set of ORs object enumerable; Type elementType; if (call.Method.Name == "Contains" && call.Arguments.Count + Convert.ToInt32(!call.Method.IsStatic) == 2 && TryGetValueFast(call.Object ?? call.Arguments[0], out enumerable) && enumerable != null && (elementType = enumerable.GetType().GetGenericArguments(typeof(IEnumerable<>)).SingleOrDefault()) != null && call.Arguments.Last().Type == elementType) { var testExpression = this.TranslateInternal(call.Arguments.Last()); var equalsElementExpressions = ((IEnumerable)enumerable).Cast<object>() .Select(o => ODataExpression.Constant(o, elementType)) .Select(c => ODataExpression.BinaryOp(testExpression, ODataBinaryOp.Equal, c)) .ToArray(); var equivalentOrExpression = equalsElementExpressions.Length == 0 ? ODataExpression.Constant(true).As<ODataExpression>() : equalsElementExpressions.Aggregate((e1, e2) => ODataExpression.BinaryOp(e1, ODataBinaryOp.Or, e2)); return equivalentOrExpression; } // ODataFunctions var thisExpression = this.TranslateInternal(call.Object); var translatedArgs = call.Arguments.Select(this.TranslateInternal); // string functions if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Substring") { return ODataExpression.Call(ODataFunction.Substring, thisExpression.Enumerate().Concat(translatedArgs)); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Replace" && call.Arguments[0].Type == typeof(string)) { // for now, we don't support the char replace overload return ODataExpression.Call(ODataFunction.Replace, thisExpression.Enumerate().Concat(translatedArgs)); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Concat" && call.Arguments.All(a => a.Type == typeof(string))) { // we support only string concats, but with any fixed number of parameters return translatedArgs.Aggregate((s1, s2) => ODataExpression.Call(ODataFunction.Concat, new[] { s1, s2 })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "StartsWith" && call.Arguments.Count == 1) { return ODataExpression.Call(ODataFunction.StartsWith, new[] { thisExpression, translatedArgs.Single() }); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "EndsWith" && call.Arguments.Count == 1) { return ODataExpression.Call(ODataFunction.EndsWith, new[] { thisExpression, translatedArgs.Single() }); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "IndexOf" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { return ODataExpression.Call(ODataFunction.IndexOf, new[] { thisExpression, translatedArgs.Single() }); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Contains" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { // note: we reverse the args here because A.SubstringOf(B) is equivalent to B.Contains(A) return ODataExpression.Call(ODataFunction.SubstringOf, new[] { translatedArgs.Single(), thisExpression }); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToLower" && call.Arguments.Count == 0) { return ODataExpression.Call(ODataFunction.ToLower, thisExpression.Enumerate()); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToUpper" && call.Arguments.Count == 0) { return ODataExpression.Call(ODataFunction.ToUpper, thisExpression.Enumerate()); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Trim" && call.Arguments.Count == 0) { return ODataExpression.Call(ODataFunction.Trim, thisExpression.Enumerate()); } // math functions if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Ceiling") { return ODataExpression.Call(ODataFunction.Ceiling, translatedArgs); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Floor") { return ODataExpression.Call(ODataFunction.Floor, translatedArgs); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Round" && call.Arguments.Count == 1) { return ODataExpression.Call(ODataFunction.Round, translatedArgs); } throw new ODataCompileException("Method " + call.Method + " could not be translated to OData"); }
private ODataExpression TranslateCall(MethodCallExpression call) { // query operators if (call.Method.DeclaringType == typeof(Queryable)) { if (this._isInsideQuery) { throw new ODataCompileException("OData does not support nested query structures!"); } // normalize overloads of query operators bool changedAllToAny; var normalized = QueryOperatorCanonicalizer.Canonicalize(call, changedAllToAny: out changedAllToAny); if (normalized != call) { // TODO FUTURE better exception message here since expression being translated won't match? var result = this.TranslateInternal(normalized); if (changedAllToAny) { var innerResultTranslator = this._resultTranslator; Throw <InvalidOperationException> .If(innerResultTranslator == null, "Sanity check: expected non-null result translator"); this._resultTranslator = (values, count) => !((bool)innerResultTranslator(values, count)); } return(result); } // handle "normal" query methods // translate the source query. If the source is a constant, that's the "root" of the query tree // e. g. that might be the OData equivalent of a DbSet or ObjectQuery constant var source = (ODataQueryExpression)this.TranslateInternal(call.Arguments[0]); switch (call.Method.Name) { case "Where": // not the index version if (call.Arguments[1].Type.IsGenericOfType(typeof(Func <, ,>))) { goto default; } var predicate = this.TranslateInsideQuery(call.Arguments[1]); if (source.OrderBy.Count > 0 || source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a filter after applying an OrderBy, Take, or Skip operation"); } return(source.Update(filter: source.Filter != null ? ODataExpression.BinaryOp(source.Filter, ODataBinaryOp.And, predicate) : predicate)); case "OrderBy": case "OrderByDescending": case "ThenBy": case "ThenByDescending": if (call.Arguments.Count != 2) { goto default; } var sortKeyExpression = this.TranslateInsideQuery(call.Arguments[1]); var sortKey = ODataExpression.SortKey(sortKeyExpression, descending: call.Method.Name.EndsWith("Descending")); if (source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a sort after applying Take or Skip operations"); } return(source.Update( orderBy: call.Method.Name.StartsWith("Then") ? source.OrderBy.Concat(sortKey.Enumerate()) : sortKey.Enumerate() )); case "Skip": object skip; Throw <InvalidOperationException> .If(!TryGetValueFast(call.Arguments[1], out skip), "Could not get value"); if (source.Top.HasValue) { throw new ODataCompileException("Cannot apply a skip after applying a Take operation"); } return(source.Update(skip: source.Skip + (int)skip)); // not right case "Take": object take; Throw <InvalidOperationException> .If(!TryGetValueFast(call.Arguments[1], out take), "Could not get value"); return(Take(source, (int)take)); case "Select": /* * MA: select is tricky in OData, since it doesn't just conform to the OData $select system query option. * Instead, you could have an intermediate select in your query, and it would be nice to be able to support that. * In that case, OData still forces us to select original columns, but we can store away the projection expressions * to re-apply later. We also need to make sure to store how to map each projected property, so that we can inline these * projections. * * For example, imagine you have the query q.Select(x => new { a = x.B + 2 }).Where(t => t.a > 5). * In OData, this would translate to "$filter=B + 2 > 5&$select=B". We can then do the final projection to the anonymous * type in memory on the client side */ // we don't support select with index if (!call.Arguments[1].Type.GetGenericArguments(typeof(Expression <>)).Single().IsGenericOfType(typeof(Func <,>))) { goto default; } // unquote and extract the lambda var projection = ((LambdaExpression)((UnaryExpression)call.Arguments[1]).Operand); // register the projection this._isInsideQuery = true; this._memberAndParameterTranslator.RegisterProjection(projection); this._isInsideQuery = false; // return the source, since the projection doesn't actually affect the returned expression // until the very end when we can use it to determine which columns to $select return(source); case "Any": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Any()); return(Take(source, 1)); case "Count": this._resultTranslator = (values, count) => ComputeCount(count: count.Value, skip: source.Skip, top: source.Top); // top 0 is so that we don't have any wasted payload of data that we won't look at return(source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0)); case "LongCount": this._resultTranslator = (values, count) => (long)ComputeCount(count: count.Value, skip: source.Skip, top: source.Top); // top 0 is so that we don't have any wasted payload of data that we won't look at return(source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0)); case "First": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.First()); return(Take(source, 1)); case "FirstOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.FirstOrDefault()); return(Take(source, 1)); case "Single": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Single());; return(Take(source, 2)); case "SingleOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.SingleOrDefault());; return(Take(source, 2)); default: throw new ODataCompileException("Query operator " + call.Method + " is not supported in OData"); } } // other OData methods // Enumerable/Collection contains (e. g. "IN"). This gets handled before the odata functions because // the thisExpression can't be translated normally. Since Contains() is declared on a bunch of different collection // types, we basically check that (1) there are either 2 arguments (static method) or an instance + 1 argument // (2) that the container argument/instance is an in-memory "constant", (3) that the collection object has an IEnumerable<T> // element type, and (4) that the test argument is of that element type // Finally, we translate the IN clause to a set of ORs object enumerable; Type elementType; if (call.Method.Name == "Contains" && call.Arguments.Count + Convert.ToInt32(!call.Method.IsStatic) == 2 && TryGetValueFast(call.Object ?? call.Arguments[0], out enumerable) && enumerable != null && (elementType = enumerable.GetType().GetGenericArguments(typeof(IEnumerable <>)).SingleOrDefault()) != null && call.Arguments.Last().Type == elementType) { var testExpression = this.TranslateInternal(call.Arguments.Last()); var equalsElementExpressions = ((IEnumerable)enumerable).Cast <object>() .Select(o => ODataExpression.Constant(o, elementType)) .Select(c => ODataExpression.BinaryOp(testExpression, ODataBinaryOp.Equal, c)) .ToArray(); var equivalentOrExpression = equalsElementExpressions.Length == 0 ? ODataExpression.Constant(true).As <ODataExpression>() : equalsElementExpressions.Aggregate((e1, e2) => ODataExpression.BinaryOp(e1, ODataBinaryOp.Or, e2)); return(equivalentOrExpression); } // ODataFunctions var thisExpression = this.TranslateInternal(call.Object); var translatedArgs = call.Arguments.Select(this.TranslateInternal); // string functions if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Substring") { return(ODataExpression.Call(ODataFunction.Substring, thisExpression.Enumerate().Concat(translatedArgs))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Replace" && call.Arguments[0].Type == typeof(string)) { // for now, we don't support the char replace overload return(ODataExpression.Call(ODataFunction.Replace, thisExpression.Enumerate().Concat(translatedArgs))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Concat" && call.Arguments.All(a => a.Type == typeof(string))) { // we support only string concats, but with any fixed number of parameters return(translatedArgs.Aggregate((s1, s2) => ODataExpression.Call(ODataFunction.Concat, new[] { s1, s2 }))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "StartsWith" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.StartsWith, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "EndsWith" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.EndsWith, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "IndexOf" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { return(ODataExpression.Call(ODataFunction.IndexOf, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Contains" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { // note: we reverse the args here because A.SubstringOf(B) is equivalent to B.Contains(A) return(ODataExpression.Call(ODataFunction.SubstringOf, new[] { translatedArgs.Single(), thisExpression })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToLower" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.ToLower, thisExpression.Enumerate())); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToUpper" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.ToUpper, thisExpression.Enumerate())); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Trim" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.Trim, thisExpression.Enumerate())); } // math functions if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Ceiling") { return(ODataExpression.Call(ODataFunction.Ceiling, translatedArgs)); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Floor") { return(ODataExpression.Call(ODataFunction.Floor, translatedArgs)); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Round" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.Round, translatedArgs)); } throw new ODataCompileException("Method " + call.Method + " could not be translated to OData"); }