/// <summary> /// Creates a new <see cref="TypeMaterializationInfo" /> instance. /// </summary> /// <param name="modelClrType"> The type that is needed in the model after conversion. </param> /// <param name="property"> The property associated with the type, or <c>null</c> if none. </param> /// <param name="typeMappingSource"> The type mapping source to use to find a mapping if the property does not have one already bound. </param> /// <param name="fromLeftOuterJoin"> Whether or not the value is coming from a LEFT OUTER JOIN operation. </param> /// <param name="index"> /// The index of the underlying result set that should be used for this type, /// or -1 if no index mapping is needed. /// </param> /// <param name="mapping"> The type mapping to use or <c>null</c> to infer one. </param> public TypeMaterializationInfo( [NotNull] Type modelClrType, [CanBeNull] IProperty property, [CanBeNull] IRelationalTypeMappingSource typeMappingSource, bool?fromLeftOuterJoin, int index = -1, [CanBeNull] RelationalTypeMapping mapping = null) { Check.NotNull(modelClrType, nameof(modelClrType)); if (mapping == null) { mapping = property?.FindRelationalMapping() ?? typeMappingSource?.GetMapping(modelClrType); } ProviderClrType = mapping?.Converter?.ProviderClrType ?? modelClrType; ModelClrType = modelClrType; Mapping = mapping; Property = property; Index = index; IsFromLeftOuterJoin = fromLeftOuterJoin; }
/// <summary> /// Gets the relational database type for a given object, throwing if no mapping is found. /// </summary> /// <param name="typeMappingSource">The type mapping source.</param> /// <param name="value">The object to get the mapping for.</param> /// <returns>The type mapping to be used.</returns> public static RelationalTypeMapping GetMappingForValue( this IRelationalTypeMappingSource typeMappingSource, object?value) => value == null || value == DBNull.Value ? RelationalTypeMapping.NullMapping : typeMappingSource.GetMapping(value.GetType());
/// <summary> /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public override void AddDbParameter(DbCommand command, object value) { Check.NotNull(command, nameof(command)); if (value == null) { command.Parameters .Add( _typeMappingSource.GetMappingForValue(null) .CreateParameter(command, Name, null)); return; } if (value is DbParameter dbParameter) { command.Parameters.Add(dbParameter); return; } var type = value.GetType(); command.Parameters.Add( _typeMappingSource.GetMapping(type) .CreateParameter(command, Name, value, type.IsNullableType())); }
/// <summary> /// Gets the relational database type for a given object, throwing if no mapping is found. /// </summary> /// <param name="typeMappingSource"> The type mapping source. </param> /// <param name="value"> The object to get the mapping for. </param> /// <returns> The type mapping to be used. </returns> public static RelationalTypeMapping GetMappingForValue( [CanBeNull] this IRelationalTypeMappingSource typeMappingSource, [CanBeNull] object value) => value == null || value == DBNull.Value || typeMappingSource == null ? RelationalTypeMapping.NullMapping : typeMappingSource.GetMapping(value.GetType());
protected override Expression VisitParameter(ParameterExpression parameterExpression) { if (!_predicateGenerating || parameterExpression?.Type != typeof(string)) { return(base.VisitParameter(parameterExpression)); } var parameterName = SqlGenerator.GenerateParameterName(parameterExpression.Name); if (Sql.ParameterBuilder.Parameters .All(p => p.InvariantName != parameterExpression.Name)) { var parameterType = parameterExpression.Type.UnwrapNullableType(); var typeMapping = TypeMappingSource.GetMapping(parameterType); if (ParameterValues.ContainsKey(parameterExpression.Name)) { var value = ParameterValues[parameterExpression.Name]; typeMapping = TypeMappingSource.GetMappingForValue(value); if (typeMapping == null || (!typeMapping.ClrType.UnwrapNullableType().IsAssignableFrom(parameterType) && (parameterType.IsEnum || !typeof(IConvertible).IsAssignableFrom(parameterType)))) { typeMapping = TypeMappingSource.GetMapping(parameterType); } } Sql.AddParameter( parameterExpression.Name, parameterName, typeMapping, parameterExpression.Type.IsNullableType()); } var parameterNamePlaceholder = SqlGenerator.GenerateParameterNamePlaceholder(parameterExpression.Name); AddLowerFunctionToSqlQuery(parameterNamePlaceholder); return(parameterExpression); }
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is TemporalTableExpression temporalTableExpression) { Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(temporalTableExpression.Name, temporalTableExpression.Schema)) .Append(" FOR SYSTEM_TIME "); switch (temporalTableExpression) { case TemporalAsOfTableExpression asOf: Sql.Append("AS OF ") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(asOf.PointInTime)); break; case TemporalFromToTableExpression fromTo: Sql.Append("FROM ") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(fromTo.From)) .Append(" TO ") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(fromTo.To)); break; case TemporalBetweenTableExpression between: Sql.Append("BETWEEN ") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(between.From)) .Append(" AND ") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(between.To)); break; case TemporalContainedInTableExpression containedIn: Sql.Append("CONTAINED IN (") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(containedIn.From)) .Append(", ") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(containedIn.To)) .Append(")"); break; case TemporalAllTableExpression: Sql.Append("ALL"); break; default: throw new InvalidOperationException(temporalTableExpression.Print()); } if (temporalTableExpression.Alias != null) { Sql.Append(AliasSeparator) .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(temporalTableExpression.Alias)); } return(temporalTableExpression); } return(base.VisitExtension(extensionExpression)); }
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> public virtual RawSqlCommand Build(string sql, IEnumerable <object> parameters) { var relationalCommandBuilder = _relationalCommandBuilderFactory.Create(); var substitutions = new List <string>(); var parameterNameGenerator = _parameterNameGeneratorFactory.Create(); var parameterValues = new Dictionary <string, object?>(); foreach (var parameter in parameters) { if (parameter is DbParameter dbParameter) { if (string.IsNullOrEmpty(dbParameter.ParameterName)) { dbParameter.ParameterName = _sqlGenerationHelper.GenerateParameterName(parameterNameGenerator.GenerateNext()); } substitutions.Add(_sqlGenerationHelper.GenerateParameterName(dbParameter.ParameterName)); relationalCommandBuilder.AddRawParameter(dbParameter.ParameterName, dbParameter); } else { var parameterName = parameterNameGenerator.GenerateNext(); var substitutedName = _sqlGenerationHelper.GenerateParameterName(parameterName); substitutions.Add(substitutedName); var typeMapping = parameter == null ? _typeMappingSource.GetMappingForValue(null) : _typeMappingSource.GetMapping(parameter.GetType()); var nullable = parameter == null || parameter.GetType().IsNullableType(); relationalCommandBuilder.AddParameter(parameterName, substitutedName, typeMapping, nullable); parameterValues.Add(parameterName, parameter); } } // ReSharper disable once CoVariantArrayConversion sql = string.Format(sql, substitutions.ToArray()); return(new RawSqlCommand( relationalCommandBuilder.Append(sql).Build(), parameterValues)); }
/// <summary> /// Creates a new <see cref="TypeMaterializationInfo" /> instance. /// </summary> /// <param name="modelClrType"> The type that is needed in the model after conversion. </param> /// <param name="property"> The property associated with the type, or <c>null</c> if none. </param> /// <param name="typeMappingSource"> The type mapper to use to find a mapping if the property does not have one already bound. </param> /// <param name="index"> /// The index of the underlying result set that should be used for this type, /// or -1 if no index mapping is needed. /// </param> public TypeMaterializationInfo( [NotNull] Type modelClrType, [CanBeNull] IProperty property, [CanBeNull] IRelationalTypeMappingSource typeMappingSource, int index = -1) { Check.NotNull(modelClrType, nameof(modelClrType)); var mapping = property?.FindRelationalMapping() ?? typeMappingSource?.GetMapping(modelClrType); ProviderClrType = mapping?.Converter?.ProviderClrType ?? modelClrType; ModelClrType = modelClrType; Mapping = mapping; Property = property; Index = index; }
/// <summary> /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public virtual TypeScaffoldingInfo FindMapping( string storeType, bool keyOrIndex, bool rowVersion) { // This is because certain providers can have no type specified as a default type e.g. SQLite Check.NotNull(storeType, nameof(storeType)); var mapping = _typeMappingSource.FindMapping(storeType); if (mapping == null) { return(null); } var canInfer = false; bool?scaffoldUnicode = null; bool?scaffoldFixedLength = null; int? scaffoldMaxLength = null; if (mapping.ClrType == typeof(byte[])) { // Check for inference var byteArrayMapping = _typeMappingSource.FindMapping( typeof(byte[]), null, keyOrIndex, rowVersion: rowVersion, size: mapping.Size, fixedLength: mapping.IsFixedLength); if (byteArrayMapping.StoreType.Equals(storeType, StringComparison.OrdinalIgnoreCase)) { canInfer = true; // Check for fixed-length var fixedLengthMapping = _typeMappingSource.FindMapping( typeof(byte[]), null, keyOrIndex, rowVersion: rowVersion, size: mapping.Size, fixedLength: true); scaffoldFixedLength = fixedLengthMapping.IsFixedLength != byteArrayMapping.IsFixedLength ? (bool?)byteArrayMapping.IsFixedLength : null; // Check for size var sizedMapping = _typeMappingSource.FindMapping( typeof(byte[]), null, keyOrIndex, rowVersion: rowVersion, fixedLength: mapping.IsFixedLength); scaffoldMaxLength = sizedMapping.Size != byteArrayMapping.Size ? byteArrayMapping.Size : null; } } else if (mapping.ClrType == typeof(string)) { // Check for inference var stringMapping = _typeMappingSource.FindMapping( typeof(string), null, keyOrIndex, unicode: mapping.IsUnicode, size: mapping.Size, fixedLength: mapping.IsFixedLength); if (stringMapping.StoreType.Equals(storeType, StringComparison.OrdinalIgnoreCase)) { canInfer = true; // Check for Unicode var unicodeMapping = _typeMappingSource.FindMapping( typeof(string), null, keyOrIndex, unicode: true, size: mapping.Size, fixedLength: mapping.IsFixedLength); scaffoldUnicode = unicodeMapping.IsUnicode != stringMapping.IsUnicode ? (bool?)stringMapping.IsUnicode : null; // Check for fixed-length var fixedLengthMapping = _typeMappingSource.FindMapping( typeof(string), null, keyOrIndex, unicode: mapping.IsUnicode, size: mapping.Size, fixedLength: true); scaffoldFixedLength = fixedLengthMapping.IsFixedLength != stringMapping.IsFixedLength ? (bool?)stringMapping.IsFixedLength : null; // Check for size var sizedMapping = _typeMappingSource.FindMapping( typeof(string), null, keyOrIndex, unicode: mapping.IsUnicode, fixedLength: mapping.IsFixedLength); scaffoldMaxLength = sizedMapping.Size != stringMapping.Size ? stringMapping.Size : null; } } else { var defaultMapping = _typeMappingSource.GetMapping(mapping.ClrType); if (defaultMapping.StoreType.Equals(storeType, StringComparison.OrdinalIgnoreCase)) { canInfer = true; } } return(new TypeScaffoldingInfo( mapping.ClrType, canInfer, scaffoldUnicode, scaffoldMaxLength, scaffoldFixedLength)); }
public virtual SqlExpression Translate( SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments, IDiagnosticsLogger <DbLoggerCategory.Query> logger) { if (_indexOfMethodInfo.Equals(method)) { return(new MySqlStringComparisonMethodTranslator(_sqlExpressionFactory, _options) .MakeIndexOfExpression(instance, arguments[0])); } if (_replaceMethodInfo.Equals(method)) { var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, arguments[0], arguments[1]); var replacementArgument = _sqlExpressionFactory.ApplyTypeMapping(arguments[1], stringTypeMapping); var replaceCall = _sqlExpressionFactory.NullableFunction( "REPLACE", new[] { _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(arguments[0], stringTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(arguments[1], stringTypeMapping) }, method.ReturnType, stringTypeMapping); // Due to a bug in all versions of MariaDB and all MySQL versions below 8.0.x (exact version that fixed the issue is // currently unclear), using `null` as the replacement argument in a REPLACE() call leads to unexpected results, in which // the call returns the original string, instead of `null`. // See https://jira.mariadb.org/browse/MDEV-24263 return(_sqlExpressionFactory.Case( new[] { new CaseWhenClause( _sqlExpressionFactory.IsNotNull(replacementArgument), replaceCall) }, _sqlExpressionFactory.Constant(null, RelationalTypeMapping.NullMapping))); } if (_toLowerMethodInfo.Equals(method) || _toUpperMethodInfo.Equals(method)) { return(_sqlExpressionFactory.NullableFunction( _toLowerMethodInfo.Equals(method) ? "LOWER" : "UPPER", new[] { instance }, method.ReturnType, instance.TypeMapping)); } if (_substringMethodInfoWithOneArg.Equals(method)) { return(_sqlExpressionFactory.Function( "SUBSTRING", new[] { instance, _sqlExpressionFactory.Add( arguments[0], _sqlExpressionFactory.Constant(1)), _sqlExpressionFactory.NullableFunction( "CHAR_LENGTH", new[] { instance }, typeof(int)) }, nullable: true, argumentsPropagateNullability: new[] { true, true, true }, method.ReturnType, instance.TypeMapping)); } if (_substringMethodInfoWithTwoArgs.Equals(method)) { return(_sqlExpressionFactory.NullableFunction( "SUBSTRING", new[] { instance, _sqlExpressionFactory.Add( arguments[0], _sqlExpressionFactory.Constant(1)), arguments[1] }, method.ReturnType, instance.TypeMapping)); } if (_isNullOrWhiteSpaceMethodInfo.Equals(method)) { return(_sqlExpressionFactory.OrElse( _sqlExpressionFactory.IsNull(arguments[0]), _sqlExpressionFactory.Equal( ProcessTrimMethod(arguments[0], null, null), _sqlExpressionFactory.Constant(string.Empty)))); } if (_trimStartMethodInfoWithoutArgs?.Equals(method) == true || _trimStartMethodInfoWithCharArg?.Equals(method) == true || _trimStartMethodInfoWithCharArrayArg.Equals(method)) { return(ProcessTrimMethod(instance, arguments.Count > 0 ? arguments[0] : null, "LEADING")); } if (_trimEndMethodInfoWithoutArgs?.Equals(method) == true || _trimEndMethodInfoWithCharArg?.Equals(method) == true || _trimEndMethodInfoWithCharArrayArg.Equals(method)) { return(ProcessTrimMethod(instance, arguments.Count > 0 ? arguments[0] : null, "TRAILING")); } if (_trimMethodInfoWithoutArgs?.Equals(method) == true || _trimMethodInfoWithCharArg?.Equals(method) == true || _trimMethodInfoWithCharArrayArg.Equals(method)) { return(ProcessTrimMethod(instance, arguments.Count > 0 ? arguments[0] : null, null)); } if (_containsMethodInfo.Equals(method)) { return(new MySqlStringComparisonMethodTranslator(_sqlExpressionFactory, _options) .MakeContainsExpression(instance, arguments[0])); } if (_startsWithMethodInfo.Equals(method)) { return(new MySqlStringComparisonMethodTranslator(_sqlExpressionFactory, _options) .MakeStartsWithExpression(instance, arguments[0])); } if (_endsWithMethodInfo.Equals(method)) { return(new MySqlStringComparisonMethodTranslator(_sqlExpressionFactory, _options) .MakeEndsWithExpression(instance, arguments[0])); } if (_padLeftWithOneArg.Equals(method)) { return(TranslatePadLeftRight( true, instance, arguments[0], _sqlExpressionFactory.Constant(" "), method.ReturnType)); } if (_padRightWithOneArg.Equals(method)) { return(TranslatePadLeftRight( false, instance, arguments[0], _sqlExpressionFactory.Constant(" "), method.ReturnType)); } if (_padLeftWithTwoArgs.Equals(method)) { return(TranslatePadLeftRight( true, instance, arguments[0], arguments[1], method.ReturnType)); } if (_padRightWithTwoArgs.Equals(method)) { return(TranslatePadLeftRight( false, instance, arguments[0], arguments[1], method.ReturnType)); } if (_firstOrDefaultMethodInfoWithoutArgs.Equals(method)) { return(_sqlExpressionFactory.NullableFunction( "SUBSTRING", new[] { arguments[0], _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1) }, method.ReturnType)); } if (_lastOrDefaultMethodInfoWithoutArgs.Equals(method)) { return(_sqlExpressionFactory.NullableFunction( "SUBSTRING", new[] { arguments[0], _sqlExpressionFactory.NullableFunction( "CHAR_LENGTH", new[] { arguments[0] }, typeof(int)), _sqlExpressionFactory.Constant(1) }, method.ReturnType)); } if (_removeMethodInfoWithOneArg.Equals(method)) { return(_sqlExpressionFactory.NullableFunction( "SUBSTRING", new[] { instance, _sqlExpressionFactory.Constant(1), arguments[0], }, method.ReturnType, instance.TypeMapping)); } if (_removeMethodInfoWithTwoArgs.Equals(method)) { var firstSubString = _sqlExpressionFactory.NullableFunction( "SUBSTRING", new[] { instance, _sqlExpressionFactory.Constant(1), arguments[0] }, method.ReturnType, instance.TypeMapping); var secondSubString = _sqlExpressionFactory.NullableFunction( "SUBSTRING", new[] { instance, _sqlExpressionFactory.Add( _sqlExpressionFactory.Add( arguments[0], arguments[1]), _sqlExpressionFactory.Constant(1)), _sqlExpressionFactory.Subtract( _sqlExpressionFactory.NullableFunction( "CHAR_LENGTH", new[] { instance }, typeof(int)), _sqlExpressionFactory.Add( arguments[0], arguments[1])), }, method.ReturnType, instance.TypeMapping); var concat = _sqlExpressionFactory.NullableFunction( "CONCAT", new[] { firstSubString, secondSubString }, method.ReturnType, instance.TypeMapping); return(concat); } if (_concatMethodInfos.Contains( (method.IsGenericMethod ? method.GetGenericMethodDefinition() : null) ?? method)) { // Handle // string[] // IEnumerable<string> // object[] // IEnumerable<T> // and // string, ... // object, ... // // Some call signature variants can never reach this code, because they will be directly called and thus only their result // is translated. var concatArguments = arguments[0] is MySqlComplexFunctionArgumentExpression mySqlComplexFunctionArgumentExpression ? new SqlExpression[] { mySqlComplexFunctionArgumentExpression } : arguments.Select( e => e switch { SqlConstantExpression c => _sqlExpressionFactory.Constant(c.Value.ToString()), SqlParameterExpression p => p.ApplyTypeMapping( ((MySqlStringTypeMapping)_typeMappingSource.GetMapping(typeof(string))).Clone(forceToString: true)), _ => e, })
/// <summary> /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// </summary> protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is TableExpression tableExpression && tableExpression.FindAnnotation(SqlServerAnnotationNames.TemporalOperationType) != null) { Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableExpression.Name, tableExpression.Schema)) .Append(" FOR SYSTEM_TIME "); var temporalOperationType = (TemporalOperationType)tableExpression[SqlServerAnnotationNames.TemporalOperationType] !; switch (temporalOperationType) { case TemporalOperationType.All: Sql.Append("ALL"); break; case TemporalOperationType.AsOf: var pointInTime = (DateTime)tableExpression[SqlServerAnnotationNames.TemporalAsOfPointInTime] !; Sql.Append("AS OF ") .Append(_typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral(pointInTime)); break; case TemporalOperationType.Between: case TemporalOperationType.ContainedIn: case TemporalOperationType.FromTo: var from = _typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral( (DateTime)tableExpression[SqlServerAnnotationNames.TemporalRangeOperationFrom] !); var to = _typeMappingSource.GetMapping(typeof(DateTime)).GenerateSqlLiteral( (DateTime)tableExpression[SqlServerAnnotationNames.TemporalRangeOperationTo] !); switch (temporalOperationType) { case TemporalOperationType.FromTo: Sql.Append($"FROM {from} TO {to}"); break; case TemporalOperationType.Between: Sql.Append($"BETWEEN {from} AND {to}"); break; case TemporalOperationType.ContainedIn: Sql.Append($"CONTAINED IN ({from}, {to})"); break; default: throw new InvalidOperationException(tableExpression.Print()); } break; default: throw new InvalidOperationException(tableExpression.Print()); } if (tableExpression.Alias != null) { Sql.Append(AliasSeparator) .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableExpression.Alias)); } return(tableExpression); } return(base.VisitExtension(extensionExpression)); }