static Expression СonvertMemberExpression(Expression expr, Expression root, LambdaExpression l) { var body = l.Body.Unwrap(); var parms = l.Parameters.ToDictionary(p => p); var ex = body.Transform(wpi => { if (wpi.NodeType == ExpressionType.Parameter && parms.ContainsKey((ParameterExpression)wpi)) { if (wpi.Type.IsSameOrParentOf(root.Type)) { return(root); } if (ExpressionBuilder.DataContextParam.Type.IsSameOrParentOf(wpi.Type)) { if (ExpressionBuilder.DataContextParam.Type != wpi.Type) { return(Expression.Convert(ExpressionBuilder.DataContextParam, wpi.Type)); } return(ExpressionBuilder.DataContextParam); } throw new LinqToDBException($"Can't convert {wpi} to expression."); } return(wpi); }); if (ex.Type != expr.Type) { ex = new ChangeTypeExpression(ex, expr.Type); } return(ex); }
public Expression ExposeExpression(Expression expression) { if (_exposedCache.TryGetValue(expression, out var result)) { return(result); } result = expression.Transform(expr => { if (_exposedCache.TryGetValue(expr, out var aleradyExposed)) { return(new TransformInfo(aleradyExposed, true)); } switch (expr.NodeType) { case ExpressionType.MemberAccess: { var me = (MemberExpression)expr; if (me.Member.IsNullableHasValueMember()) { return(new TransformInfo(Expression.NotEqual(me.Expression, Expression.Constant(null, me.Expression.Type)), false, true)); } if (CanBeCompiled(expr)) { break; } var l = ConvertMethodExpression(me.Expression?.Type ?? me.Member.ReflectedType !, me.Member, out var alias); if (l != null) { var body = l.Body.Unwrap(); var parms = l.Parameters.ToDictionary(p => p); var ex = body.Transform(wpi => { if (wpi.NodeType == ExpressionType.Parameter && parms.ContainsKey((ParameterExpression)wpi)) { if (wpi.Type.IsSameOrParentOf(me.Expression !.Type)) { return(me.Expression); } if (ExpressionBuilder.DataContextParam.Type.IsSameOrParentOf(wpi.Type)) { if (ExpressionBuilder.DataContextParam.Type != wpi.Type) { return(Expression.Convert(ExpressionBuilder.DataContextParam, wpi.Type)); } return(ExpressionBuilder.DataContextParam); } throw new LinqToDBException($"Can't convert {wpi} to expression."); } return(wpi); }); if (ex.Type != expr.Type) { ex = new ChangeTypeExpression(ex, expr.Type); } return(new TransformInfo(AliasCall(ex, alias !), false, true)); } break; } case ExpressionType.Convert: { var ex = (UnaryExpression)expr; if (ex.Method != null) { var l = ConvertMethodExpression(ex.Method.DeclaringType !, ex.Method, out var alias); if (l != null) { var exposed = l.GetBody(ex.Operand); return(new TransformInfo(exposed, false, true)); } } break; } case ExpressionType.Constant: { var c = (ConstantExpression)expr; // Fix Mono behaviour. // //if (c.Value is IExpressionQuery) // return ((IQueryable)c.Value).Expression; if (c.Value is IQueryable queryable && !(queryable is ITable)) { var e = queryable.Expression; if (!_visitedExpressions !.Contains(e)) { _visitedExpressions !.Add(e); return(new TransformInfo(e, false, true)); } } break; } case ExpressionType.Invoke: { var invocation = (InvocationExpression)expr; if (invocation.Expression.NodeType == ExpressionType.Call) { var mc = (MethodCallExpression)invocation.Expression; if (mc.Method.Name == "Compile" && typeof(LambdaExpression).IsSameOrParentOf(mc.Method.DeclaringType !)) { if (mc.Object.EvaluateExpression() is LambdaExpression lambds) { var map = new Dictionary <Expression, Expression>(); for (int i = 0; i < invocation.Arguments.Count; i++) { map.Add(lambds.Parameters[i], invocation.Arguments[i]); } var newBody = lambds.Body.Transform(se => { if (se.NodeType == ExpressionType.Parameter && map.TryGetValue(se, out var newExpr)) { return(newExpr); } return(se); }); return(new TransformInfo(newBody, false, true)); } } } break; } } _exposedCache.Add(expr, expr); return(new TransformInfo(expr, false)); }); _exposedCache[expression] = result; return(result); }
//Creates a cached generator for the converter to use for all data tables with a matching set of data columns. Due to our assumption that data types are loose in //data tables, this will generate an extremely generic converter that doesn't statically use the data table's data typing. private static Func <DataRow, TEntity> GetConverter(IDictionary <DataColumn, string> columnToMemberMap) { List <string> columnNames = columnToMemberMap.Select(k => k.Key.ColumnName).ToList(); if (!converters.TryGetValue(columnNames, out Func <DataRow, TEntity> value)) { NewExpression instantiate = Expression.New(type); ParameterExpression dataRow = Expression.Parameter(typeof(DataRow), "dataRow"); //Declare a dictionary to prevent creating more than 1 instance of a temporary parser type. ConcurrentDictionary <Type, ParameterExpression> parameters = new ConcurrentDictionary <Type, ParameterExpression>(); List <MemberBinding> memberBindings = new List <MemberBinding>(); //Iterate over the column map and generate the expressions needed. foreach (KeyValuePair <DataColumn, string> columnToMember in columnToMemberMap) { if (TryGetMemberInfo(columnToMember.Value, columnToMember.Key.Table.CaseSensitive, out MemberInfo memberInfo)) { //Get a member expression. This is used for type resolution. Type memberType = Expression.MakeMemberAccess(instantiate, memberInfo).Type, realType = Nullable.GetUnderlyingType(memberType) ?? memberType; //Get the data column expression used to access the data row field. DataColumnExpression dataColumn = DataExpression.DataColumn(DataExpression.DataTable(dataRow), columnToMember.Key.ColumnName); //Get the is null expression used to determine if the data field is null. DataFieldIsNullExpression callDataFieldIsNull = DataExpression.DataFieldIsNull(dataRow, dataColumn); ConditionalExpression dataFieldIsNull = Expression.Condition(callDataFieldIsNull, Expression.Default(memberType), Expression.Default(memberType)); //Get the is assignable from expression used to determine if the data column can be converted to the member type. IsAssignableFromExpression callIsAssignableFrom = DataExpression.IsAssignableFrom(DataExpression.DataType(dataColumn), memberType); //Get the is assignable from true result. If member type is string, then call the trim method on the string. Expression isAssignableFromTrue = Expression.Convert(DataExpression.DataField(dataRow, dataColumn), memberType); if (typeof(string).IsAssignableFrom(memberType)) { isAssignableFromTrue = Expression.Call(isAssignableFromTrue, "Trim", Type.EmptyTypes); } //Get the conditional expression used to process the data column type conversion. ConditionalExpression isAssignableFrom = Expression.Condition(callIsAssignableFrom, isAssignableFromTrue, Expression.Default(memberType)); if (realType.GetMethods().Any(m => m.Name == "TryParse")) { //Get the is string parameter expression used for try parsing. ParameterExpression stringLocal = parameters.GetOrAdd(typeof(string), Expression.Variable(typeof(string))), outLocal = parameters.GetOrAdd(realType, Expression.Variable(realType)); //Get the type is assign and try parse expressions used for try parsing. TypeIsAssignExpression typeIsAssign = DataExpression.TypeIsAssign( DataExpression.DataField(dataRow, dataColumn), stringLocal); TryParseExpression callTryParse = DataExpression.TryParse(stringLocal, outLocal); //Get the conditional expression used to process the try parse conversion. ConditionalExpression tryParse = Expression.Condition( Expression.AndAlso(typeIsAssign, callTryParse), Expression.Convert(outLocal, memberType), Expression.Default(memberType)); //Update is assignable from with the try parse method. isAssignableFrom = isAssignableFrom.Update(isAssignableFrom.Test, isAssignableFrom.IfTrue, tryParse); } if (typeof(IConvertible).IsAssignableFrom(realType)) { //Get the change type expression. ChangeTypeExpression callChangeType = DataExpression.ChangeType( DataExpression.DataField(dataRow, dataColumn), realType); //Get the conversion expression for the change type. If member type is string, then call the trim method on the string. Expression changeType = Expression.Convert(callChangeType, memberType); if (typeof(string).IsAssignableFrom(memberType)) { changeType = Expression.Call(changeType, "Trim", Type.EmptyTypes); } //Determine if try parse is set. If so, update it and is assignable from. Otherwise, add change type to is assignable from. if (isAssignableFrom.IfFalse is ConditionalExpression tryParse) { tryParse = tryParse.Update(tryParse.Test, tryParse.IfTrue, changeType); isAssignableFrom = isAssignableFrom.Update(isAssignableFrom.Test, isAssignableFrom.IfTrue, tryParse); } else { isAssignableFrom = isAssignableFrom.Update(isAssignableFrom.Test, isAssignableFrom.IfTrue, changeType); } } dataFieldIsNull = dataFieldIsNull.Update(dataFieldIsNull.Test, dataFieldIsNull.IfTrue, isAssignableFrom); //Silently ignore conversion errors with the try catch expression. This is mostly for testing purposes and might be removed //in the future. memberBindings.Add(Expression.Bind(memberInfo, Expression.TryCatch(dataFieldIsNull, Expression.Catch(typeof(Exception), Expression.Default(memberType))))); } } //Get the converter lambda expression, creating the initialization block with its parameters. Expression <Func <DataRow, TEntity> > converter = Expression.Lambda <Func <DataRow, TEntity> >( Expression.Block(parameters.Values, Expression.MemberInit(instantiate, memberBindings)), dataRow); //Use expression reducer to reduce the conversion lambda expression, then compile and return the delegate. converter = (Expression <Func <DataRow, TEntity> >) new ExpressionReducer().Visit(converter); value = converters.GetOrAdd(new HashSet <string>(columnNames), converter.Compile()); } return(value); }