private ContainsExpression VisitContains(MethodCallExpression expression) { // Handle extension methods defined by Linqs if (IsDeclaring(expression, typeof(Queryable), typeof(Enumerable))) { AExpression values = Visit <AExpression>(expression.Arguments[0]); AExpression value = Visit <AExpression>(expression.Arguments[1]); return(new ContainsExpression(values, value)); } // Handle custom extension method if (IsDeclaring(expression, typeof(SqlQueryableHelper))) { // Get value expression first, because the source will change to the subquery making the value out of scope AExpression value = Visit <AExpression>(expression.Arguments[2]); // Evaluate the subquery expressions ASourceExpression source = Visit <ASourceExpression>(expression.Arguments[0]); LambdaExpression fieldLambda = (LambdaExpression)StripQuotes(expression.Arguments[1]); FieldExpression field = Visit <FieldExpression>(fieldLambda.Body); // Create the expression return(new ContainsExpression(new ScalarExpression(source, field), value)); } throw new MethodTranslationException(expression.Method); }
private SelectExpression VisitSkip(MethodCallExpression expression) { if (IsDeclaring(expression, typeof(Queryable), typeof(Enumerable))) { ASourceExpression source = Visit <ASourceExpression>(expression.Arguments[0]); int count = (int)((ConstantExpression)expression.Arguments[1]).Value; return(new SelectExpression(source, source.Fields, -1, count)); } throw new MethodTranslationException(expression.Method); }
/// <summary> /// Initializes a new instance of <see cref="AggregateExpression"/> with the specified source and function. /// </summary> /// <param name="source">The source expression to perform the aggregate function on.</param> /// <param name="field">The field the aggregate function is applied to.</param> /// <param name="value">The type of aggregate operation to perform.</param> public AggregateExpression(ASourceExpression source, FieldExpression field, AggregateFunction function) { if (source == null) throw new ArgumentNullException(nameof(source)); if (field == null) throw new ArgumentNullException(nameof(field)); Source = source; SourceField = field; Function = function; }
public JoinExpression VisitJoin(MethodCallExpression expression) { // Handle the default Queryable extension Join if (IsDeclaring(expression, typeof(Queryable), typeof(Enumerable))) { // Resolve the sources ASourceExpression outer = Visit <ASourceExpression>(expression.Arguments[0]); ASourceExpression inner = Visit <ASourceExpression>(expression.Arguments[1]); // Set the active expressions (so fields calls can find their expression) sources = new[] { outer, inner }; // Create the predicate LambdaExpression outerLambda = (LambdaExpression)StripQuotes(expression.Arguments[2]); LambdaExpression innerLambda = (LambdaExpression)StripQuotes(expression.Arguments[3]); FieldExpression outerField = Visit <FieldExpression>(outerLambda.Body); FieldExpression innerField = Visit <FieldExpression>(innerLambda.Body); APredicateExpression predicate = new CompositeExpression(outerField, innerField, CompositeOperator.Equal); // Decode the result selector IEnumerable <FieldExpression> fields = DecodeJoinSelector(expression.Arguments[4], outer.Fields, inner.Fields); // Create the expression return(new JoinExpression(outer, inner, predicate, fields, JoinType.Inner)); } // Handle the default SqlQueryableHelper extension Join if (IsDeclaring(expression, typeof(SqlQueryableHelper))) { // Resolve the sources ASourceExpression outer = Visit <ASourceExpression>(expression.Arguments[0]); ASourceExpression inner = Visit <ASourceExpression>(expression.Arguments[1]); // Set the active expressions (so fields calls can find their expression) sources = new[] { outer, inner }; // Create the predicate LambdaExpression predicateLambda = (LambdaExpression)StripQuotes(expression.Arguments[2]); APredicateExpression predicate = Visit <APredicateExpression>(predicateLambda.Body); // Decode the result selector IEnumerable <FieldExpression> fields = DecodeJoinSelector(expression.Arguments[3], outer.Fields, inner.Fields); // Resolve the join type ConstantExpression joinType = (ConstantExpression)expression.Arguments[4]; // Create the expression return(new JoinExpression(outer, inner, predicate, fields, (JoinType)joinType.Value)); } throw new MethodTranslationException(expression.Method); }
private WhereExpression VisitWhere(MethodCallExpression expression) { // Handle the default Queryable extension Where if (IsDeclaring(expression, typeof(Queryable), typeof(Enumerable))) { ASourceExpression source = Visit <ASourceExpression>(expression.Arguments[0]); LambdaExpression lambda = (LambdaExpression)StripQuotes(expression.Arguments[1]); APredicateExpression predicate = Visit <APredicateExpression>(lambda.Body); return(new WhereExpression(source, predicate)); } throw new MethodTranslationException(expression.Method); }
/// <summary> /// Creates a unique name for an <see cref="IQueryNode"/>. Identical input nodes that have the same reference will always produce the same name. /// </summary> /// <param name="node">The input node to get an identifiable name for.</param> /// <returns>A unique name for the specified <see cref="IQueryNode"/> in the format "t{index}".</returns> public string GetSource(ASourceExpression node) { if (sources.TryGetValue(node, out string name)) { return(name); } else { name = "t" + source++; sources[node] = name; return(name); } }
private SelectExpression VisitOrderBy(MethodCallExpression expression) { MethodInfo method = expression.Method; if (IsDeclaring(expression, typeof(Queryable), typeof(Enumerable), typeof(SqlQueryableHelper))) { // Resolve the source ASourceExpression source = Visit <ASourceExpression>(expression.Arguments[0]); // Resolve the optional selector FieldExpression field = source.Fields.First(); if (expression.Arguments.Count > 1) { LambdaExpression lambda = (LambdaExpression)StripQuotes(expression.Arguments[1]); field = Visit <FieldExpression>(lambda.Body); } // Decode the direction OrderType direction = method.Name.EndsWith("Descending") ? OrderType.Descending : OrderType.Ascending; // Handle an existing select expression if (source is SelectExpression select) { if (method.Name.StartsWith("ThenBy") && !select.Orderings.Any()) { throw new InvalidOperationException($"{method.Name} can only be applied to an ordered sequence."); } if (method.Name.StartsWith("OrderBy") && select.Orderings.Any()) { throw new InvalidOperationException($"{method.Name} can only be applied to an unordered sequence."); } // Clone and modify the select expression IEnumerable <FieldExpression> fields = select.Fields.Select(x => x.SourceExpression); IEnumerable <Ordering> orderings = select.Orderings.Concat(new[] { new Ordering(field.SourceExpression, direction) }); return(new SelectExpression(select.Source, fields, select.Take, select.Skip, orderings)); } // Create the expression if (method.Name.StartsWith("ThenBy")) { throw new InvalidOperationException($"{method.Name} can only be applied to an ordered sequence."); } return(new SelectExpression(source, orderings: new[] { new Ordering(field, direction) })); } throw new MethodTranslationException(expression.Method); }
/// <summary> /// Initializes a new instance of <see cref="WhereExpression"/> filtering the specified source. /// </summary> /// <param name="source">The source expression to select from.</param> public WhereExpression(ASourceExpression source, APredicateExpression predicate) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); } Source = source; Predicate = predicate; Fields = new FieldExpressions(this, source.Fields); Expressions = new[] { source }; }
/// <summary> /// Initializes a new instance of <see cref="TableExpression"/>, selecting the specified fields from the specified table. /// </summary> /// <param name="table">The name of the table in the database to query.</param> /// <param name="alias">The alias name the <see cref="TableExpression"/> should expose for other queries.</param> public TableExpression(string table, string alias, IEnumerable <string> fields) { if (string.IsNullOrWhiteSpace(table)) { throw new ArgumentException("Cannot be whitespace.", nameof(table)); } if (string.IsNullOrWhiteSpace(alias)) { throw new ArgumentException("Cannot be whitespace.", nameof(alias)); } if (fields == null) { throw new ArgumentNullException(nameof(fields)); } Table = table; Alias = alias; Fields = new FieldExpressions(this, alias, fields); Expressions = new ASourceExpression[0]; }
/// <summary> /// Initializes a new instance of <see cref="JoinExpression"/> with the specified sources. /// </summary> /// <param name="outer">The outer source expression to aggregate.</param> /// <param name="inner">The inner source expression to aggregate.</param> /// <param name="predicate">The optional predicate to condition the join on.</param> /// <param name="fields">The fields to select from the sources. If null, all fields are selected.</param> /// <param name="joinType">The type of join to perform.</param> public JoinExpression(ASourceExpression outer, ASourceExpression inner, APredicateExpression predicate = null, IEnumerable <FieldExpression> fields = null, JoinType joinType = JoinType.Inner) { if (outer == null) { throw new ArgumentNullException(nameof(outer)); } if (inner == null) { throw new ArgumentNullException(nameof(inner)); } Outer = outer; Inner = inner; Predicate = predicate ?? new BooleanExpression(true); JoinType = joinType; fields = fields ?? outer.Fields.Concat(inner.Fields); Fields = new FieldExpressions(this, fields); Expressions = new[] { outer, inner }; }
/// <summary> /// Initializes a new instance of <see cref="FieldExpressions"/> with the specified source and fields. /// </summary> /// <param name="source">The expression which the fields belong to.</param> /// <param name="fields">The fields to add.</param> public FieldExpressions(ASourceExpression source, IEnumerable <FieldExpression> fields) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (fields == null) { throw new ArgumentNullException(nameof(fields)); } if (fields.Any(x => x == null)) { throw new ArgumentException("One or more field was null.", nameof(fields)); } foreach (FieldExpression field in fields) { Add(new FieldExpression(source, field.TableName, field.FieldName, field)); } }
/// <summary> /// Initializes a new instance of <see cref="SelectExpression"/> selecting the specified fields from the source. /// </summary> /// <param name="source">The source expression to select from.</param> /// <param name="fields">The fields to select from the source.</param> /// <param name="take">The number of fields to take from the source. Values less than zero indicate all rows.</param> /// <param name="skip">The number of fields to ignore on the source before reading rows.</param> /// <param name="orderings">The optional sorting of the selected rows.</param> /// <exception cref="ArgumentNullException">fields is null.</exception> /// <exception cref="ArgumentException">fields contains no elements or orderings contains duplicate field orderings.</exception> /// <exception cref="KeyNotFoundException">An ordering key could not be found in fields.</exception> public SelectExpression(ASourceExpression source, IEnumerable <FieldExpression> fields, int take = -1, int skip = 0, IEnumerable <Ordering> orderings = null) { if (fields == null) { throw new ArgumentNullException(nameof(fields)); } if (!fields.Any()) { throw new ArgumentException("There must be at least one field specified in a select query.", nameof(fields)); } Source = source; Fields = new FieldExpressions(this, fields); Expressions = new[] { source }; Take = take; Skip = skip; Orderings = orderings? .Select(x => { if (x == null) { throw new ArgumentException("None of the ordering can be null.", nameof(orderings)); } int count = orderings.Count(y => y.Field == x.Field); if (count != 1) { throw new ArgumentException($"One ordering per field is allowed but '[{x.Field.TableName}].[{x.Field.FieldName}]' has {count}.", nameof(orderings)); } if (!Fields.Any(y => y.SourceExpression == x.Field)) { throw new KeyNotFoundException($"The ordering field '[{x.Field.TableName}].[{x.Field.FieldName}]' could not be found in the fields parameter."); } return(x); }) .ToArray(); }
private AggregateExpression VisitCount(MethodCallExpression expression) { if (IsDeclaring(expression, typeof(Queryable), typeof(Enumerable), typeof(SqlQueryableHelper))) { // Map the source ASourceExpression source = Visit <ASourceExpression>(expression.Arguments[0]); // Resolve the optional predicate if (expression.Arguments.Count > 1) { LambdaExpression lambda = (LambdaExpression)StripQuotes(expression.Arguments[1]); APredicateExpression predicate = Visit <APredicateExpression>(lambda.Body); source = new WhereExpression(source, predicate); } // Resolve the field to be counted (must be done after the source has been manipulated) FieldExpression field = source.Fields.First(); // Create the expression return(new AggregateExpression(source, field, AggregateFunction.Count)); } throw new MethodTranslationException(expression.Method); }
/// <summary> /// Initializes a new instance of <see cref="FieldExpressions"/> with the specified table and fields names. /// </summary> /// <param name="source">The expression which the fields belong to.</param> /// <param name="table">The name of the table all the fields are in.</param> /// <param name="fields">The fields to add.</param> public FieldExpressions(ASourceExpression source, string table, IEnumerable <string> fields) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (string.IsNullOrWhiteSpace(table)) { throw new ArgumentException("Cannot be null or whitespace.", nameof(table)); } if (fields == null) { throw new ArgumentNullException(nameof(fields)); } if (fields.Any(x => string.IsNullOrWhiteSpace(x))) { throw new ArgumentException("One or more field was whitespace.", nameof(fields)); } foreach (string field in fields) { Add(new FieldExpression(source, table, field)); } }
/// <summary> /// Initializes a new instance of <see cref="SelectExpression"/> selecting all the fields from the specified source. /// </summary> /// <param name="source">The source expression to select from.</param> /// <param name="take">The number of fields to take from the source. Values less than zero indicate all rows.</param> /// <param name="skip">The number of fields to ignore on the source before reading rows.</param> /// <param name="orderings">The optional sorting of the selected rows.</param> /// <exception cref="ArgumentNullException">the source <see cref="ASourceExpression.Fields"/> property is null.</exception> /// <exception cref="ArgumentException">the source <see cref="ASourceExpression.Fields" /> property contains no elements.</exception> /// <exception cref="KeyNotFoundException">An ordering key could not be found in the sources <see cref="ASourceExpression.Fields"/> property.</exception> public SelectExpression(ASourceExpression source, int take = -1, int skip = 0, IEnumerable <Ordering> orderings = null) : this(source, source?.Fields, take, skip, orderings) { }
/// <summary> /// Initializes a new instance of <see cref="ScalarExpression"/> selecting the single field exposed on the source. /// </summary> /// <param name="source">The source expression to select from.</param> public ScalarExpression(ASourceExpression source) : this(source, source?.Fields.Single()) { }
/// <summary> /// Initializes a new instance of <see cref="ScalarExpression"/> selecting the specified field from the source. /// </summary> /// <param name="source">The source expression to select from.</param> /// <param name="field">The field to select from the source.</param> public ScalarExpression(ASourceExpression source, FieldExpression field) : base(source, field != null ? new[] { field } : throw new ArgumentNullException(nameof(field))) { }
public static IEnumerable <Record> ExecuteQuery(this DbConnection connection, ASourceExpression expression, SqlQueryVisitor visitor) { // Create the outer select expression SelectExpression select = expression as SelectExpression; if (select == null) { select = new SelectExpression(expression, expression.Fields); } // Ensure the connection is open if (connection.State != ConnectionState.Open) { throw new InvalidOperationException("The connection must be open to execute a query."); } // Create the command LinkedList <Record> items = new LinkedList <Record>(); using (DbCommand command = connection.CreateCommand()) { // Prepare the query Query query = visitor.GenerateQuery(select); command.CommandText = query.Sql; foreach (KeyValuePair <string, object> pair in query.Parameters) { command.AddParameter(pair.Key, pair.Value); } // Execute the query using (DbDataReader reader = command.ExecuteReader()) { ILookup <string, CommandField> fields = reader.GetFieldMap(select.Fields); while (reader.Read()) { // Read the row of the result set Dictionary <string, RecordItem> row = fields .Select(x => ReadItem(reader, x, x.Key)) .ToDictionary(x => x.Key); // At this point the row has been created items.AddLast(new Record(row)); } } } return(items); }