public virtual void BuildSelect(Expression selectExpression, TranslationContext context, Type expectedResultType = null) { // collect columns, split Expression in // - things we will do in CLR // - things we will do in SQL expectedResultType = expectedResultType ?? selectExpression.Type; LambdaExpression recordReaderExpr; var dataRecordParameter = Expression.Parameter(typeof(IDataRecord), "dataRecord"); var sessionParameter = Expression.Parameter(typeof(EntitySession), "session"); // if we have a GroupByExpression, the result type is not the same: // - we need to read what is going to be the Key expression // - the final row generator builds a IGrouping<K,T> instead of T var selectGroupExpression = selectExpression as GroupExpression; if (selectGroupExpression != null) { var sqlOutExpr = CutOutSqlTierLambda(selectGroupExpression.GroupedExpression, dataRecordParameter, sessionParameter, null, context); var selectKeyExpr = CutOutSqlTierLambda(selectGroupExpression.KeyExpression, dataRecordParameter, sessionParameter, null, context); recordReaderExpr = sqlOutExpr; if (selectGroupExpression.UseClrGrouping) { recordReaderExpr = BuildGroupByPairsReader(sqlOutExpr, selectKeyExpr, dataRecordParameter, sessionParameter, context); var grouper = QueryResultsProcessor.CreateGroupBy(selectKeyExpr.Body.Type, sqlOutExpr.Body.Type); context.CurrentSelect.ResultsProcessor = grouper; context.CurrentSelect.Group.Remove(selectGroupExpression); } } else recordReaderExpr = CutOutSqlTierLambda(selectExpression, dataRecordParameter, sessionParameter, expectedResultType, context); context.CurrentSelect.Reader = recordReaderExpr; }
/// <summary> /// Registers the first table. Extracts the table type and registeres the piece /// </summary> /// <param name="fromExpression"></param> /// <param name="context"></param> /// <returns></returns> public virtual Expression ExtractFirstTable(Expression fromExpression, TranslationContext context) { switch(fromExpression.NodeType) { case ExpressionType.Call: var callExpression = (MethodCallExpression)fromExpression; var srcQueryExpr = callExpression.Arguments.Count > 0 ? callExpression.Arguments[0] : null; Util.Check(srcQueryExpr != null, "LINQ: failed to create source table for {0}. Expected a query expression as argument. ", fromExpression); switch(srcQueryExpr.NodeType) { case ExpressionType.Constant: // most common case - it is an EntitySet wrapped in constant return ExtractFirstTable(srcQueryExpr, context); default: // special case - source is expression like 'pub.Books' where pub is parameter inside lambda return Analyze(srcQueryExpr, context); } case ExpressionType.Constant: var constExpr = (ConstantExpression)fromExpression; var entQuery = constExpr.Value as Vita.Entities.Linq.EntityQuery; if (entQuery == null) break; var iLock = entQuery as ILockTarget; var lockOptions = iLock == null ? LockOptions.None : iLock.LockOptions; return CreateTable(entQuery.ElementType, context, lockOptions); } Util.Throw("LINQ engine error (ExtractFirstTable): failed to translate expression: {0}", fromExpression); return null; //never happens }
protected virtual void AddOffset(Expression offset, TranslationContext context) { var previousOffset = context.CurrentSelect.Offset; if (previousOffset != null) context.CurrentSelect.Offset = Expression.Add(offset, previousOffset); else context.CurrentSelect.Offset = offset; }
protected virtual void AddLimit(Expression limit, TranslationContext context) { var previousLimit = context.CurrentSelect.Limit; if (previousLimit != null) context.CurrentSelect.Limit = MergeLimits(previousLimit, limit); else context.CurrentSelect.Limit = limit; }
public TranslationContext(TranslationContext source) { this.DbModel = source.DbModel; this.Command = source.Command; this.CallStack = source.CallStack; this.ExternalValues = source.ExternalValues; this.MetaTables = source.MetaTables; this.SelectExpressions = source.SelectExpressions; this.LambdaParameters = source.LambdaParameters; this._currentScopeIndex = source._currentScopeIndex; }
public Expression Analyze(ExpressionChain expressionChain, Expression parameter, TranslationContext context) { Expression resultExpression = parameter; Expression last = expressionChain.Last(); foreach (Expression expr in expressionChain) { if (expr == last) context.IsExternalInExpressionChain = true; resultExpression = this.Analyze(expr, resultExpression, context); } return resultExpression; }
protected Expression Analyze(Expression expression, TranslationContext rewriterContext) { // small optimization if (expression is ConstantExpression) return expression; // RI: handling comparison with NULL is moved to ExpressionDispatcher //expression = AnalyzeNull(expression, rewriterContext); expression = AnalyzeNot(expression, rewriterContext); expression = AnalyzeBinaryBoolean(expression, rewriterContext); // constant optimization at last, because the previous optimizations may generate constant expressions //RI: disabled this //expression = AnalyzeConstant(expression, rewriterContext); return expression; }
//RI: new stuff to handle 2 special cases: 1. Entity comparison 2. Comparison with NULL // Comparison with null used to be in ExpressionOptimizer, moved here - it is not optimization, it is essential for correct SQL protected virtual Expression CheckEqualOperator(BinaryExpression expression, TranslationContext context) { var left = expression.Left; var right = expression.Right; var hasNonNullable = IsNonNullableMember(left) || IsNonNullableMember(right); // Try getting PK expressions - not null if we have entity references; replace expression with comparison of PKs var leftKey = GetPrimaryKeyAccessor(left, context); var rightKey = GetPrimaryKeyAccessor(right, context); if(leftKey != null && rightKey != null) { var hasParameters = left.NodeType == ExpressionType.Parameter || right.NodeType == ExpressionType.Parameter; if(hasParameters && !hasNonNullable && expression.NodeType == ExpressionType.Equal) return CreateSqlFunction(SqlFunctionType.EqualNullables, leftKey, rightKey); else return Expression.MakeBinary(expression.NodeType, leftKey, rightKey); } // check if it is null comparison var isNullFuncType = expression.NodeType == ExpressionType.Equal ? SqlFunctionType.IsNull : SqlFunctionType.IsNotNull; if (leftKey != null && right.IsConstNull()) return CreateSqlFunction(isNullFuncType, leftKey); if (rightKey != null && left.IsConstNull()) return CreateSqlFunction(isNullFuncType, rightKey); // null-checks of plain values (strings) if (left.IsConstNull()) return CreateSqlFunction(isNullFuncType, right); if (right.IsConstNull()) return CreateSqlFunction(isNullFuncType, left); // If it is Equal, and both are nullable, add matching NULLs; // a == b -> (a = b) | (a is Null & b IS NULL) var forceIgnoreCase = context.Command.Info.Options.IsSet(QueryOptions.ForceIgnoreCase); if (forceIgnoreCase && left.Type == typeof(string) & right.Type == typeof(string)) { return CreateSqlFunction(SqlFunctionType.StringEqual, forceIgnoreCase, left, right); } if (expression.NodeType == ExpressionType.Equal && !hasNonNullable && left.NodeType != ExpressionType.Constant && right.NodeType != ExpressionType.Constant && left.Type.IsNullable() && right.Type.IsNullable()) { return CreateSqlFunction(SqlFunctionType.EqualNullables, forceIgnoreCase, left, right); } return expression; }
protected virtual Expression AnalyzeNot(Expression expression, TranslationContext rewriterContext) { if (expression.NodeType == ExpressionType.Not) { var notExpression = expression as UnaryExpression; var subExpression = notExpression.Operand; var subOperands = subExpression.GetOperands().ToArray(); switch (subExpression.NodeType) { case ExpressionType.Equal: return Expression.NotEqual(subOperands[0], subOperands[1]); case ExpressionType.GreaterThan: return Expression.LessThanOrEqual(subOperands[0], subOperands[1]); case ExpressionType.GreaterThanOrEqual: return Expression.LessThan(subOperands[0], subOperands[1]); case ExpressionType.LessThan: return Expression.GreaterThanOrEqual(subOperands[0], subOperands[1]); case ExpressionType.LessThanOrEqual: return Expression.GreaterThan(subOperands[0], subOperands[1]); case ExpressionType.Not: return subOperands[0]; // not not x -> x :) case ExpressionType.NotEqual: return Expression.Equal(subOperands[0], subOperands[1]); case ExpressionType.Extension: if (subExpression is SqlFunctionExpression) { var funcExpr = (SqlFunctionExpression) subExpression; switch(funcExpr.FunctionType) { case SqlFunctionType.IsNotNull: return new SqlFunctionExpression(SqlFunctionType.IsNull, typeof(bool), subOperands); case SqlFunctionType.IsNull: return new SqlFunctionExpression(SqlFunctionType.IsNotNull, typeof(bool), subOperands); } }//if break; } } return expression; }
/// <summary> /// Registers a where clause in the current context scope /// </summary> /// <param name="whereExpression"></param> /// <param name="context"></param> public virtual void RegisterWhere(Expression whereExpression, TranslationContext context) { var where = CheckBoolBitExpression(whereExpression); context.CurrentSelect.Where.Add(where); }
public ColumnExpression CreateColumn(TableExpression table, string memberName, TranslationContext context) { var col = table.TableInfo.GetColumnByMemberName(memberName); Util.Check(col != null, "Column for member [{0}] not found in table {1}. Member type is not supported by LINQ.", memberName, table.Name); return new ColumnExpression(table, col); }
public ColumnExpression CreateColumn(TableExpression table, string memberName, TranslationContext context) { var col = table.TableInfo.GetColumnByMemberName(memberName); Util.Check(col != null, "Column for member [{0}] not found in table {1}. Member type is not supported by LINQ.", memberName, table.Name); return(new ColumnExpression(table, col)); }
private Expression GetPrimaryKeyAccessor(Expression expression, TranslationContext context) { if(!expression.Type.IsInterface) return null; if (expression.Type.IsDbPrimitive()) return null; if(expression.IsConstNull()) return null; var table = context.DbModel.GetTable(expression.Type, throwIfNotFound: false); if (table == null) return null; var pkMembers = table.PrimaryKey.EntityKey.KeyMembers; if (pkMembers.Count > 1) Util.Throw("Equal operator for entities with composite key is not supported. Expression: {0}.", expression); var pkProp = pkMembers[0].Member.ClrMemberInfo; if (pkProp == null) //theoretically it might happen Util.Throw("Using hidden members in LINQ is not supported. Expression: {0}.", expression); Expression pkRead = Expression.MakeMemberAccess(expression, pkProp); // Bug fix - one-to-one relation // in case it is an interface/entity - repeat getting PK again if (pkRead.Type.IsInterface) pkRead = GetPrimaryKeyAccessor(pkRead, context); return pkRead; }
//Checks operands of Join expression: if it returns Entity, replaces it with PK of entity private Expression CheckJoinKeySelector(Expression selector, TranslationContext context) { var tmp = selector; if (tmp.NodeType == ExpressionType.Quote) tmp = ((UnaryExpression)tmp).Operand; // It should be lambda var lambda = (LambdaExpression)tmp; var retType = lambda.Body.Type; if (retType.IsDbPrimitive()) return selector; var newBody = GetPrimaryKeyAccessor(lambda.Body, context); if (newBody == null) return selector; //Build new quoted lambda var newSelector = Expression.Quote(Expression.Lambda(newBody, lambda.Parameters)); return newSelector; }
/// <summary> /// Registers all columns of a table. /// </summary> /// <param name="tableExpression"></param> /// <param name="context"></param> /// <returns></returns> public virtual IList <ColumnExpression> RegisterAllColumns(TableExpression tableExpression, TranslationContext context) { var result = new List <ColumnExpression>(); foreach (var colInfo in tableExpression.TableInfo.Columns) { if (colInfo.Member == null) { continue; } var colExpr = RegisterColumn(tableExpression, colInfo.Member.MemberName, context); result.Add(colExpr); } return(result); }
public virtual TableExpression CreateTable(Type tableType, TranslationContext context, LockOptions lockOptions = LockOptions.None) { var tableInfo = _dbModel.GetTable(tableType); var tableExpr = new TableExpression(tableType, tableInfo.FullName, tableInfo, lockOptions); return tableExpr; }
/// <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="context"></param> /// <returns></returns> protected virtual ColumnExpression GetRegisteredColumn(TableExpression table, string name, TranslationContext context) { return (from queryColumn in context.EnumerateScopeColumns() where queryColumn.Table.IsEqualTo(table) && queryColumn.Name == name // where queryColumn.Table == table && queryColumn.Name == name // - RI: this does not work select queryColumn).SingleOrDefault(); }
// RI: new stuff, optimized entity reader protected virtual Expression GetOutputTableReader(TableExpression tableExpression, ParameterExpression dataRecordParameter, ParameterExpression sessionParameter, TranslationContext context) { // Note: we have to create materializer each time, because column indexes in output can change from query to query var entMatzer = new EntityMaterializer(tableExpression.TableInfo); var allColExprs = RegisterAllColumns(tableExpression, context); foreach (var col in allColExprs) { var colIndex = RegisterOutputValue(col, context); entMatzer.AddColumn(col.ColumnInfo, colIndex); } var entMatzerConst = Expression.Constant(entMatzer); var callReadEntity = Expression.Call(entMatzerConst, EntityMaterializer.ReadMethodInfo, dataRecordParameter, sessionParameter); var convExpr = Expression.Convert(callReadEntity, tableExpression.Type); return convExpr; }
public virtual TableExpression RegisterAssociation(TableExpression tableExpression, EntityMemberInfo refMember, TranslationContext context) { IList <MemberInfo> otherKeys; TableJoinType joinType; string joinID; var theseKeys = GetAssociationMembers(tableExpression, refMember, out otherKeys, out joinType, out joinID); // if the memberInfo has no corresponding association, we get a null, that we propagate if (theseKeys == null) { return(null); } // the current table has the foreign key, the other table the referenced (usually primary) key if (theseKeys.Count != otherKeys.Count) { Util.Throw("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 otherType = refMember.DataType; var otherTableInfo = context.DbModel.GetTable(otherType); var otherTableExpression = CreateTable(otherType, context); // new TableExpression(otherType, otherTableInfo.FullName, otherTableInfo, joinID); otherTableExpression = RegisterTable(otherTableExpression, context); 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], context); // foreign is created, we will store it later if this assocation is registered too Expression thisKey = CreateColumn(tableExpression, theseKeys[keyIndex].Name, context); createdColumns.Add((ColumnExpression)thisKey); // the other key is set as left operand, this must be this way // since some vendors (SQL Server) don't support the opposite var referenceExpression = MakeEqual(otherKey, thisKey); // 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 var existingTable = (from t in context.EnumerateScopeTables() where t.IsEqualTo(otherTableExpression) select t).SingleOrDefault(); if (existingTable != null) { return(existingTable); } context.CurrentSelect.Tables.Add(otherTableExpression); foreach (var createdColumn in createdColumns) { context.CurrentSelect.Columns.Add(createdColumn); } return(otherTableExpression); }
protected virtual Expression GetOutputValueReader(Expression expression, Type expectedType, ParameterExpression dataRecordParameter, ParameterExpression sessionParameter, TranslationContext context) { expectedType = expectedType ?? expression.Type; int valueIndex = RegisterOutputValue(expression, context); Func <object, object> conv = x => x; if (expression is ColumnExpression) { //With column everything is simple var colExpr = (ColumnExpression)expression; conv = colExpr.ColumnInfo.TypeInfo.ColumnToPropertyConverter; } else { //Otherwise get converter from type registry; // Why we need converters for non-column values. // Example: Count(*) function. In this case expectedType is always 'int', but some providers (MySql) return Int64. // In this case expression.Type is Int64 (it is SqlFunction expression), while expectedType is int32. We need a converter. // Note - even if expression.Type is the same as expectedType, we still might need a converter. // Example : 'Max(decimalCol)' in SQLite. SQLite stores decimals as doubles (we do), so SQL will return double; // we need an extra converter to cast to Decimal expected by c# code. We must go through TypeRegistry anyway, // to verify how type is supported. conv = _dbModel.Driver.TypeRegistry.GetLinqValueConverter(expression.Type, expectedType); } var reader = ColumnReaderHelper.GetColumnValueReader(dataRecordParameter, valueIndex, expectedType, conv); return(reader); }
/// <summary> /// Promotes a table to a common parent between its current scope and our current scope /// </summary> /// <param name="tableExpression"></param> /// <param name="context"></param> /// <returns></returns> protected virtual TableExpression PromoteTable(TableExpression tableExpression, TranslationContext context) { int currentIndex = 0; SelectExpression oldSelect = null; SelectExpression commonScope = null; TableExpression foundTable = null; do { // take a select oldSelect = context.SelectExpressions[currentIndex]; // look for a common scope if (oldSelect != context.CurrentSelect) { commonScope = FindCommonScope(oldSelect, context.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 < context.SelectExpressions.Count && foundTable == null); if (foundTable != null) { oldSelect.Tables.Remove(foundTable); commonScope.Tables.Add(foundTable); } return(foundTable); }
/// <summary> /// Returns an existing table or registers the current one /// </summary> /// <param name="tableExpression"></param> /// <param name="context"></param> /// <returns>A registered table or the current newly registered one</returns> public virtual TableExpression RegisterTable(TableExpression tableExpression, TranslationContext context) { // 1. Find the table in current scope var foundTableExpression = (from t in context.EnumerateScopeTables() where t.IsEqualTo(tableExpression) select t).FirstOrDefault(); if (foundTableExpression != null) { return(foundTableExpression); } // 2. Find it in all scopes, and promote it to current scope. foundTableExpression = PromoteTable(tableExpression, context); if (foundTableExpression != null) { return(foundTableExpression); } // 3. Add it context.CurrentSelect.Tables.Add(tableExpression); return(tableExpression); }
protected virtual Expression GetOutputValueReader(Expression expression, Type expectedType, ParameterExpression dataRecordParameter, ParameterExpression sessionParameter, TranslationContext context) { expectedType = expectedType ?? expression.Type; int valueIndex = RegisterOutputValue(expression, context); Func<object, object> conv = x => x; if(expression is ColumnExpression) { //With column everything is simple var colExpr = (ColumnExpression)expression; conv = colExpr.ColumnInfo.TypeInfo.ColumnToPropertyConverter; } else { //Otherwise get converter from type registry; // Why we need converters for non-column values. // Example: Count(*) function. In this case expectedType is always 'int', but some providers (MySql) return Int64. // In this case expression.Type is Int64 (it is SqlFunction expression), while expectedType is int32. We need a converter. // Note - even if expression.Type is the same as expectedType, we still might need a converter. // Example : 'Max(decimalCol)' in SQLite. SQLite stores decimals as doubles (we do), so SQL will return double; // we need an extra converter to cast to Decimal expected by c# code. We must go through TypeRegistry anyway, // to verify how type is supported. conv = _dbModel.Driver.TypeRegistry.GetLinqValueConverter(expression.Type, expectedType); } var reader = ColumnReaderHelper.GetColumnValueReader(dataRecordParameter, valueIndex, expectedType, conv); return reader; }
/// <summary> /// Registers all columns of a table. /// </summary> /// <param name="tableExpression"></param> /// <param name="context"></param> /// <returns></returns> public virtual IList<ColumnExpression> RegisterAllColumns(TableExpression tableExpression, TranslationContext context) { var result = new List<ColumnExpression>(); foreach (var colInfo in tableExpression.TableInfo.Columns) { if (colInfo.Member == null) continue; var colExpr = RegisterColumn(tableExpression, colInfo.Member.MemberName, context); result.Add(colExpr); } return result; }
/// <summary> /// Promotes a table to a common parent between its current scope and our current scope /// </summary> /// <param name="tableExpression"></param> /// <param name="context"></param> /// <returns></returns> protected virtual TableExpression PromoteTable(TableExpression tableExpression, TranslationContext context) { int currentIndex = 0; SelectExpression oldSelect = null; SelectExpression commonScope = null; TableExpression foundTable = null; do { // take a select oldSelect = context.SelectExpressions[currentIndex]; // look for a common scope if (oldSelect != context.CurrentSelect) { commonScope = FindCommonScope(oldSelect, context.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 < context.SelectExpressions.Count && foundTable == null); if (foundTable != null) { oldSelect.Tables.Remove(foundTable); commonScope.Tables.Add(foundTable); } return foundTable; }
private Expression BuildListPropertyJoinExpression(TableExpression fromTable, TableExpression toTable, EntityMemberInfo refMember, TranslationContext context) { var fromKeyMembers = refMember.ReferenceInfo.FromKey.ExpandedKeyMembers; var toKeyMembers = refMember.ReferenceInfo.ToKey.ExpandedKeyMembers; var clauses = new List<Expression>(); for (int i = 0; i < fromKeyMembers.Count; i++) { var mFrom = fromKeyMembers[i]; var mTo = toKeyMembers[i]; var colFrom = RegisterColumn(fromTable, mFrom.Member.ClrClassMemberInfo, context); var colTo = RegisterColumn(toTable, mTo.Member.ClrClassMemberInfo, context); var eqExpr = MakeEqual(colFrom, colTo); clauses.Add(eqExpr); } //Build AND var result = clauses[0]; for (int i = 1; i < clauses.Count; i++) result = Expression.And(result, clauses[i]); return result; }
public virtual TableExpression RegisterAssociation(TableExpression tableExpression, EntityMemberInfo refMember, TranslationContext context) { IList<MemberInfo> otherKeys; TableJoinType joinType; string joinID; var theseKeys = GetAssociationMembers(tableExpression, refMember, out otherKeys, out joinType, out joinID); // if the memberInfo has no corresponding association, we get a null, that we propagate if (theseKeys == null) return null; // the current table has the foreign key, the other table the referenced (usually primary) key if (theseKeys.Count != otherKeys.Count) Util.Throw("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 otherType = refMember.DataType; var otherTableInfo = context.DbModel.GetTable(otherType); var otherTableExpression = CreateTable(otherType, context); // new TableExpression(otherType, otherTableInfo.FullName, otherTableInfo, joinID); otherTableExpression = RegisterTable(otherTableExpression, context); 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], context); // foreign is created, we will store it later if this assocation is registered too Expression thisKey = CreateColumn(tableExpression, theseKeys[keyIndex].Name, context); createdColumns.Add((ColumnExpression)thisKey); // the other key is set as left operand, this must be this way // since some vendors (SQL Server) don't support the opposite var referenceExpression = MakeEqual(otherKey, thisKey); // 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 var existingTable = (from t in context.EnumerateScopeTables() where t.IsEqualTo(otherTableExpression) select t).SingleOrDefault(); if (existingTable != null) return existingTable; context.CurrentSelect.Tables.Add(otherTableExpression); foreach (var createdColumn in createdColumns) context.CurrentSelect.Columns.Add(createdColumn); return otherTableExpression; }
private Expression AnalyzeEntityListMember(TableExpression table, PropertyInfo property, TranslationContext context) { var propType = property.PropertyType; if (!propType.IsEntitySequence()) return null; var modelInfo = context.DbModel.EntityApp.Model; var masterEntInfo = modelInfo.GetEntityInfo(table.Type); var entMember = masterEntInfo.GetMember(property.Name); Util.Check(entMember != null, "Failed to find member {0} on entity {1}.", property.Name, masterEntInfo.Name); Util.Check(entMember.Kind == MemberKind.EntityList, "Internal LINQ error: expected List member ({0}.{1}", masterEntInfo.Name, property.Name); var listInfo = entMember.ChildListInfo; Expression whereExpr; switch(listInfo.RelationType) { case EntityRelationType.ManyToOne: var childTable = CreateTable(listInfo.TargetEntity.EntityType, context); whereExpr = BuildListPropertyJoinExpression(childTable, table, listInfo.ParentRefMember, context); if (!string.IsNullOrWhiteSpace(listInfo.Filter)) { var filterExpr = new TableFilterExpression(childTable, listInfo.Filter); whereExpr = Expression.And(whereExpr, filterExpr); } context.CurrentSelect.Where.Add(whereExpr); return childTable; case EntityRelationType.ManyToMany: var linkTable = CreateTable(listInfo.LinkEntity.EntityType, context); whereExpr = BuildListPropertyJoinExpression(linkTable, table, listInfo.ParentRefMember, context); context.CurrentSelect.Where.Add(whereExpr); var targetTable = RegisterAssociation(linkTable, listInfo.OtherEntityRefMember, context); return targetTable; } return null; //never happens }
/// <summary> /// Registers a column /// This method requires the table to be already registered /// </summary> /// <param name="table"></param> /// <param name="name"></param> /// <param name="context"></param> /// <returns></returns> public ColumnExpression RegisterColumn(TableExpression table, string name, TranslationContext context) { var queryColumn = GetRegisteredColumn(table, name, context); if (queryColumn == null) { table = RegisterTable(table, context); queryColumn = CreateColumn(table, name, context); context.CurrentSelect.Columns.Add(queryColumn); } return queryColumn; }
// Converts expression returning list of entities into expression returning list of primary keys. // ex: session.EntitySet<IBook>() -> session.EntitySet<IBook>().Select(b=>b.Id) // This is used for conversion of expressions involving entities - which SQL cannot process directly- into expressions over IDs // One example is entSet.Contains(ent) method - we convert it into entSet.Select(e => e.Id).Contains(ent.Id) private Expression BuildSelectPrimaryKeys(Expression expression, TranslationContext context) { var entType = expression.Type.GetGenericArguments()[0]; // build lambda var entPrm = Expression.Parameter(entType, "p"); var body = GetPrimaryKeyAccessor(entPrm, context); var pkType = body.Type; var lambda = Expression.Lambda(body, entPrm); var quotedLambda = Expression.Quote(lambda); //build Select method var genSelMethod = ExpressionUtil.QueryableSelectMethod.MakeGenericMethod(entType, pkType); // Special case - when entity set is list, ex: pub.Books property; If we want to put Queryable.Select over pub.Books, we must convert // the argument. It has no effect on SQL generation, it purely to satisfy type checking restraints of expression construction. if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) { var asQMethod = ExpressionUtil.QueryableAsQueryableMethod.MakeGenericMethod(entType); expression = Expression.Call(null, asQMethod, expression); } var callSelect = Expression.Call(null, genSelMethod, expression, quotedLambda); return callSelect; }
public ColumnExpression RegisterColumn(TableExpression tableExpression, MemberInfo memberInfo, TranslationContext context) { return RegisterColumn(tableExpression, memberInfo.Name, context); }
private IList<Expression> ConvertContainsWithObject(IList<Expression> parameters, TranslationContext context) { var arg1Type = parameters[1].Type; if (arg1Type.IsDbPrimitive()) return parameters; // we have List<Entity>.Contains - convert to using IDs var newParams = new List<Expression>(); // for param 0 (entity sequence), add 'Select(e=>e.Id)' var prm0 = BuildSelectPrimaryKeys(parameters[0], context); //for parameter 1 (entity to select), add accessor to primary key var prm1 = GetPrimaryKeyAccessor(parameters[1], context); newParams.Add(prm0); newParams.Add(prm1); return newParams; }
/// <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="context"></param> /// <returns>Expression index</returns> public virtual int RegisterOutputValue(Expression expression, TranslationContext context) { var scope = context.CurrentSelect; //check if expression is already registered var operands = scope.Operands.ToList(); var index = operands.IndexOf(expression); if (index >= 0) return index; operands.Add(expression); context.CurrentSelect = (SelectExpression)scope.Mutate(operands); return operands.Count - 1; }
// Converts expressions like 'book.Publisher.Id' to 'book.Publisher_id', to avoid unnecessary joins // Note that we cannot perform this optimization on original expression tree; the Publisher_id property is not available // on IBook interface, so we must first convert IBook to book table, and then create member access expr 'BookTable.Publisher_id' private Expression OptimizeChainedMemberAccess(MemberExpression memberExpression, TranslationContext context) { var bookPubIdExpr = memberExpression; //just rename it for clarity var bookPubExpr = bookPubIdExpr.Expression as MemberExpression; var bookExpr = bookPubExpr.Expression; if (bookPubExpr.Member.IsStaticMember()) return null; // check that child expr src object (book) is a Table var bookTable = context.DbModel.GetTable(bookExpr.Type, throwIfNotFound: false); if (bookTable == null) return null; // check that child expr result (book.Publisher) has Table result type var pubTable = context.DbModel.GetTable(bookPubExpr.Type, throwIfNotFound: false); if (pubTable == null) return null; //check that expr is trying read PK member of the target entity (Id of Publisher) var idMemberName = bookPubIdExpr.Member.Name; var idMember = pubTable.PrimaryKey.EntityKey.ExpandedKeyMembers.FirstOrDefault(km => km.Member.MemberName == idMemberName); if (idMember == null) return null; //OK, it might be a case for optimization. Analyze bottom parameter (Book reference) var analyzedBookExpr = Analyze(bookExpr, context); // Don't optimize it if it is based on input parameter; just return derived parameter expression. // note: we cannot return null here, it is too late - bookExpr had been already analyzed, it will mess it all if (analyzedBookExpr is ExternalValueExpression) { var bookPubPrm = DeriveMemberAccessParameter((ExternalValueExpression)analyzedBookExpr, bookPubExpr.Member, context); var bookPubIdPrm = DeriveMemberAccessParameter(bookPubPrm, bookPubIdExpr.Member, context); return bookPubIdPrm; } // Find FK member on src table (Publisher_id on book entity) var idMemberIndex = pubTable.PrimaryKey.EntityKey.ExpandedKeyMembers.IndexOf(idMember); var pubRefMember = bookPubExpr.Member; var pubRefMemberInfo = bookTable.Entity.Members.First(m => m.ClrMemberInfo == pubRefMember); var bookPubIdMemberInfo = pubRefMemberInfo.ReferenceInfo.FromKey.ExpandedKeyMembers[idMemberIndex].Member; ColumnExpression queryColumnExpression = RegisterColumn((TableExpression)analyzedBookExpr, bookPubIdMemberInfo.ClrClassMemberInfo, context); return queryColumnExpression; }
/// <summary> /// Returns an existing table or registers the current one /// </summary> /// <param name="tableExpression"></param> /// <param name="context"></param> /// <returns>A registered table or the current newly registered one</returns> public virtual TableExpression RegisterTable(TableExpression tableExpression, TranslationContext context) { // 1. Find the table in current scope var foundTableExpression = (from t in context.EnumerateScopeTables() where t.IsEqualTo(tableExpression) select t).FirstOrDefault(); if (foundTableExpression != null) return foundTableExpression; // 2. Find it in all scopes, and promote it to current scope. foundTableExpression = PromoteTable(tableExpression, context); if (foundTableExpression != null) return foundTableExpression; // 3. Add it context.CurrentSelect.Tables.Add(tableExpression); return tableExpression; }
private TranslatedLinqCommand TranslateNonQuery(LinqCommand command) { LinqCommandPreprocessor.PreprocessCommand(_dbModel.EntityApp.Model, command); var rewriterContext = new TranslationContext(_dbModel, command); var cmdInfo = command.Info; // convert lambda params into an initial set of ExternalValueExpression objects; foreach(var prm in cmdInfo.Lambda.Parameters) { var inpParam = new ExternalValueExpression(prm); rewriterContext.ExternalValues.Add(inpParam); } //Analyze/transform base select query var exprChain = ExpressionChain.Build(cmdInfo.Lambda.Body); var selectExpr = BuildSelectExpression(exprChain, rewriterContext); // Analyze external values (parameters?), create DbParameters var cmdParams = BuildParameters(command, rewriterContext); var flags = command.Info.Flags; // If there's at least one parameter that must be converted to literal (ex: value list), we cannot cache the query bool canCache = !rewriterContext.ExternalValues.Any(v => v.SqlUse == ExternalValueSqlUse.Literal); if (!canCache) flags |= LinqCommandFlags.NoQueryCache; // !!! Before that, everyting is the same as in TranslateSelect var targetEnt = command.TargetEntity; var targetTableInfo = _dbModel.GetTable(targetEnt.EntityType); TableExpression targetTable; bool isSingleTable = selectExpr.Tables.Count == 1 && selectExpr.Tables[0].TableInfo == targetTableInfo; if(isSingleTable) { targetTable = selectExpr.Tables[0]; } else targetTable = _translator.CreateTable(targetEnt.EntityType, rewriterContext); var commandData = new NonQueryLinqCommandData(command, selectExpr, targetTable, isSingleTable); // Analyze base query output expression var readerBody = selectExpr.Reader.Body; switch(command.CommandType) { case LinqCommandType.Update: case LinqCommandType.Insert: Util.Check(readerBody.NodeType == ExpressionType.New, "Query for LINQ {0} command must return New object", commandData.CommandType); var newExpr = readerBody as NewExpression; var outValues = selectExpr.Operands.ToList(); for(int i = 0; i < newExpr.Members.Count; i++) { var memberName = newExpr.Members[i].Name; var memberInfo = targetEnt.GetMember(memberName); Util.Check(memberInfo != null, "Member {0} not found in entity {1}.", memberName, targetEnt, targetEnt.EntityType); switch(memberInfo.Kind) { case MemberKind.Column: var col = _translator.CreateColumn(targetTable, memberName, rewriterContext); commandData.TargetColumns.Add(col); commandData.SelectOutputValues.Add(outValues[i]); break; case MemberKind.EntityRef: var fromKey = memberInfo.ReferenceInfo.FromKey; Util.Check(fromKey.ExpandedKeyMembers.Count == 1, "References with composite keys are not supported in LINQ non-query operations. Reference: ", memberName); var pkMember = fromKey.ExpandedKeyMembers[0].Member; var col2 = _translator.CreateColumn(targetTable, pkMember.MemberName, rewriterContext); commandData.TargetColumns.Add(col2); commandData.SelectOutputValues.Add(outValues[i]); break; default: Util.Throw("Property cannot be used in the context: {0}.", memberName); break; } } break; case LinqCommandType.Delete: commandData.SelectOutputValues.Add(readerBody); //should return single value - primary key break; } // Build SQL var sqlBuilder = new SqlBuilder(_dbModel); var sqlStmt = sqlBuilder.BuildNonQuery(commandData); var sqlTemplate = sqlStmt.ToString(); var defaultSql = FormatSql(sqlTemplate, cmdParams); return new TranslatedLinqCommand(sqlTemplate, defaultSql, cmdParams, flags); }
public ColumnExpression RegisterColumn(TableExpression tableExpression, MemberInfo memberInfo, TranslationContext context) { return(RegisterColumn(tableExpression, memberInfo.Name, context)); }