Expression BuildExpression(int fieldIndex, ISqlExpression?sqlExpression) { Expression expr; if (SequenceHelper.UnwrapSubqueryContext(Sequence) is DefaultIfEmptyBuilder.DefaultIfEmptyContext defaultIfEmpty) { expr = Builder.BuildSql(_returnType, fieldIndex, sqlExpression); if (defaultIfEmpty.DefaultValue != null && expr is ConvertFromDataReaderExpression convert) { var generator = new ExpressionGenerator(); expr = convert.MakeNullable(); if (expr.Type.IsNullable()) { var exprVar = generator.AssignToVariable(expr, "nullable"); var defaultValue = defaultIfEmpty.DefaultValue; if (defaultValue.Type != expr.Type) { var convertLambda = Builder.MappingSchema.GenerateSafeConvert(defaultValue.Type, expr.Type); defaultValue = InternalExtensions.ApplyLambdaToExpression(convertLambda, defaultValue); } var resultVar = generator.AssignToVariable(defaultValue, "result"); generator.AddExpression(Expression.IfThen( Expression.NotEqual(exprVar, ExpressionInstances.UntypedNull), Expression.Assign(resultVar, Expression.Convert(exprVar, resultVar.Type)))); generator.AddExpression(resultVar); expr = generator.Build(); } } } else if (_methodName == "Sum" || _returnType.IsNullableType()) { expr = Builder.BuildSql(_returnType, fieldIndex, sqlExpression); } else { expr = Expression.Block( Expression.Call(null, MemberHelper.MethodOf(() => CheckNullValue(false, null !)), Expression.Call(ExpressionBuilder.DataReaderParam, Methods.ADONet.IsDBNull, ExpressionInstances.Constant0), Expression.Constant(_methodName)), Builder.BuildSql(_returnType, fieldIndex, sqlExpression)); } return(expr); }
Expression BuildExpression(int fieldIndex, ISqlExpression?sqlExpression) { Expression expr; if (Sequence is DefaultIfEmptyBuilder.DefaultIfEmptyContext defaultIfEmpty) { expr = Builder.BuildSql(_returnType, fieldIndex, sqlExpression); if (defaultIfEmpty.DefaultValue != null && expr is ConvertFromDataReaderExpression convert) { var generator = new ExpressionGenerator(); expr = convert.MakeNullable(); if (expr.Type.IsNullable()) { var exprVar = generator.AssignToVariable(expr, "nullable"); var resultVar = generator.AssignToVariable(defaultIfEmpty.DefaultValue, "result"); generator.AddExpression(Expression.IfThen( Expression.NotEqual(exprVar, Expression.Constant(null)), Expression.Assign(resultVar, Expression.Convert(exprVar, resultVar.Type)))); generator.AddExpression(resultVar); expr = generator.Build(); } } } else if (_returnType.IsClass || _methodName == "Sum" || _returnType.IsNullable()) { expr = Builder.BuildSql(_returnType, fieldIndex, sqlExpression); } else { expr = Expression.Block( Expression.Call(null, MemberHelper.MethodOf(() => CheckNullValue(false, null !)), Expression.Call(ExpressionBuilder.DataReaderParam, Methods.ADONet.IsDBNull, Expression.Constant(0)), Expression.Constant(_methodName)), Builder.BuildSql(_returnType, fieldIndex, sqlExpression)); } return(expr); }
private static OracleProviderAdapter CreateAdapter(string assemblyName, string clientNamespace, string typesNamespace, string?factoryName) { var isNative = false; #if NETFRAMEWORK isNative = assemblyName == NativeAssemblyName; #endif var assembly = Common.Tools.TryLoadAssembly(assemblyName, factoryName); if (assembly == null) { throw new InvalidOperationException($"Cannot load assembly {assemblyName}"); } var connectionType = assembly.GetType($"{clientNamespace}.OracleConnection", true) !; var parameterType = assembly.GetType($"{clientNamespace}.OracleParameter", true) !; var dataReaderType = assembly.GetType($"{clientNamespace}.OracleDataReader", true) !; var transactionType = assembly.GetType($"{clientNamespace}.OracleTransaction", true) !; var dbType = assembly.GetType($"{clientNamespace}.OracleDbType", true) !; var commandType = assembly.GetType($"{clientNamespace}.OracleCommand", true) !; var mappingSchema = new MappingSchema(); // do not set default conversion for BFile as it could be converted to file name, byte[], Stream and we don't know what user needs var oracleBFileType = loadType("OracleBFile", DataType.BFile, skipConvertExpression: true) !; var oracleBinaryType = loadType("OracleBinary", DataType.VarBinary) !; var oracleBlobType = loadType("OracleBlob", DataType.Blob) !; var oracleClobType = loadType("OracleClob", DataType.NText) !; var oracleDateType = loadType("OracleDate", DataType.DateTime) !; var oracleDecimalType = loadType("OracleDecimal", DataType.Decimal) !; var oracleIntervalDSType = loadType("OracleIntervalDS", DataType.Time) !; var oracleIntervalYMType = loadType("OracleIntervalYM", DataType.Date) !; var oracleStringType = loadType("OracleString", DataType.NVarChar) !; var oracleTimeStampType = loadType("OracleTimeStamp", DataType.DateTime2) !; var oracleTimeStampLTZType = loadType("OracleTimeStampLTZ", DataType.DateTimeOffset) !; var oracleTimeStampTZType = loadType("OracleTimeStampTZ", DataType.DateTimeOffset) !; var oracleXmlTypeType = loadType("OracleXmlType", DataType.Xml) !; var oracleXmlStreamType = loadType("OracleXmlStream", DataType.Xml, true, false) !; var oracleRefCursorType = loadType("OracleRefCursor", DataType.Binary, hasValue: false) !; var oracleRefType = loadType("OracleRef", DataType.Binary, true); BulkCopyAdapter?bulkCopy = null; var typeMapper = new TypeMapper(); typeMapper.RegisterTypeWrapper <OracleConnection>(connectionType); typeMapper.RegisterTypeWrapper <OracleParameter>(parameterType); typeMapper.RegisterTypeWrapper <OracleDbType>(dbType); typeMapper.RegisterTypeWrapper <OracleCommand>(commandType); typeMapper.RegisterTypeWrapper <OracleDataReader>(dataReaderType); typeMapper.RegisterTypeWrapper <OracleTimeStampTZ>(oracleTimeStampTZType); typeMapper.RegisterTypeWrapper <OracleTimeStampLTZ>(oracleTimeStampLTZType); typeMapper.RegisterTypeWrapper <OracleDecimal>(oracleDecimalType); if (isNative) { var bulkCopyType = assembly.GetType($"{clientNamespace}.OracleBulkCopy", true) !; var bulkCopyOptionsType = assembly.GetType($"{clientNamespace}.OracleBulkCopyOptions", true) !; var bulkRowsCopiedEventHandlerType = assembly.GetType($"{clientNamespace}.OracleRowsCopiedEventHandler", true) !; var bulkCopyColumnMappingType = assembly.GetType($"{clientNamespace}.OracleBulkCopyColumnMapping", true) !; var bulkCopyColumnMappingCollectionType = assembly.GetType($"{clientNamespace}.OracleBulkCopyColumnMappingCollection", true) !; var rowsCopiedEventArgsType = assembly.GetType($"{clientNamespace}.OracleRowsCopiedEventArgs", true) !; // bulk copy types typeMapper.RegisterTypeWrapper <OracleBulkCopy>(bulkCopyType); typeMapper.RegisterTypeWrapper <OracleBulkCopyOptions>(bulkCopyOptionsType); typeMapper.RegisterTypeWrapper <OracleRowsCopiedEventHandler>(bulkRowsCopiedEventHandlerType); typeMapper.RegisterTypeWrapper <OracleBulkCopyColumnMapping>(bulkCopyColumnMappingType); typeMapper.RegisterTypeWrapper <OracleBulkCopyColumnMappingCollection>(bulkCopyColumnMappingCollectionType); typeMapper.RegisterTypeWrapper <OracleRowsCopiedEventArgs>(rowsCopiedEventArgsType); typeMapper.FinalizeMappings(); bulkCopy = new BulkCopyAdapter( typeMapper.BuildWrappedFactory((IDbConnection connection, OracleBulkCopyOptions options) => new OracleBulkCopy((OracleConnection)connection, options)), typeMapper.BuildWrappedFactory((int source, string destination) => new OracleBulkCopyColumnMapping(source, destination))); } else { typeMapper.FinalizeMappings(); } var paramMapper = typeMapper.Type <OracleParameter>(); var dbTypeBuilder = paramMapper.Member(p => p.OracleDbType); var connectionMapper = typeMapper.Type <OracleConnection>(); var commandMapper = typeMapper.Type <OracleCommand>(); // data reader expressions // rd.GetOracleTimeStampTZ(i) => DateTimeOffset var generator = new ExpressionGenerator(typeMapper); var rdParam = Expression.Parameter(typeof(IDataReader), "rd"); var indexParam = Expression.Parameter(typeof(int), "i"); var tstzExpr = generator.MapExpression((IDataReader rd, int i) => ((OracleDataReader)rd).GetOracleTimeStampTZ(i), rdParam, indexParam); var tstzVariable = generator.AssignToVariable(tstzExpr, "tstz"); var expr = generator.MapExpression((OracleTimeStampTZ tstz) => new DateTimeOffset( tstz.Year, tstz.Month, tstz.Day, tstz.Hour, tstz.Minute, tstz.Second, tstz.GetTimeZoneOffset()).AddTicks(tstz.Nanosecond / NanosecondsPerTick), tstzVariable); generator.AddExpression(expr); var body = generator.Build(); var readDateTimeOffsetFromOracleTimeStampTZ = (Expression <Func <IDataReader, int, DateTimeOffset> >)Expression.Lambda(body, rdParam, indexParam); // rd.GetOracleTimeStampLTZ(i) => DateTimeOffset generator = new ExpressionGenerator(typeMapper); tstzExpr = generator.MapExpression((IDataReader rd, int i) => ((OracleDataReader)rd).GetOracleTimeStampLTZ(i).ToOracleTimeStampTZ(), rdParam, indexParam); tstzVariable = generator.AssignToVariable(tstzExpr, "tstz"); expr = generator.MapExpression((OracleTimeStampTZ tstz) => new DateTimeOffset( tstz.Year, tstz.Month, tstz.Day, tstz.Hour, tstz.Minute, tstz.Second, tstz.GetTimeZoneOffset()).AddTicks(tstz.Nanosecond / NanosecondsPerTick), tstzVariable); generator.AddExpression(expr); body = generator.Build(); var readDateTimeOffsetFromOracleTimeStampLTZ = (Expression <Func <IDataReader, int, DateTimeOffset> >)Expression.Lambda(body, rdParam, indexParam); // rd.GetOracleDecimal(i) => decimal generator = new ExpressionGenerator(typeMapper); var decExpr = generator.MapExpression((IDataReader rd, int i) => ((OracleDataReader)rd).GetOracleDecimal(i), rdParam, indexParam); var oracleDecimalVar = generator.AssignToVariable(decExpr, "dec"); var precision = generator.AssignToVariable(Expression.Constant(29), "precision"); var decimalVar = generator.AddVariable(Expression.Parameter(typeof(decimal), "dec")); var label = Expression.Label(typeof(decimal)); generator.AddExpression( Expression.Loop( Expression.TryCatch( Expression.Block( Expression.Assign(oracleDecimalVar, generator.MapExpression((OracleDecimal d, int p) => OracleDecimal.SetPrecision(d, p), oracleDecimalVar, precision)), Expression.Assign(decimalVar, Expression.Convert(oracleDecimalVar, typeof(decimal))), Expression.Break(label, decimalVar)), Expression.Catch( typeof(OverflowException), Expression.Block( Expression.IfThen( Expression.LessThanOrEqual(Expression.SubtractAssign(precision, Expression.Constant(1)), Expression.Constant(26)), Expression.Rethrow())))), label)); body = generator.Build(); var readOracleDecimalToDecimalAdv = (Expression <Func <IDataReader, int, decimal> >)Expression.Lambda(body, rdParam, indexParam); // workaround for mapper issue with complex reader expressions handling // https://github.com/linq2db/linq2db/issues/2032 var compiledReader = readOracleDecimalToDecimalAdv.Compile(); readOracleDecimalToDecimalAdv = (Expression <Func <IDataReader, int, decimal> >)Expression.Lambda( Expression.Invoke(Expression.Constant(compiledReader), rdParam, indexParam), rdParam, indexParam); var readOracleDecimalToInt = (Expression <Func <IDataReader, int, int> >)typeMapper.MapLambda <IDataReader, int, int>((rd, i) => (int)(decimal)OracleDecimal.SetPrecision(((OracleDataReader)rd).GetOracleDecimal(i), 27)); var readOracleDecimalToLong = (Expression <Func <IDataReader, int, long> >)typeMapper.MapLambda <IDataReader, int, long>((rd, i) => (long)(decimal)OracleDecimal.SetPrecision(((OracleDataReader)rd).GetOracleDecimal(i), 27)); var readOracleDecimalToDecimal = (Expression <Func <IDataReader, int, decimal> >)typeMapper.MapLambda <IDataReader, int, decimal>((rd, i) => (decimal)OracleDecimal.SetPrecision(((OracleDataReader)rd).GetOracleDecimal(i), 27)); return(new OracleProviderAdapter( connectionType, dataReaderType, parameterType, commandType, transactionType, mappingSchema, oracleBFileType, oracleBinaryType, oracleBlobType, oracleClobType, oracleDateType, oracleDecimalType, oracleIntervalDSType, oracleIntervalYMType, oracleStringType, oracleTimeStampType, oracleTimeStampLTZType, oracleTimeStampTZType, oracleXmlTypeType, oracleXmlStreamType, oracleRefCursorType, oracleRefType, typeMapper.BuildWrappedFactory((string connectionString) => new OracleConnection(connectionString)), typesNamespace, dbTypeBuilder.BuildSetter <IDbDataParameter>(), dbTypeBuilder.BuildGetter <IDbDataParameter>(), connectionMapper.Member(c => c.HostName).BuildGetter <IDbConnection>(), connectionMapper.Member(c => c.DatabaseName).BuildGetter <IDbConnection>(), commandMapper.Member(p => p.BindByName).BuildSetter <IDbCommand>(), commandMapper.Member(p => p.ArrayBindCount).BuildSetter <IDbCommand>(), commandMapper.Member(p => p.InitialLONGFetchSize).BuildSetter <IDbCommand>(), typeMapper.BuildFactory((DateTimeOffset dto, string offset) => new OracleTimeStampTZ(dto.Year, dto.Month, dto.Day, dto.Hour, dto.Minute, dto.Second, GetDateTimeOffsetNanoseconds(dto), offset)), readDateTimeOffsetFromOracleTimeStampTZ, readDateTimeOffsetFromOracleTimeStampLTZ, readOracleDecimalToDecimalAdv, readOracleDecimalToInt, readOracleDecimalToLong, readOracleDecimalToDecimal, bulkCopy)); Type?loadType(string typeName, DataType dataType, bool optional = false, bool hasNull = true, bool hasValue = true, bool skipConvertExpression = false) { var type = assembly !.GetType($"{typesNamespace}.{typeName}", !optional); if (type == null) { return(null); } if (hasNull) { // if native provider fails here, check that you have ODAC installed properly var getNullValue = Expression.Lambda <Func <object> >(Expression.Convert(ExpressionHelper.Field(type, "Null"), typeof(object))).Compile(); mappingSchema.AddScalarType(type, getNullValue(), true, dataType); } else { mappingSchema.AddScalarType(type, null, true, dataType); } if (skipConvertExpression) { return(type); } // conversion from provider-specific type var valueParam = Expression.Parameter(type); Expression memberExpression; if (!hasValue) { memberExpression = valueParam; } else { memberExpression = ExpressionHelper.Property(valueParam, "Value"); } var condition = Expression.Condition( Expression.Equal(valueParam, ExpressionHelper.Field(type, "Null")), Expression.Constant(null, typeof(object)), Expression.Convert(memberExpression, typeof(object))); var convertExpression = Expression.Lambda(condition, valueParam); mappingSchema.SetConvertExpression(type, typeof(object), convertExpression); return(type); } }
public void Setup() { var typeMapper = new TypeMapper(); typeMapper.RegisterTypeWrapper <Wrapped.OracleDataReader>(typeof(Original.OracleDataReader)); typeMapper.RegisterTypeWrapper <Wrapped.OracleTimeStampTZ>(typeof(Original.OracleTimeStampTZ)); typeMapper.RegisterTypeWrapper <Wrapped.OracleTimeStampLTZ>(typeof(Original.OracleTimeStampLTZ)); typeMapper.RegisterTypeWrapper <Wrapped.OracleDecimal>(typeof(Original.OracleDecimal)); typeMapper.FinalizeMappings(); // _readDateTimeOffsetFromOracleTimeStampTZ var generator = new ExpressionGenerator(typeMapper); var rdParam = Expression.Parameter(typeof(ITestDataReader), "rd"); var indexParam = Expression.Parameter(typeof(int), "i"); var tstzExpr = generator.MapExpression((ITestDataReader rd, int i) => ((Wrapped.OracleDataReader)rd).GetOracleTimeStampTZ(i), rdParam, indexParam); var tstzVariable = generator.AssignToVariable(tstzExpr, "tstz"); var expr = generator.MapExpression((Wrapped.OracleTimeStampTZ tstz) => new DateTimeOffset( tstz.Year, tstz.Month, tstz.Day, tstz.Hour, tstz.Minute, tstz.Second, tstz.GetTimeZoneOffset()).AddTicks(tstz.Nanosecond / NanosecondsPerTick), tstzVariable); generator.AddExpression(expr); var body = generator.Build(); _readDateTimeOffsetFromOracleTimeStampTZ = ((Expression <Func <ITestDataReader, int, DateTimeOffset> >)Expression.Lambda(body, rdParam, indexParam)).Compile(); // _readDateTimeOffsetFromOracleTimeStampLTZ generator = new ExpressionGenerator(typeMapper); tstzExpr = generator.MapExpression((ITestDataReader rd, int i) => ((Wrapped.OracleDataReader)rd).GetOracleTimeStampLTZ(i).ToOracleTimeStampTZ(), rdParam, indexParam); tstzVariable = generator.AssignToVariable(tstzExpr, "tstz"); expr = generator.MapExpression((Wrapped.OracleTimeStampTZ tstz) => new DateTimeOffset( tstz.Year, tstz.Month, tstz.Day, tstz.Hour, tstz.Minute, tstz.Second, tstz.GetTimeZoneOffset()).AddTicks(tstz.Nanosecond / NanosecondsPerTick), tstzVariable); generator.AddExpression(expr); body = generator.Build(); _readDateTimeOffsetFromOracleTimeStampLTZ = ((Expression <Func <ITestDataReader, int, DateTimeOffset> >)Expression.Lambda(body, rdParam, indexParam)).Compile(); // rd.GetOracleDecimal(i) => decimal generator = new ExpressionGenerator(typeMapper); var decExpr = generator.MapExpression((ITestDataReader rd, int i) => ((Wrapped.OracleDataReader)rd).GetOracleDecimal(i), rdParam, indexParam); var oracleDecimalVar = generator.AssignToVariable(decExpr, "dec"); var precision = generator.AssignToVariable(Expression.Constant(29), "precision"); var decimalVar = generator.AddVariable(Expression.Parameter(typeof(decimal), "dec")); var label = Expression.Label(typeof(decimal)); generator.AddExpression( Expression.Loop( Expression.TryCatch( Expression.Block( Expression.Assign(oracleDecimalVar, generator.MapExpression((Wrapped.OracleDecimal d, int p) => Wrapped.OracleDecimal.SetPrecision(d, p), oracleDecimalVar, precision)), Expression.Assign(decimalVar, Expression.Convert(oracleDecimalVar, typeof(decimal))), Expression.Break(label, decimalVar)), Expression.Catch( typeof(OverflowException), Expression.Block( Expression.IfThen( Expression.LessThanOrEqual(Expression.SubtractAssign(precision, Expression.Constant(1)), Expression.Constant(26)), Expression.Rethrow())))), label)); body = generator.Build(); var readOracleDecimalToDecimalAdv = (Expression <Func <ITestDataReader, int, decimal> >)Expression.Lambda(body, rdParam, indexParam); // workaround for mapper issue with complex reader expressions handling // https://github.com/linq2db/linq2db/issues/2032 var compiledReader = readOracleDecimalToDecimalAdv.Compile(); _readOracleDecimalToDecimalAdv = ((Expression <Func <ITestDataReader, int, decimal> >)Expression.Lambda( Expression.Invoke(Expression.Constant(compiledReader), rdParam, indexParam), rdParam, indexParam)).Compile(); _readOracleDecimalToInt = ((Expression <Func <ITestDataReader, int, int> >)typeMapper.MapLambda <ITestDataReader, int, int>((rd, i) => (int)(decimal)Wrapped.OracleDecimal.SetPrecision(((Wrapped.OracleDataReader)rd).GetOracleDecimal(i), 27))).Compile(); _readOracleDecimalToLong = ((Expression <Func <ITestDataReader, int, long> >)typeMapper.MapLambda <ITestDataReader, int, long>((rd, i) => (long)(decimal)Wrapped.OracleDecimal.SetPrecision(((Wrapped.OracleDataReader)rd).GetOracleDecimal(i), 27))).Compile(); _readOracleDecimalToDecimal = ((Expression <Func <ITestDataReader, int, decimal> >)typeMapper.MapLambda <ITestDataReader, int, decimal>((rd, i) => (decimal)Wrapped.OracleDecimal.SetPrecision(((Wrapped.OracleDataReader)rd).GetOracleDecimal(i), 27))).Compile(); }