protected virtual void ContributeParameter(IDbCommand command, ITypeHandlerRegistry typeHandlers, string name, object value, DocumentMap mapping = null) { if (value == null) { command.Parameters.Add(new SqlParameter(name, DBNull.Value)); return; } var typeHandler = typeHandlers.Resolve(value.GetType()); if (typeHandler != null) { var p = new SqlParameter(name, SqlDbType.NVarChar); typeHandler.WriteDatabase(p, value); command.Parameters.Add(p); return; } if (value is TableValuedParameter tvp && command is SqlCommand sqlCommand) { var p = sqlCommand.Parameters.Add(name, SqlDbType.Structured); p.Value = tvp.DataRecords; p.TypeName = tvp.TypeName; return; } if (value is IEnumerable && (value is string) == false && (value is byte[]) == false) { var inClauseNames = new List <string>(); var i = 0; var inClauseValues = ((IEnumerable)value).Cast <object>().ToList(); ListExtender.ExtendListRepeatingLastValue(inClauseValues); foreach (var inClauseValue in inClauseValues) { i++; var inClauseName = name + "_" + i; inClauseNames.Add(inClauseName); ContributeParameter(command, typeHandlers, inClauseName, inClauseValue); } if (i == 0) { var inClauseName = name + "_" + i; inClauseNames.Add(inClauseName); ContributeParameter(command, typeHandlers, inClauseName, null); } var originalParameter = Regex.Escape("@" + name.TrimStart('@')) + @"(?=[^\w\$@#_]|$)"; var replacementParameters = "(" + string.Join(", ", inClauseNames.Select(x => "@" + x)) + ")"; command.CommandText = Regex.Replace(command.CommandText, originalParameter, match => replacementParameters, RegexOptions.IgnoreCase); return; } var columnType = DatabaseTypeConverter.AsDbType(value.GetType()); if (columnType == null) { throw new InvalidOperationException($"Cannot map type '{value.GetType().FullName}' to a DbType. Consider providing a custom ITypeHandler."); } var param = new SqlParameter(); param.ParameterName = name; param.DbType = columnType.Value; param.Value = value; if (columnType == DbType.String && value is string text) { var size = GetBestSizeBucket(text); if (size > 0) { param.Size = size; } } // To assist SQL's query plan caching, assign a parameter size for our // common id lookups where possible. if (mapping != null && mapping.IdColumn != null && mapping.IdColumn.MaxLength > 0 && columnType == DbType.String && string.Equals(name, mapping.IdColumn.ColumnName, StringComparison.OrdinalIgnoreCase)) { if (mapping.IdColumn.MaxLength != null) { param.Size = mapping.IdColumn.MaxLength.Value; } } if (columnType == DbType.String && mapping != null) { var indexed = mapping.WritableIndexedColumns(); var columnMap = indexed.FirstOrDefault(i => string.Equals(i.ColumnName, param.ParameterName, StringComparison.OrdinalIgnoreCase)); if (columnMap != null && columnMap.MaxLength != null) { param.Size = columnMap.MaxLength.Value; } } command.Parameters.Add(param); }
public static Expression GetValueFromReaderAsType(Expression reader, Expression index, Type propertyType, ITypeHandlerRegistry customTypeHandlerRegistry) { const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; // For value types where we know they cannot be null // E.g., foo.Age = reader.GetInt32(i) var neverNull = new Func <string, Expression>(nameOfGetMethod => Expression.Call(reader, typeof(IDataRecord).GetMethod(nameOfGetMethod, bindingFlags), index)); // For reference types // E.g., foo.Name = reader.IsDBNull(i) ? null : reader.GetString(i); var maybeNull = new Func <string, Expression>(nameOfGetMethod => Expression.Condition( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.IsDBNull), bindingFlags), index), Expression.Constant(null, propertyType), Expression.Call(reader, typeof(IDataRecord).GetMethod(nameOfGetMethod, bindingFlags), index))); // For nullable types (whether the method return is slightly different, and needs to be cast) // E.g., foo.LuckyNumber = reader.IsDBNull(i) ? null : (int?)reader.GetInt32(i); var maybeNullWithCast = new Func <string, Expression>(nameOfGetMethod => Expression.Condition( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.IsDBNull), bindingFlags), index), Expression.Constant(null, propertyType), Expression.Convert( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameOfGetMethod, bindingFlags), index), propertyType))); // Optimize for common types if (propertyType == typeof(string)) { return(maybeNull(nameof(IDataRecord.GetString))); } if (propertyType == typeof(DateTime)) { return(neverNull(nameof(IDataRecord.GetDateTime))); } if (propertyType == typeof(DateTime?)) { return(maybeNullWithCast(nameof(IDataRecord.GetDateTime))); } if (propertyType == typeof(int)) { return(neverNull(nameof(IDataRecord.GetInt32))); } if (propertyType == typeof(int?)) { return(maybeNullWithCast(nameof(IDataRecord.GetInt32))); } if (propertyType == typeof(decimal)) { return(neverNull(nameof(IDataRecord.GetDecimal))); } if (propertyType == typeof(decimal?)) { return(maybeNullWithCast(nameof(IDataRecord.GetDecimal))); } if (propertyType == typeof(char)) { return(neverNull(nameof(IDataRecord.GetChar))); } if (propertyType == typeof(char?)) { return(maybeNullWithCast(nameof(IDataRecord.GetChar))); } if (propertyType == typeof(long)) { return(neverNull(nameof(IDataRecord.GetInt64))); } if (propertyType == typeof(long?)) { return(maybeNullWithCast(nameof(IDataRecord.GetInt64))); } if (propertyType == typeof(short)) { return(neverNull(nameof(IDataRecord.GetInt16))); } if (propertyType == typeof(short?)) { return(maybeNullWithCast(nameof(IDataRecord.GetInt16))); } if (propertyType == typeof(Guid)) { return(neverNull(nameof(IDataRecord.GetGuid))); } if (propertyType == typeof(Guid?)) { return(maybeNullWithCast(nameof(IDataRecord.GetGuid))); } if (propertyType == typeof(float)) { return(neverNull(nameof(IDataRecord.GetFloat))); } if (propertyType == typeof(float?)) { return(maybeNullWithCast(nameof(IDataRecord.GetFloat))); } // Enum or Nullable<Enum> if (propertyType.IsEnum || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable <>) && propertyType.GetGenericArguments()[0].IsEnum)) { var underlyingEnumType = propertyType.IsEnum ? propertyType : propertyType.GetGenericArguments()[0]; // reader.IsDBNull(0) // ? default(TEnum?) // : (TEnum?)(reader.GetFieldType(0) == typeof(string) // ? (Enum.Parse<TEnum>(reader.GetString(0))) // : ((TEnum)reader.GetValue(0))); return(Expression.Condition( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.IsDBNull), bindingFlags), index), Expression.Default(propertyType), Expression.Convert( Expression.Condition( Expression.Equal( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.GetFieldType), bindingFlags), index), Expression.Constant(typeof(string), typeof(Type))), Expression.Call(null, typeof(Enum).GetMethods(BindingFlags.Static | BindingFlags.Public).Single(m => m.Name == "Parse" && m.IsGenericMethod && m.GetParameters().Length == 2).MakeGenericMethod(underlyingEnumType), Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.GetString), bindingFlags), index), Expression.Constant(true) // ignoreCase ), Expression.Convert( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.GetValue), bindingFlags), index), underlyingEnumType)), propertyType ))); } // We allow custom type handlers, but not for the primitive types shown above - we deal with so many // of these that we want them to be fast! var typeHandler = customTypeHandlerRegistry.Resolve(propertyType); if (typeHandler != null) { var handler = Expression.Constant(typeHandler, typeof(ITypeHandler)); return(Expression.Convert(Expression.Call(handler, typeof(ITypeHandler).GetMethod(nameof(ITypeHandler.ReadDatabase), bindingFlags), reader, index), propertyType)); } // Fallback: // E.g., foo.SomeValue = reader.IsDBNull(i) ? default(float?) : (float?) reader[i]; return(Expression.Condition( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.IsDBNull), bindingFlags), index), Expression.Default(propertyType), Expression.Convert( Expression.Call(reader, typeof(IDataRecord).GetMethod(nameof(IDataRecord.GetValue), bindingFlags), index), propertyType))); }