/// <summary> /// Returns a registered column, or null if not found /// This method requires the table to be already registered /// </summary> /// <param name="table"></param> /// <param name="name"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual ColumnExpression GetRegisteredColumn(TableExpression table, string name, BuilderContext builderContext) { return (from queryColumn in builderContext.EnumerateScopeColumns() where queryColumn.Table.IsEqualTo(table) && queryColumn.Name == name select queryColumn).SingleOrDefault(); }
protected virtual Expression Analyze(Expression expression, BuilderContext builderContext) { foreach (var analyze in GetAnalyzers()) { var e = analyze(expression); if (e != null) return e; } return expression; }
protected virtual Expression AnalyzeExpression(Expression expression, BuilderContext builderContext) { // string Add --> Concat var binaryExpression = expression as BinaryExpression; if (expression.NodeType == ExpressionType.Add && binaryExpression != null && typeof(string).IsAssignableFrom(binaryExpression.Left.Type)) { return new SpecialExpression(SpecialExpressionType.Concat, binaryExpression.Left, binaryExpression.Right); } return expression; }
protected Expression Analyze(Expression expression, BuilderContext builderContext) { // small optimization if (expression is ConstantExpression) return expression; expression = AnalyzeNull(expression, builderContext); expression = AnalyzeNot(expression, builderContext); // constant optimization at last, because the previous optimizations may generate constant expressions expression = AnalyzeConstant(expression, builderContext); return expression; }
/// <summary> /// Returns an existing table or registers the current one /// </summary> /// <param name="tableExpression"></param> /// <param name="builderContext"></param> /// <returns>A registered table or the current newly registered one</returns> public virtual TableExpression RegisterTable(TableExpression tableExpression, BuilderContext builderContext) { // 1. Find the table in current scope var foundTableExpression = (from t in builderContext.EnumerateScopeTables() where t.IsEqualTo(tableExpression) select t).SingleOrDefault(); if (foundTableExpression != null) return foundTableExpression; // 2. Find it in all scopes, and promote it to current scope. foundTableExpression = PromoteTable(tableExpression, builderContext); if (foundTableExpression != null) return foundTableExpression; // 3. Add it builderContext.CurrentSelect.Tables.Add(tableExpression); return tableExpression; }
protected virtual Expression AnalyzeConstant(Expression expression, BuilderContext builderContext) { // we try to find a non-constant operand, and if we do, we won't change this expression foreach (var operand in expression.GetOperands()) { if (!(operand is ConstantExpression)) return expression; } // now, we just simply return a constant with new value try { var optimizedExpression = Expression.Constant(expression.Evaluate()); // sometimes, optimizing an expression changes its type, and we just can't allow this. if (optimizedExpression.Type == expression.Type) return optimizedExpression; } // if we fail to evaluate the expression, then just return it catch (ArgumentException) { } return expression; }
public virtual void UnregisterParameter(InputParameterExpression expression, BuilderContext builderContext) { builderContext.ExpressionQuery.Parameters.Remove(expression); }
/// <summary> /// Registers an external parameter /// Since these can be complex expressions, we don't try to identify them /// and push them every time /// The only loss may be a small memory loss (if anyone can prove me that the same Expression can be used twice) /// </summary> /// <param name="expression"></param> /// <param name="alias"></param> /// <param name="builderContext"></param> /// <returns></returns> public virtual InputParameterExpression RegisterParameter(Expression expression, string alias, BuilderContext builderContext) { var queryParameterExpression = new InputParameterExpression(expression, alias); builderContext.ExpressionQuery.Parameters.Add(queryParameterExpression); return queryParameterExpression; }
public virtual Expression Parse(Expression expression, BuilderContext builderContext) { return expression.Recurse(e => Analyze(e, builderContext)); }
/// <summary> /// Creates a new BuilderContext where parameters have a local scope /// </summary> /// <returns></returns> public BuilderContext NewQuote() { var builderContext = new BuilderContext(); // scope independent parts builderContext.QueryContext = QueryContext; builderContext.ExpressionQuery = ExpressionQuery; builderContext.MetaTables = MetaTables; builderContext.currentScopeIndex = currentScopeIndex; builderContext.SelectExpressions = SelectExpressions; builderContext.ExpectMetaTableDefinition = ExpectMetaTableDefinition; // scope dependent parts builderContext.Parameters = new Dictionary<string, Expression>(Parameters); return builderContext; }
public virtual LambdaExpression BuildTableReader1(Type tableType, IList<string> parameters, BuilderContext builderContext) { var dataRecordParameter = Expression.Parameter(typeof(IDataRecord), "dataRecord"); var mappingContextParameter = Expression.Parameter(typeof(MappingContext), "mappingContext"); var table = builderContext.QueryContext.DataContext.Mapping.GetTable(tableType); var bindings = new List<MemberBinding>(); foreach (var column in DataMapper.GetColumns(table)) { var columnName = DataMapper.GetColumnName(tableType, column, builderContext.QueryContext.DataContext); var invoke = GetOutputValueReader(column.GetMemberType(), GetTableIndex(parameters, columnName), dataRecordParameter, mappingContextParameter); var parameterColumn = GetOutputValueReader(invoke, dataRecordParameter, mappingContextParameter, builderContext); var binding = Expression.Bind(column, parameterColumn); bindings.Add(binding); } var newExpression = Expression.New(tableType); var initExpression = Expression.MemberInit(newExpression, bindings); return Expression.Lambda(initExpression, dataRecordParameter, mappingContextParameter); }
/// <summary> /// Registers the table as returned by the SQL request. /// Actually, the table is split into its columns. /// </summary> /// <param name="tableExpression"></param> /// <param name="dataRecordParameter"></param> /// <param name="mappingContextParameter"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual Expression GetOutputTableReader(TableExpression tableExpression, ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter, BuilderContext builderContext) { var bindings = new List<MemberBinding>(); foreach (var columnExpression in RegisterAllColumns(tableExpression, builderContext)) { var parameterColumn = GetOutputValueReader(columnExpression, dataRecordParameter, mappingContextParameter, builderContext); var binding = Expression.Bind(columnExpression.MemberInfo, parameterColumn); bindings.Add(binding); } var newExpression = Expression.New(tableExpression.Type); var initExpression = Expression.MemberInit(newExpression, bindings); return initExpression; }
/// <summary> /// Registers all columns of a table. /// </summary> /// <param name="tableExpression"></param> /// <param name="builderContext"></param> /// <returns></returns> public virtual IEnumerable<ColumnExpression> RegisterAllColumns(TableExpression tableExpression, BuilderContext builderContext) { foreach (var metaMember in builderContext.QueryContext.DataContext.Mapping.GetTable(tableExpression.Type).RowType.PersistentDataMembers) { yield return RegisterColumn(tableExpression, metaMember.Member, builderContext); } }
/// <summary> /// Promotes a table to a common parent between its current scope and our current scope /// </summary> /// <param name="tableExpression"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual TableExpression PromoteTable(TableExpression tableExpression, BuilderContext builderContext) { // 1. Find the table ScopeExpression SelectExpression oldSelect = FindTableScope(ref tableExpression, builderContext); if (oldSelect == null) return null; // 2. Find a common ScopeExpression var commonScope = FindCommonScope(oldSelect, builderContext.CurrentSelect); commonScope.Tables.Add(tableExpression); return tableExpression; }
/// <summary> /// Promotes a table to a common parent between its current scope and our current scope /// </summary> /// <param name="tableExpression"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual TableExpression PromoteTable(TableExpression tableExpression, BuilderContext builderContext) { int currentIndex = 0; SelectExpression oldSelect = null; SelectExpression commonScope = null; TableExpression foundTable = null; do { // take a select oldSelect = builderContext.SelectExpressions[currentIndex]; // look for a common scope if (oldSelect != builderContext.CurrentSelect) { commonScope = FindCommonScope(oldSelect, builderContext.CurrentSelect); if (commonScope != null) // if a common scope exists, look for an equivalent table in that select for (int tableIndex = 0; tableIndex < oldSelect.Tables.Count && foundTable == null; tableIndex++) { if (oldSelect.Tables[tableIndex].IsEqualTo(tableExpression)) { // found a matching table! foundTable = oldSelect.Tables[tableIndex]; } } } ++currentIndex; } while (currentIndex < builderContext.SelectExpressions.Count && foundTable == null); if (foundTable != null) { oldSelect.Tables.Remove(foundTable); commonScope.Tables.Add(foundTable); } return foundTable; }
/// <summary> /// Registers the ColumnExpression as returned by the SQL request. /// </summary> /// <param name="expression"></param> /// <param name="dataRecordParameter"></param> /// <param name="mappingContextParameter"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual Expression GetOutputValueReader(ColumnExpression expression, ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter, BuilderContext builderContext) { int valueIndex = RegisterOutputParameter(expression, builderContext); Type storageType = expression.StorageInfo != null ? expression.StorageInfo.GetMemberType() : null; return GetOutputValueReader(storageType ?? expression.Type, valueIndex, dataRecordParameter, mappingContextParameter); }
/// <summary> /// Creates an entity set creator, to be used at run-time /// </summary> /// <param name="expression"></param> /// <param name="dataRecordParameter"></param> /// <param name="mappingContextParameter"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual Expression GetEntitySetBuilder(EntitySetExpression expression, ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter, BuilderContext builderContext) { var entityType = expression.EntitySetType.GetGenericArguments()[0]; List<ElementInit> members = new List<ElementInit>(); var add = expression.EntitySetType.GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(KeyValuePair<object, MemberInfo>) }, null); foreach (var info in expression.Columns) { var column = info.Key; var tk = info.Value; MemberInfo memberInfo = column.StorageInfo ?? column.MemberInfo; PropertyInfo propertyInfo = memberInfo as PropertyInfo; if (propertyInfo == null || propertyInfo.CanWrite) { var parameterColumn = GetOutputValueReader(column, dataRecordParameter, mappingContextParameter, builderContext); members.Add(Expression.ElementInit(add, new Expression[]{ Expression.New(typeof(KeyValuePair<object, MemberInfo>).GetConstructor(new Type[]{typeof(object), typeof(MemberInfo)}), Expression.Convert(parameterColumn, typeof(object)), Expression.Constant(tk.Member, typeof(MemberInfo)))})); } } return Expression.ListInit( Expression.New( expression.EntitySetType.GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(DataContext) }, null), Expression.Constant(builderContext.QueryContext.DataContext)), members); }
/// <summary> /// Registers the table as returned by the SQL request. /// Actually, the table is split into its columns. /// </summary> /// <param name="tableExpression"></param> /// <param name="dataRecordParameter"></param> /// <param name="mappingContextParameter"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual Expression GetOutputTableReader(TableExpression tableExpression, ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter, BuilderContext builderContext) { var bindings = new List<MemberBinding>(); foreach (ColumnExpression columnExpression in RegisterAllColumns(tableExpression, builderContext)) { MemberInfo memberInfo = columnExpression.StorageInfo ?? columnExpression.MemberInfo; PropertyInfo propertyInfo = memberInfo as PropertyInfo; if (propertyInfo == null || propertyInfo.CanWrite) { var parameterColumn = GetOutputValueReader(columnExpression, dataRecordParameter, mappingContextParameter, builderContext); var binding = Expression.Bind(memberInfo, parameterColumn); bindings.Add(binding); } } var newExpression = Expression.New(tableExpression.Type); var initExpression = Expression.MemberInit(newExpression, bindings); return initExpression; }
/// <summary> /// Creates a new BuilderContext with a new query scope /// </summary> /// <returns></returns> public BuilderContext NewSelect() { var builderContext = new BuilderContext(); // we basically copy everything builderContext.QueryContext = QueryContext; builderContext.ExpressionQuery = ExpressionQuery; builderContext.MetaTables = MetaTables; builderContext.Parameters = Parameters; builderContext.SelectExpressions = SelectExpressions; builderContext.ExpectMetaTableDefinition = ExpectMetaTableDefinition; // except CurrentScope, of course builderContext.currentScopeIndex = SelectExpressions.Count; SelectExpressions.Add(new SelectExpression(CurrentSelect)); return builderContext; }
/// <summary> /// Registers a MetaTable /// </summary> /// <param name="metaTableType"></param> /// <param name="aliases"></param> /// <param name="builderContext"></param> /// <returns></returns> public virtual MetaTableExpression RegisterMetaTable(Type metaTableType, IDictionary<MemberInfo, TableExpression> aliases, BuilderContext builderContext) { MetaTableExpression metaTableExpression; if (!builderContext.MetaTables.TryGetValue(metaTableType, out metaTableExpression)) { metaTableExpression = new MetaTableExpression(aliases); builderContext.MetaTables[metaTableType] = metaTableExpression; } return metaTableExpression; }
/// <summary> /// Registers a where clause in the current context scope /// </summary> /// <param name="whereExpression"></param> /// <param name="builderContext"></param> public virtual void RegisterWhere(Expression whereExpression, BuilderContext builderContext) { builderContext.CurrentSelect.Where.Add(whereExpression); }
protected virtual SelectExpression FindTableScope(ref TableExpression tableExpression, BuilderContext builderContext) { foreach (var scope in builderContext.SelectExpressions) { for (int tableIndex = 0; tableIndex < scope.Tables.Count; tableIndex++) { if (scope.Tables[tableIndex].IsEqualTo(tableExpression)) { tableExpression = scope.Tables[tableIndex]; scope.Tables.RemoveAt(tableIndex); return scope; } } } return null; }
/// <summary> /// Registers an expression to be returned by main request. /// The strategy is to try to find it in the already registered parameters, and if not found, add it /// </summary> /// <param name="expression">The expression to be registered</param> /// <param name="builderContext"></param> /// <returns>Expression index</returns> public virtual int RegisterOutputParameter(Expression expression, BuilderContext builderContext) { var scope = builderContext.CurrentSelect; var operands = scope.Operands.ToList(); for (int index = 0; index < operands.Count; index++) { if (ExpressionEquals(operands[index], expression)) return index; } operands.Add(expression); builderContext.CurrentSelect = (SelectExpression)scope.Mutate(operands); return operands.Count - 1; }
/// <summary> /// Registers a column /// This method requires the table to be already registered /// </summary> /// <param name="table"></param> /// <param name="memberInfo"></param> /// <param name="name"></param> /// <param name="builderContext"></param> /// <returns></returns> public ColumnExpression RegisterColumn(TableExpression table, MemberInfo memberInfo, string name, BuilderContext builderContext) { if (memberInfo == null) return null; var queryColumn = GetRegisteredColumn(table, name, builderContext); if (queryColumn == null) { table = RegisterTable(table, builderContext); queryColumn = CreateColumn(table, memberInfo, builderContext); builderContext.CurrentSelect.Columns.Add(queryColumn); } return queryColumn; }
/// <summary> /// Builds a Row builder, based on a given list of parameters /// </summary> /// <param name="tableType"></param> /// <param name="parameters"></param> /// <param name="builderContext"></param> /// <returns></returns> public virtual LambdaExpression BuildTableReader(Type tableType, IList<string> parameters, BuilderContext builderContext) { var dataRecordParameter = Expression.Parameter(typeof(IDataRecord), "dataRecord"); var mappingContextParameter = Expression.Parameter(typeof(MappingContext), "mappingContext"); //var table = builderContext.QueryContext.DataContext.Mapping.GetTable(tableType); var bindings = new List<MemberBinding>(); for (int parameterIndex = 0; parameterIndex < parameters.Count; parameterIndex++) { var parameter = parameters[parameterIndex]; var memberInfo = tableType.GetSingleMember(parameter); if (memberInfo == null) { memberInfo = tableType.GetSingleMember(parameter, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase); } // TODO real error if (memberInfo == null) throw new ArgumentException(string.Format("Invalid column '{0}'", parameter)); //var column = DataMapper.GetColumnName(tableType, memberInfo, builderContext.QueryContext.DataContext); //var columnName = DataMapper.GetColumnName(tableType, memberInfo, builderContext.QueryContext.DataContext); var invoke = GetOutputValueReader(memberInfo.GetMemberType(), parameterIndex, //GetTableIndex(parameters, columnName), dataRecordParameter, mappingContextParameter); var parameterColumn = GetOutputValueReader(invoke, dataRecordParameter, mappingContextParameter, builderContext); var binding = Expression.Bind(memberInfo, parameterColumn); bindings.Add(binding); } var newExpression = Expression.New(tableType); var initExpression = Expression.MemberInit(newExpression, bindings); return Expression.Lambda(initExpression, dataRecordParameter, mappingContextParameter); }
/// <summary> /// Creates a default TableExpression /// </summary> /// <param name="tableType"></param> /// <param name="builderContext"></param> /// <returns></returns> public virtual TableExpression CreateTable(Type tableType, BuilderContext builderContext) { return new TableExpression(tableType, DataMapper.GetTableName(tableType, builderContext.QueryContext.DataContext)); }
/// <summary> /// Registers the expression as returned by the SQL request. /// </summary> /// <param name="expression"></param> /// <param name="dataRecordParameter"></param> /// <param name="mappingContextParameter"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual Expression GetOutputValueReader(Expression expression, ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter, BuilderContext builderContext) { int valueIndex = RegisterOutputParameter(expression, builderContext); return GetOutputValueReader(expression.Type, valueIndex, dataRecordParameter, mappingContextParameter); }
/// <summary> /// Registers an association /// </summary> /// <param name="tableExpression">The table holding the member, to become the joinedTable</param> /// <param name="tableMemberInfo"></param> /// <param name="builderContext"></param> /// <returns></returns> public virtual TableExpression RegisterAssociation(TableExpression tableExpression, MemberInfo tableMemberInfo, BuilderContext builderContext) { IList<MemberInfo> theseKeys, otherKeys; TableJoinType joinType; string joinID; var otherTableType = DataMapper.GetAssociation(tableExpression, tableMemberInfo, out theseKeys, out otherKeys, out joinType, out joinID, builderContext.QueryContext.DataContext); // if the memberInfo has no corresponding association, we get a null, that we propagate if (otherTableType == null) return null; // the current table has the foreign key, the other table the referenced (usually primary) key if (theseKeys.Count != otherKeys.Count) throw Error.BadArgument("S0128: Association arguments (FK and ref'd PK) don't match"); // we first create the table, with the JoinID, and we MUST complete the table later, with the Join() method var otherTableExpression = new TableExpression(otherTableType, DataMapper.GetTableName(otherTableType, builderContext.QueryContext.DataContext), joinID); Expression joinExpression = null; var createdColumns = new List<ColumnExpression>(); for (int keyIndex = 0; keyIndex < theseKeys.Count; keyIndex++) { // joinedKey is registered, even if unused by final select (required columns will be filtered anyway) Expression otherKey = RegisterColumn(otherTableExpression, otherKeys[keyIndex], builderContext); // foreign is created, we will store it later if this assocation is registered too Expression thisKey = CreateColumn(tableExpression, theseKeys[keyIndex], builderContext); createdColumns.Add((ColumnExpression)thisKey); // if the key is nullable, then convert it // TODO: this will probably need to be changed if (otherKey.Type.IsNullable()) otherKey = Expression.Convert(otherKey, otherKey.Type.GetNullableType()); if (thisKey.Type.IsNullable()) thisKey = Expression.Convert(thisKey, thisKey.Type.GetNullableType()); var referenceExpression = Expression.Equal(thisKey, otherKey); // if we already have a join expression, then we have a double condition here, so "AND" it if (joinExpression != null) joinExpression = Expression.And(joinExpression, referenceExpression); else joinExpression = referenceExpression; } // we complete the table here, now that we have all join information otherTableExpression.Join(joinType, tableExpression, joinExpression); // our table is created, with the expressions // now check if we didn't register exactly the same if ((from t in builderContext.EnumerateScopeTables() where t.IsEqualTo(otherTableExpression) select t).SingleOrDefault() == null) { builderContext.CurrentSelect.Tables.Add(otherTableExpression); foreach (var createdColumn in createdColumns) builderContext.CurrentSelect.Columns.Add(createdColumn); } return otherTableExpression; }
public ColumnExpression CreateColumn(TableExpression table, MemberInfo memberInfo, BuilderContext builderContext) { var dataMember = builderContext.QueryContext.DataContext.Mapping.GetTable(table.Type).RowType .GetDataMember(memberInfo); if (dataMember == null) return null; return new ColumnExpression(table, dataMember.MappedName, memberInfo); }
public BuilderContext Clone() { var builderContext = new BuilderContext(); builderContext.QueryContext = QueryContext; builderContext.ExpressionQuery = ExpressionQuery; builderContext.MetaTables = MetaTables; builderContext.Parameters = Parameters; builderContext.SelectExpressions = SelectExpressions; builderContext.currentScopeIndex = currentScopeIndex; builderContext.ExpectMetaTableDefinition = ExpectMetaTableDefinition; return builderContext; }