From 944864f88521aaae9d22a08f2209aae64c0a006f Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 26 Aug 2020 13:37:07 -0700 Subject: [PATCH] Throw detailed exception message in buffered data reader (#22233) Resolves #20364 Adds code for error handling when create buffered data reader. Code needs to handle unexpected nulls and incorrect type of data encountered --- .../Properties/RelationalStrings.Designer.cs | 6 + .../Properties/RelationalStrings.resx | 3 + .../Query/Internal/BufferedDataReader.cs | 467 ++++++++++++++---- .../Internal/FromSqlQueryingEnumerable.cs | 64 +-- .../Internal/SingleQueryingEnumerable.cs | 64 +-- .../Query/Internal/SplitQueryingEnumerable.cs | 50 +- ...sitor.ShaperProcessingExpressionVisitor.cs | 84 ++-- ...alShapedQueryCompilingExpressionVisitor.cs | 9 +- src/EFCore.Relational/Storage/ReaderColumn.cs | 44 +- .../Storage/ReaderColumn`.cs | 20 +- .../Storage/RelationalCommand.cs | 6 +- .../RelationalCommandParameterObject.cs | 32 ++ .../Query/FromSqlQueryTestBase.cs | 6 +- .../Query/Internal/BufferedDataReaderTest.cs | 18 +- .../Query/FromSqlQuerySqlServerTest.cs | 48 -- .../Query/QueryBugsTest.cs | 7 +- 16 files changed, 662 insertions(+), 266 deletions(-) diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 4587186a00a..e4400ca231b 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -497,6 +497,12 @@ public static string FunctionOverrideMismatch([CanBeNull] object propertySpecifi GetString("FunctionOverrideMismatch", nameof(propertySpecification), nameof(function)), propertySpecification, function); + /// + /// Data is Null. This method or property cannot be called on Null values. + /// + public static string GetXMethodOnNullData + => GetString("GetXMethodOnNullData"); + /// /// Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and the comment '{comment}' does not match the comment '{otherComment}'. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 1704e062e8f..b0c1399309c 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -301,6 +301,9 @@ The property '{propertySpecification}' has specific configuration for the function '{function}', however it isn't mapped to a column on that function return. Remove the specific configuration or map an entity type that contains this property to '{function}'. + + Data is Null. This method or property cannot be called on Null values. + Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and the comment '{comment}' does not match the comment '{otherComment}'. diff --git a/src/EFCore.Relational/Query/Internal/BufferedDataReader.cs b/src/EFCore.Relational/Query/Internal/BufferedDataReader.cs index 3c028eaf7bd..6f0cdfb9090 100644 --- a/src/EFCore.Relational/Query/Internal/BufferedDataReader.cs +++ b/src/EFCore.Relational/Query/Internal/BufferedDataReader.cs @@ -8,6 +8,7 @@ using System.Data.Common; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -26,6 +27,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal public class BufferedDataReader : DbDataReader { private DbDataReader _underlyingReader; + private readonly bool _detailedErrorsEnabled; private List _bufferedDataRecords = new List(); private BufferedDataRecord _currentResultSet; private int _currentResultSetNumber; @@ -39,9 +41,10 @@ public class BufferedDataReader : DbDataReader /// 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. /// - public BufferedDataReader([NotNull] DbDataReader reader) + public BufferedDataReader([NotNull] DbDataReader reader, bool detailedErrorsEnabled) { _underlyingReader = reader; + _detailedErrorsEnabled = detailedErrorsEnabled; } /// @@ -173,7 +176,7 @@ public virtual BufferedDataReader Initialize([NotNull] IReadOnlyList InitializeAsync( do { _bufferedDataRecords.Add( - await new BufferedDataRecord().InitializeAsync(_underlyingReader, columns, cancellationToken) + await new BufferedDataRecord(_detailedErrorsEnabled).InitializeAsync(_underlyingReader, columns, cancellationToken) .ConfigureAwait(false)); } while (await _underlyingReader.NextResultAsync(cancellationToken).ConfigureAwait(false)); @@ -705,6 +708,12 @@ private sealed class BufferedDataRecord private DbDataReader _underlyingReader; private IReadOnlyList _columns; private int[] _indexMap; + private readonly bool _detailedErrorsEnabled; + + public BufferedDataRecord(bool detailedErrorsEnabled) + { + _detailedErrorsEnabled = detailedErrorsEnabled; + } public bool IsDataReady { get; private set; } @@ -809,66 +818,49 @@ public ulong GetUInt64(int ordinal) public object GetValue(int ordinal) => GetFieldValue(ordinal); +#pragma warning disable IDE0060 // Remove unused parameter public int GetValues(object[] values) +#pragma warning restore IDE0060 // Remove unused parameter => throw new NotSupportedException(); public T GetFieldValue(int ordinal) - { - switch (_columnTypeCases[ordinal]) + => (_columnTypeCases[ordinal]) switch { - case TypeCase.Bool: - return (T)(object)GetBoolean(ordinal); - case TypeCase.Byte: - return (T)(object)GetByte(ordinal); - case TypeCase.Char: - return (T)(object)GetChar(ordinal); - case TypeCase.DateTime: - return (T)(object)GetDateTime(ordinal); - case TypeCase.DateTimeOffset: - return (T)(object)GetDateTimeOffset(ordinal); - case TypeCase.Decimal: - return (T)(object)GetDecimal(ordinal); - case TypeCase.Double: - return (T)(object)GetDouble(ordinal); - case TypeCase.Float: - return (T)(object)GetFloat(ordinal); - case TypeCase.Guid: - return (T)(object)GetGuid(ordinal); - case TypeCase.Short: - return (T)(object)GetInt16(ordinal); - case TypeCase.Int: - return (T)(object)GetInt32(ordinal); - case TypeCase.Long: - return (T)(object)GetInt64(ordinal); - case TypeCase.SByte: - return (T)(object)GetSByte(ordinal); - case TypeCase.UShort: - return (T)(object)GetUInt16(ordinal); - case TypeCase.UInt: - return (T)(object)GetUInt32(ordinal); - case TypeCase.ULong: - return (T)(object)GetUInt64(ordinal); - case TypeCase.Empty: - return default; - default: - return (T)_objects[_currentRowNumber * _objectCount + _ordinalToIndexMap[ordinal]]; - } - } - - public Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) - => Task.FromResult(GetFieldValue(ordinal)); + TypeCase.Bool => (T)(object)GetBoolean(ordinal), + TypeCase.Byte => (T)(object)GetByte(ordinal), + TypeCase.Char => (T)(object)GetChar(ordinal), + TypeCase.DateTime => (T)(object)GetDateTime(ordinal), + TypeCase.DateTimeOffset => (T)(object)GetDateTimeOffset(ordinal), + TypeCase.Decimal => (T)(object)GetDecimal(ordinal), + TypeCase.Double => (T)(object)GetDouble(ordinal), + TypeCase.Float => (T)(object)GetFloat(ordinal), + TypeCase.Guid => (T)(object)GetGuid(ordinal), + TypeCase.Short => (T)(object)GetInt16(ordinal), + TypeCase.Int => (T)(object)GetInt32(ordinal), + TypeCase.Long => (T)(object)GetInt64(ordinal), + TypeCase.SByte => (T)(object)GetSByte(ordinal), + TypeCase.UShort => (T)(object)GetUInt16(ordinal), + TypeCase.UInt => (T)(object)GetUInt32(ordinal), + TypeCase.ULong => (T)(object)GetUInt64(ordinal), + _ => (T)_objects[_currentRowNumber * _objectCount + _ordinalToIndexMap[ordinal]], + }; public bool IsDBNull(int ordinal) => _nulls[_currentRowNumber * _nullCount + _nullOrdinalToIndexMap[ordinal]]; - public Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) - => Task.FromResult(IsDBNull(ordinal)); - public bool Read() => IsDataReady = ++_currentRowNumber < _rowCount; +#pragma warning disable IDE0060 // Remove unused parameter + public Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) + => Task.FromResult(GetFieldValue(ordinal)); + + public Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) + => Task.FromResult(IsDBNull(ordinal)); + public Task ReadAsync(CancellationToken cancellationToken) => Task.FromResult(Read()); +#pragma warning restore IDE0060 // Remove unused parameter public BufferedDataRecord Initialize([NotNull] DbDataReader reader, [NotNull] IReadOnlyList columns) { @@ -1217,6 +1209,15 @@ private void InitializeFields() var fieldCount = FieldCount; if (FieldCount < _columns.Count) { + if (_columns.Count > 0 + && _columns[0].Name != null) + { + // Non-composed FromSql + var missingColumns = _columns.Select(c => c.Name).Except(_columnNames); + + throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(missingColumns.First())); + } + throw new InvalidOperationException(RelationalStrings.TooFewReaderFields); } @@ -1479,104 +1480,360 @@ private void DoubleBufferCapacity() private void ReadBool(DbDataReader reader, int ordinal, ReaderColumn column) { - _tempBools[_currentRowNumber * _boolCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _tempBools[_currentRowNumber * _boolCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _tempBools[_currentRowNumber * _boolCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadByte(DbDataReader reader, int ordinal, ReaderColumn column) { - _bytes[_currentRowNumber * _byteCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _bytes[_currentRowNumber * _byteCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _bytes[_currentRowNumber * _byteCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + } private void ReadChar(DbDataReader reader, int ordinal, ReaderColumn column) { - _chars[_currentRowNumber * _charCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _chars[_currentRowNumber * _charCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _chars[_currentRowNumber * _charCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadDateTime(DbDataReader reader, int ordinal, ReaderColumn column) { - _dateTimes[_currentRowNumber * _dateTimeCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _dateTimes[_currentRowNumber * _dateTimeCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _dateTimes[_currentRowNumber * _dateTimeCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadDateTimeOffset(DbDataReader reader, int ordinal, ReaderColumn column) { - _dateTimeOffsets[_currentRowNumber * _dateTimeOffsetCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _dateTimeOffsets[_currentRowNumber * _dateTimeOffsetCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _dateTimeOffsets[_currentRowNumber * _dateTimeOffsetCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadDecimal(DbDataReader reader, int ordinal, ReaderColumn column) { - _decimals[_currentRowNumber * _decimalCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _decimals[_currentRowNumber * _decimalCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _decimals[_currentRowNumber * _decimalCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadDouble(DbDataReader reader, int ordinal, ReaderColumn column) { - _doubles[_currentRowNumber * _doubleCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _doubles[_currentRowNumber * _doubleCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _doubles[_currentRowNumber * _doubleCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadFloat(DbDataReader reader, int ordinal, ReaderColumn column) { - _floats[_currentRowNumber * _floatCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _floats[_currentRowNumber * _floatCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _floats[_currentRowNumber * _floatCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadGuid(DbDataReader reader, int ordinal, ReaderColumn column) { - _guids[_currentRowNumber * _guidCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _guids[_currentRowNumber * _guidCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _guids[_currentRowNumber * _guidCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadShort(DbDataReader reader, int ordinal, ReaderColumn column) { - _shorts[_currentRowNumber * _shortCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _shorts[_currentRowNumber * _shortCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _shorts[_currentRowNumber * _shortCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadInt(DbDataReader reader, int ordinal, ReaderColumn column) { - _ints[_currentRowNumber * _intCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _ints[_currentRowNumber * _intCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _ints[_currentRowNumber * _intCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadLong(DbDataReader reader, int ordinal, ReaderColumn column) { - _longs[_currentRowNumber * _longCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _longs[_currentRowNumber * _longCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _longs[_currentRowNumber * _longCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadSByte(DbDataReader reader, int ordinal, ReaderColumn column) { - _sbytes[_currentRowNumber * _sbyteCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _sbytes[_currentRowNumber * _sbyteCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _sbytes[_currentRowNumber * _sbyteCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadUShort(DbDataReader reader, int ordinal, ReaderColumn column) { - _ushorts[_currentRowNumber * _ushortCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _ushorts[_currentRowNumber * _ushortCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _ushorts[_currentRowNumber * _ushortCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadUInt(DbDataReader reader, int ordinal, ReaderColumn column) { - _uints[_currentRowNumber * _uintCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _uints[_currentRowNumber * _uintCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _uints[_currentRowNumber * _uintCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadULong(DbDataReader reader, int ordinal, ReaderColumn column) { - _ulongs[_currentRowNumber * _ulongCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _ulongs[_currentRowNumber * _ulongCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _ulongs[_currentRowNumber * _ulongCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private void ReadObject(DbDataReader reader, int ordinal, ReaderColumn column) { - _objects[_currentRowNumber * _objectCount + _ordinalToIndexMap[ordinal]] = - ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + if (_detailedErrorsEnabled) + { + try + { + _objects[_currentRowNumber * _objectCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } + catch (Exception e) + { + ThrowReadValueException(e, reader, ordinal, column); + } + } + else + { + _objects[_currentRowNumber * _objectCount + _ordinalToIndexMap[ordinal]] = + ((ReaderColumn)column).GetFieldValue(reader, _indexMap); + } } private enum TypeCase @@ -1600,6 +1857,50 @@ private enum TypeCase ULong, UShort } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ThrowReadValueException( + Exception exception, + DbDataReader reader, + int ordinal, + ReaderColumn column) + { + var value = reader.GetFieldValue(ordinal); + var property = column.Property; + var expectedType = column.Type.MakeNullable(column.IsNullable); + + var actualType = value?.GetType(); + + string message; + + if (property != null) + { + var entityType = property.DeclaringType.DisplayName(); + var propertyName = property.Name; + if (expectedType == typeof(object)) + { + expectedType = property.ClrType; + } + + message = exception is NullReferenceException + || Equals(value, DBNull.Value) + ? CoreStrings.ErrorMaterializingPropertyNullReference(entityType, propertyName, expectedType) + : exception is InvalidCastException + ? CoreStrings.ErrorMaterializingPropertyInvalidCast(entityType, propertyName, expectedType, actualType) + : CoreStrings.ErrorMaterializingProperty(entityType, propertyName); + } + else + { + message = exception is NullReferenceException + || Equals(value, DBNull.Value) + ? CoreStrings.ErrorMaterializingValueNullReference(expectedType) + : exception is InvalidCastException + ? CoreStrings.ErrorMaterializingValueInvalidCast(expectedType, actualType) + : CoreStrings.ErrorMaterializingValue; + } + + throw new InvalidOperationException(message, exception); + } } } } diff --git a/src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs index d6e7d04e3de..d52963c2028 100644 --- a/src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs @@ -30,6 +30,7 @@ public class FromSqlQueryingEnumerable : IEnumerable, IAsyncEnumerable, private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; + private readonly bool _detailedErrorsEnabled; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -43,7 +44,8 @@ public FromSqlQueryingEnumerable( [NotNull] IReadOnlyList columnNames, [NotNull] Func shaper, [NotNull] Type contextType, - bool standAloneStateManager) + bool standAloneStateManager, + bool detailedErrorsEnabled) { _relationalQueryContext = relationalQueryContext; _relationalCommandCache = relationalCommandCache; @@ -52,6 +54,7 @@ public FromSqlQueryingEnumerable( _contextType = contextType; _queryLogger = relationalQueryContext.QueryLogger; _standAloneStateManager = standAloneStateManager; + _detailedErrorsEnabled = detailedErrorsEnabled; } /// @@ -61,7 +64,11 @@ public FromSqlQueryingEnumerable( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) - => new AsyncEnumerator(this, cancellationToken); + { + _relationalQueryContext.CancellationToken = cancellationToken; + + return new AsyncEnumerator(this); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -96,7 +103,8 @@ public virtual DbCommand CreateDbCommand() _relationalQueryContext.ParameterValues, null, null, - null), + null, + _detailedErrorsEnabled), Guid.Empty, (DbCommandMethod)(-1)); @@ -144,6 +152,7 @@ private sealed class Enumerator : IEnumerator private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; + private readonly bool _detailedErrorsEnabled; private RelationalDataReader _dataReader; private int[] _indexMap; @@ -157,6 +166,7 @@ public Enumerator(FromSqlQueryingEnumerable queryingEnumerable) _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; _standAloneStateManager = queryingEnumerable._standAloneStateManager; + _detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled; } public T Current { get; private set; } @@ -199,14 +209,14 @@ private bool InitializeReader(DbContext _, bool result) var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); - _dataReader - = relationalCommand.ExecuteReader( - new RelationalCommandParameterObject( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalCommandCache.ReaderColumns, - _relationalQueryContext.Context, - _relationalQueryContext.CommandLogger)); + _dataReader = relationalCommand.ExecuteReader( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger, + _detailedErrorsEnabled)); _indexMap = BuildIndexMap(_columnNames, _dataReader.DbDataReader); @@ -234,14 +244,12 @@ private sealed class AsyncEnumerator : IAsyncEnumerator private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; - private readonly CancellationToken _cancellationToken; + private readonly bool _detailedErrorsEnabled; private RelationalDataReader _dataReader; private int[] _indexMap; - public AsyncEnumerator( - FromSqlQueryingEnumerable queryingEnumerable, - CancellationToken cancellationToken) + public AsyncEnumerator(FromSqlQueryingEnumerable queryingEnumerable) { _relationalQueryContext = queryingEnumerable._relationalQueryContext; _relationalCommandCache = queryingEnumerable._relationalCommandCache; @@ -250,7 +258,7 @@ public AsyncEnumerator( _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; _standAloneStateManager = queryingEnumerable._standAloneStateManager; - _cancellationToken = cancellationToken; + _detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled; } public T Current { get; private set; } @@ -264,10 +272,10 @@ public async ValueTask MoveNextAsync() if (_dataReader == null) { await _relationalQueryContext.ExecutionStrategyFactory.Create() - .ExecuteAsync(true, InitializeReaderAsync, null, _cancellationToken).ConfigureAwait(false); + .ExecuteAsync(true, InitializeReaderAsync, null, _relationalQueryContext.CancellationToken).ConfigureAwait(false); } - var hasNext = await _dataReader.ReadAsync(_cancellationToken).ConfigureAwait(false); + var hasNext = await _dataReader.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false); Current = hasNext ? _shaper(_relationalQueryContext, _dataReader.DbDataReader, _indexMap) @@ -290,16 +298,16 @@ private async Task InitializeReaderAsync(DbContext _, bool result, Cancell var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); - _dataReader - = await relationalCommand.ExecuteReaderAsync( - new RelationalCommandParameterObject( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalCommandCache.ReaderColumns, - _relationalQueryContext.Context, - _relationalQueryContext.CommandLogger), - cancellationToken) - .ConfigureAwait(false); + _dataReader = await relationalCommand.ExecuteReaderAsync( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger, + _detailedErrorsEnabled), + cancellationToken) + .ConfigureAwait(false); _indexMap = BuildIndexMap(_columnNames, _dataReader.DbDataReader); diff --git a/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs index 49c4722f993..570382f50eb 100644 --- a/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs @@ -28,6 +28,7 @@ public class SingleQueryingEnumerable : IEnumerable, IAsyncEnumerable, private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; + private readonly bool _detailedErrorsEnabled; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -40,7 +41,8 @@ public SingleQueryingEnumerable( [NotNull] RelationalCommandCache relationalCommandCache, [NotNull] Func shaper, [NotNull] Type contextType, - bool standAloneStateManager) + bool standAloneStateManager, + bool detailedErrorsEnabled) { _relationalQueryContext = relationalQueryContext; _relationalCommandCache = relationalCommandCache; @@ -48,6 +50,7 @@ public SingleQueryingEnumerable( _contextType = contextType; _queryLogger = relationalQueryContext.QueryLogger; _standAloneStateManager = standAloneStateManager; + _detailedErrorsEnabled = detailedErrorsEnabled; } /// @@ -57,7 +60,11 @@ public SingleQueryingEnumerable( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) - => new AsyncEnumerator(this, cancellationToken); + { + _relationalQueryContext.CancellationToken = cancellationToken; + + return new AsyncEnumerator(this); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -113,6 +120,7 @@ private sealed class Enumerator : IEnumerator private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; + private readonly bool _detailedErrorsEnabled; private RelationalDataReader _dataReader; private SingleQueryResultCoordinator _resultCoordinator; @@ -125,6 +133,7 @@ public Enumerator(SingleQueryingEnumerable queryingEnumerable) _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; _standAloneStateManager = queryingEnumerable._standAloneStateManager; + _detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled; } public T Current { get; private set; } @@ -194,14 +203,14 @@ private bool InitializeReader(DbContext _, bool result) var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); - _dataReader - = relationalCommand.ExecuteReader( - new RelationalCommandParameterObject( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalCommandCache.ReaderColumns, - _relationalQueryContext.Context, - _relationalQueryContext.CommandLogger)); + _dataReader = relationalCommand.ExecuteReader( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger, + _detailedErrorsEnabled)); _resultCoordinator = new SingleQueryResultCoordinator(); @@ -228,14 +237,12 @@ private sealed class AsyncEnumerator : IAsyncEnumerator private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; - private readonly CancellationToken _cancellationToken; + private readonly bool _detailedErrorsEnabled; private RelationalDataReader _dataReader; private SingleQueryResultCoordinator _resultCoordinator; - public AsyncEnumerator( - SingleQueryingEnumerable queryingEnumerable, - CancellationToken cancellationToken) + public AsyncEnumerator(SingleQueryingEnumerable queryingEnumerable) { _relationalQueryContext = queryingEnumerable._relationalQueryContext; _relationalCommandCache = queryingEnumerable._relationalCommandCache; @@ -243,7 +250,7 @@ public AsyncEnumerator( _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; _standAloneStateManager = queryingEnumerable._standAloneStateManager; - _cancellationToken = cancellationToken; + _detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled; } public T Current { get; private set; } @@ -257,11 +264,12 @@ public async ValueTask MoveNextAsync() if (_dataReader == null) { await _relationalQueryContext.ExecutionStrategyFactory.Create() - .ExecuteAsync(true, InitializeReaderAsync, null, _cancellationToken) + .ExecuteAsync(true, InitializeReaderAsync, null, _relationalQueryContext.CancellationToken) .ConfigureAwait(false); } - var hasNext = _resultCoordinator.HasNext ?? await _dataReader.ReadAsync(_cancellationToken).ConfigureAwait(false); + var hasNext = _resultCoordinator.HasNext + ?? await _dataReader.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false); Current = default; if (hasNext) @@ -280,7 +288,7 @@ await _relationalQueryContext.ExecutionStrategyFactory.Create() break; } - if (!await _dataReader.ReadAsync(_cancellationToken).ConfigureAwait(false)) + if (!await _dataReader.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false)) { _resultCoordinator.HasNext = false; // Enumeration has ended, materialize last element @@ -311,16 +319,16 @@ private async Task InitializeReaderAsync(DbContext _, bool result, Cancell var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); - _dataReader - = await relationalCommand.ExecuteReaderAsync( - new RelationalCommandParameterObject( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalCommandCache.ReaderColumns, - _relationalQueryContext.Context, - _relationalQueryContext.CommandLogger), - cancellationToken) - .ConfigureAwait(false); + _dataReader = await relationalCommand.ExecuteReaderAsync( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger, + _detailedErrorsEnabled), + cancellationToken) + .ConfigureAwait(false); _resultCoordinator = new SingleQueryResultCoordinator(); diff --git a/src/EFCore.Relational/Query/Internal/SplitQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/SplitQueryingEnumerable.cs index eac68f8c378..e1763674f72 100644 --- a/src/EFCore.Relational/Query/Internal/SplitQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/SplitQueryingEnumerable.cs @@ -30,6 +30,7 @@ public class SplitQueryingEnumerable : IEnumerable, IAsyncEnumerable, I private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; + private readonly bool _detailedErrorsEnabled; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -44,7 +45,8 @@ public SplitQueryingEnumerable( [NotNull] Action relatedDataLoaders, [NotNull] Func relatedDataLoadersAsync, [NotNull] Type contextType, - bool standAloneStateManager) + bool standAloneStateManager, + bool detailedErrorsEnabled) { _relationalQueryContext = relationalQueryContext; _relationalCommandCache = relationalCommandCache; @@ -54,6 +56,7 @@ public SplitQueryingEnumerable( _contextType = contextType; _queryLogger = relationalQueryContext.QueryLogger; _standAloneStateManager = standAloneStateManager; + _detailedErrorsEnabled = detailedErrorsEnabled; } /// @@ -102,7 +105,8 @@ public virtual DbCommand CreateDbCommand() _relationalQueryContext.ParameterValues, null, null, - null), + null, + _detailedErrorsEnabled), Guid.Empty, (DbCommandMethod)(-1)); @@ -124,6 +128,7 @@ private sealed class Enumerator : IEnumerator private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; + private readonly bool _detailedErrorsEnabled; private RelationalDataReader _dataReader; private SplitQueryResultCoordinator _resultCoordinator; @@ -138,6 +143,7 @@ public Enumerator(SplitQueryingEnumerable queryingEnumerable) _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; _standAloneStateManager = queryingEnumerable._standAloneStateManager; + _detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled; } public T Current { get; private set; } @@ -191,14 +197,14 @@ private bool InitializeReader(DbContext _, bool result) var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); - _dataReader - = relationalCommand.ExecuteReader( - new RelationalCommandParameterObject( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalCommandCache.ReaderColumns, - _relationalQueryContext.Context, - _relationalQueryContext.CommandLogger)); + _dataReader = relationalCommand.ExecuteReader( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger, + _detailedErrorsEnabled)); _resultCoordinator = new SplitQueryResultCoordinator(); @@ -233,6 +239,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _standAloneStateManager; + private readonly bool _detailedErrorEnabled; private RelationalDataReader _dataReader; private SplitQueryResultCoordinator _resultCoordinator; @@ -247,6 +254,7 @@ public AsyncEnumerator(SplitQueryingEnumerable queryingEnumerable) _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; _standAloneStateManager = queryingEnumerable._standAloneStateManager; + _detailedErrorEnabled = queryingEnumerable._detailedErrorsEnabled; } public T Current { get; private set; } @@ -268,7 +276,7 @@ await _executionStrategy.ExecuteAsync( true, InitializeReaderAsync, null, _relationalQueryContext.CancellationToken).ConfigureAwait(false); } - var hasNext = await _dataReader.ReadAsync().ConfigureAwait(false); + var hasNext = await _dataReader.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false); Current = default; if (hasNext) @@ -303,16 +311,16 @@ private async Task InitializeReaderAsync(DbContext _, bool result, Cancell var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); - _dataReader - = await relationalCommand.ExecuteReaderAsync( - new RelationalCommandParameterObject( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalCommandCache.ReaderColumns, - _relationalQueryContext.Context, - _relationalQueryContext.CommandLogger), - cancellationToken) - .ConfigureAwait(false); + _dataReader = await relationalCommand.ExecuteReaderAsync( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger, + _detailedErrorEnabled), + cancellationToken) + .ConfigureAwait(false); _resultCoordinator = new SplitQueryResultCoordinator(); diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 5e04ef4f8dc..02106f812cc 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -641,6 +641,7 @@ protected override Expression VisitExtension(Expression extensionExpression) collectionIdConstant, Expression.Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), _executionStrategyParameter, + Expression.Constant(_detailedErrorsEnabled), _resultCoordinatorParameter, Expression.Constant(relationalCommandCache), Expression.Constant(childIdentifierLambda.Compile()), @@ -845,6 +846,7 @@ protected override Expression VisitExtension(Expression extensionExpression) collectionIdConstant, Expression.Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), _executionStrategyParameter, + Expression.Constant(_detailedErrorsEnabled), _resultCoordinatorParameter, Expression.Constant(relationalCommandCache), Expression.Constant(childIdentifierLambda.Compile()), @@ -991,9 +993,11 @@ Expression valueExpression getMethod, indexExpression); + var buffering = false; if (_readerColumns != null && _readerColumns[index] == null) { + buffering = true; var bufferedReaderLambdaExpression = valueExpression; var columnType = bufferedReaderLambdaExpression.Type; if (!columnType.IsValueType @@ -1007,6 +1011,7 @@ Expression valueExpression columnType, nullable, _indexMapParameter != null ? ((ColumnExpression)_selectExpression.Projection[index].Expression).Name : null, + property, Expression.Lambda( bufferedReaderLambdaExpression, dbDataReader, @@ -1049,7 +1054,8 @@ Expression valueExpression valueExpression); } - if (_detailedErrorsEnabled) + if (_detailedErrorsEnabled + && !buffering) { var exceptionParameter = Expression.Parameter(typeof(Exception), name: "e"); @@ -1341,6 +1347,7 @@ private static void PopulateSplitIncludeCollection childIdentifier, @@ -1364,14 +1371,14 @@ bool InitializeReader(DbContext _, bool result) { var relationalCommand = relationalCommandCache.GetRelationalCommand(queryContext.ParameterValues); - dataReader - = relationalCommand.ExecuteReader( - new RelationalCommandParameterObject( - queryContext.Connection, - queryContext.ParameterValues, - relationalCommandCache.ReaderColumns, - queryContext.Context, - queryContext.CommandLogger)); + dataReader = relationalCommand.ExecuteReader( + new RelationalCommandParameterObject( + queryContext.Connection, + queryContext.ParameterValues, + relationalCommandCache.ReaderColumns, + queryContext.Context, + queryContext.CommandLogger, + detailedErrorsEnabled)); return result; } @@ -1421,6 +1428,7 @@ private static async Task PopulateSplitIncludeCollectionAsync childIdentifier, @@ -1445,16 +1453,16 @@ async Task InitializeReaderAsync(DbContext _, bool result, CancellationTok { var relationalCommand = relationalCommandCache.GetRelationalCommand(queryContext.ParameterValues); - dataReader - = await relationalCommand.ExecuteReaderAsync( - new RelationalCommandParameterObject( - queryContext.Connection, - queryContext.ParameterValues, - relationalCommandCache.ReaderColumns, - queryContext.Context, - queryContext.CommandLogger), - cancellationToken) - .ConfigureAwait(false); + dataReader = await relationalCommand.ExecuteReaderAsync( + new RelationalCommandParameterObject( + queryContext.Connection, + queryContext.ParameterValues, + relationalCommandCache.ReaderColumns, + queryContext.Context, + queryContext.CommandLogger, + detailedErrorsEnabled), + cancellationToken) + .ConfigureAwait(false); return result; } @@ -1661,6 +1669,7 @@ private static void PopulateSplitCollection childIdentifier, @@ -1681,14 +1690,14 @@ bool InitializeReader(DbContext _, bool result) { var relationalCommand = relationalCommandCache.GetRelationalCommand(queryContext.ParameterValues); - dataReader - = relationalCommand.ExecuteReader( - new RelationalCommandParameterObject( - queryContext.Connection, - queryContext.ParameterValues, - relationalCommandCache.ReaderColumns, - queryContext.Context, - queryContext.CommandLogger)); + dataReader = relationalCommand.ExecuteReader( + new RelationalCommandParameterObject( + queryContext.Connection, + queryContext.ParameterValues, + relationalCommandCache.ReaderColumns, + queryContext.Context, + queryContext.CommandLogger, + detailedErrorsEnabled)); return result; } @@ -1733,6 +1742,7 @@ private static async Task PopulateSplitCollectionAsync childIdentifier, @@ -1754,16 +1764,16 @@ async Task InitializeReaderAsync(DbContext _, bool result, CancellationTok { var relationalCommand = relationalCommandCache.GetRelationalCommand(queryContext.ParameterValues); - dataReader - = await relationalCommand.ExecuteReaderAsync( - new RelationalCommandParameterObject( - queryContext.Connection, - queryContext.ParameterValues, - relationalCommandCache.ReaderColumns, - queryContext.Context, - queryContext.CommandLogger), - cancellationToken) - .ConfigureAwait(false); + dataReader = await relationalCommand.ExecuteReaderAsync( + new RelationalCommandParameterObject( + queryContext.Connection, + queryContext.ParameterValues, + relationalCommandCache.ReaderColumns, + queryContext.Context, + queryContext.CommandLogger, + detailedErrorsEnabled), + cancellationToken) + .ConfigureAwait(false); return result; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index 440d6379702..4dcceb1e99f 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -76,7 +76,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery Expression.Constant(shaper.Compile()), Expression.Constant(_contextType), Expression.Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution)); + QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Expression.Constant(_detailedErrorsEnabled)); } if (splitQuery) @@ -98,7 +99,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery relatedDataLoadersAsyncParameter, Expression.Constant(_contextType), Expression.Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution)); + QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Expression.Constant(_detailedErrorsEnabled)); } return Expression.New( @@ -108,7 +110,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery Expression.Constant(shaper.Compile()), Expression.Constant(_contextType), Expression.Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution)); + QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Expression.Constant(_detailedErrorsEnabled)); } } } diff --git a/src/EFCore.Relational/Storage/ReaderColumn.cs b/src/EFCore.Relational/Storage/ReaderColumn.cs index 9a63087a6f8..3847aadf80f 100644 --- a/src/EFCore.Relational/Storage/ReaderColumn.cs +++ b/src/EFCore.Relational/Storage/ReaderColumn.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Concurrent; +using System.Linq; using System.Reflection; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; namespace Microsoft.EntityFrameworkCore.Storage { @@ -28,11 +30,25 @@ private static readonly ConcurrentDictionary _constructor /// The CLR type of the column. /// A value indicating if the column is nullable. /// The name of the column. + [Obsolete("Use constructor which also takes IPropertyBase.")] protected ReaderColumn([NotNull] Type type, bool nullable, [CanBeNull] string name) + : this(type, nullable, name, null) + { + } + + /// + /// Creates a new instance of the class. + /// + /// The CLR type of the column. + /// A value indicating if the column is nullable. + /// The name of the column. + /// The property being read if any, null otherwise. + protected ReaderColumn([NotNull] Type type, bool nullable, [CanBeNull] string name, [CanBeNull] IPropertyBase property) { Type = type; IsNullable = nullable; Name = name; + Property = property; } /// @@ -50,6 +66,11 @@ protected ReaderColumn([NotNull] Type type, bool nullable, [CanBeNull] string na /// public virtual string Name { get; } + /// + /// The property being read if any, null otherwise. + /// + public virtual IPropertyBase Property { get; } + /// /// Creates an instance of . /// @@ -60,10 +81,31 @@ protected ReaderColumn([NotNull] Type type, bool nullable, [CanBeNull] string na /// A used to get the field value for this column. /// /// An instance of . + [Obsolete("Use method which also takes IPropertyBase.")] public static ReaderColumn Create([NotNull] Type type, bool nullable, [CanBeNull] string columnName, [NotNull] object readFunc) => (ReaderColumn)GetConstructor(type).Invoke(new[] { nullable, columnName, readFunc }); + /// + /// Creates an instance of . + /// + /// The type of the column. + /// Whether the column can contain values. + /// The column name if it is used to access the column values, otherwise. + /// The property being read if any, null otherwise. + /// + /// A used to get the field value for this column. + /// + /// An instance of . + public static ReaderColumn Create( + [NotNull] Type type, + bool nullable, + [CanBeNull] string columnName, + [CanBeNull] IPropertyBase property, + [NotNull] object readFunc) + => (ReaderColumn)GetConstructor(type).Invoke(new[] { nullable, columnName, property, readFunc }); + private static ConstructorInfo GetConstructor(Type type) - => _constructors.GetOrAdd(type, t => typeof(ReaderColumn<>).MakeGenericType(t).GetConstructors()[0]); + => _constructors.GetOrAdd( + type, t => typeof(ReaderColumn<>).MakeGenericType(t).GetConstructors().First(ci=> ci.GetParameters().Length == 4)); } } diff --git a/src/EFCore.Relational/Storage/ReaderColumn`.cs b/src/EFCore.Relational/Storage/ReaderColumn`.cs index 7bfc8a8b1ff..e37ff7b767f 100644 --- a/src/EFCore.Relational/Storage/ReaderColumn`.cs +++ b/src/EFCore.Relational/Storage/ReaderColumn`.cs @@ -4,6 +4,7 @@ using System; using System.Data.Common; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; namespace Microsoft.EntityFrameworkCore.Storage { @@ -24,8 +25,25 @@ public class ReaderColumn : ReaderColumn /// A value indicating if the column is nullable. /// The name of the column. /// A function to get field value for the column from the reader. + [Obsolete("Use constructor which also takes IPropertyBase.")] public ReaderColumn(bool nullable, [CanBeNull] string name, [NotNull] Func getFieldValue) - : base(typeof(T), nullable, name) + : this(nullable, name, property: null, getFieldValue) + { + } + + /// + /// Creates a new instance of the class. + /// + /// A value indicating if the column is nullable. + /// The name of the column. + /// The property being read if any, null otherwise. + /// A function to get field value for the column from the reader. + public ReaderColumn( + bool nullable, + [CanBeNull] string name, + [CanBeNull] IPropertyBase property, + [NotNull] Func getFieldValue) + : base(typeof(T), nullable, name, property) { GetFieldValue = getFieldValue; } diff --git a/src/EFCore.Relational/Storage/RelationalCommand.cs b/src/EFCore.Relational/Storage/RelationalCommand.cs index ff3cbed73ba..07ef0d0e595 100644 --- a/src/EFCore.Relational/Storage/RelationalCommand.cs +++ b/src/EFCore.Relational/Storage/RelationalCommand.cs @@ -385,6 +385,7 @@ public virtual RelationalDataReader ExecuteReader(RelationalCommandParameterObje var context = parameterObject.Context; var readerColumns = parameterObject.ReaderColumns; var logger = parameterObject.Logger; + var detailedErrorsEnabled = parameterObject.DetailedErrorsEnabled; var commandId = Guid.NewGuid(); var command = CreateDbCommand(parameterObject, commandId, DbCommandMethod.ExecuteReader); @@ -425,7 +426,7 @@ public virtual RelationalDataReader ExecuteReader(RelationalCommandParameterObje if (readerColumns != null) { - reader = new BufferedDataReader(reader).Initialize(readerColumns); + reader = new BufferedDataReader(reader, detailedErrorsEnabled).Initialize(readerColumns); } var result = CreateRelationalDataReader( @@ -479,6 +480,7 @@ public virtual async Task ExecuteReaderAsync( var context = parameterObject.Context; var readerColumns = parameterObject.ReaderColumns; var logger = parameterObject.Logger; + var detailedErrorsEnabled = parameterObject.DetailedErrorsEnabled; var commandId = Guid.NewGuid(); var command = CreateDbCommand(parameterObject, commandId, DbCommandMethod.ExecuteReader); @@ -524,7 +526,7 @@ public virtual async Task ExecuteReaderAsync( if (readerColumns != null) { - reader = await new BufferedDataReader(reader).InitializeAsync(readerColumns, cancellationToken) + reader = await new BufferedDataReader(reader, detailedErrorsEnabled).InitializeAsync(readerColumns, cancellationToken) .ConfigureAwait(false); } diff --git a/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs b/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs index 6aea922e97c..e0f996c89b7 100644 --- a/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs +++ b/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs @@ -39,6 +39,32 @@ public RelationalCommandParameterObject( [CanBeNull] IReadOnlyList readerColumns, [CanBeNull] DbContext context, [CanBeNull] IDiagnosticsLogger logger) + : this(connection, parameterValues, readerColumns, context, logger, detailedErrorsEnabled: false) + { + } + + /// + /// + /// Creates a new parameter object for the given parameters. + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The connection on which the command will execute. + /// The SQL parameter values to use, or null if none. + /// The expected columns if the reader needs to be buffered, or null otherwise. + /// The current instance, or null if it is not known. + /// A logger, or null if no logger is available. + /// A value indicating if detailed errors are enabled. + public RelationalCommandParameterObject( + [NotNull] IRelationalConnection connection, + [CanBeNull] IReadOnlyDictionary parameterValues, + [CanBeNull] IReadOnlyList readerColumns, + [CanBeNull] DbContext context, + [CanBeNull] IDiagnosticsLogger logger, + bool detailedErrorsEnabled) { Check.NotNull(connection, nameof(connection)); @@ -47,6 +73,7 @@ public RelationalCommandParameterObject( ReaderColumns = readerColumns; Context = context; Logger = logger; + DetailedErrorsEnabled = detailedErrorsEnabled; } /// @@ -73,5 +100,10 @@ public RelationalCommandParameterObject( /// A logger, or null if no logger is available. /// public IDiagnosticsLogger Logger { get; } + + /// + /// A value indicating if detailed errors are enabled. + /// + public bool DetailedErrorsEnabled { get; } } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs index c4a95627983..0af741e9585 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs @@ -42,7 +42,7 @@ public virtual void Bad_data_error_handling_invalid_cast_key() () => context.Set().FromSqlRaw( NormalizeDelimitersInRawString( - @"SELECT [ProductID] AS [ProductName], [ProductName] AS [ProductID], [SupplierID], [UnitPrice], [UnitsInStock], [Discontinued] + @"SELECT [ProductName] AS [ProductID], [ProductID] AS [ProductName], [SupplierID], [UnitPrice], [UnitsInStock], [Discontinued] FROM [Products]")) .ToList()).Message); } @@ -67,7 +67,7 @@ public virtual void Bad_data_error_handling_invalid_cast_projection() { using var context = CreateContext(); Assert.Equal( - CoreStrings.ErrorMaterializingPropertyInvalidCast("Product", "UnitPrice", typeof(decimal?), typeof(int)), + CoreStrings.ErrorMaterializingValueInvalidCast(typeof(decimal?), typeof(int)), Assert.Throws( () => context.Set().FromSqlRaw( @@ -89,7 +89,7 @@ public virtual void Bad_data_error_handling_invalid_cast_no_tracking() context.Set() .FromSqlRaw( NormalizeDelimitersInRawString( - @"SELECT [ProductID] AS [ProductName], [ProductName] AS [ProductID], [SupplierID], [UnitPrice], [UnitsInStock], [Discontinued] + @"SELECT [ProductName] AS [ProductID], [ProductID] AS [ProductName], [SupplierID], [UnitPrice], [UnitsInStock], [Discontinued] FROM [Products]")).AsNoTracking() .ToList()).Message); } diff --git a/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs b/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs index 2638e0bac61..f327e3ec550 100644 --- a/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs +++ b/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs @@ -23,8 +23,8 @@ public class BufferedDataReaderTest public async Task Metadata_methods_return_expected_results(bool async) { var reader = new FakeDbDataReader(new[] { "columnName" }, new[] { new[] { new object() }, new[] { new object() } }); - var columns = new ReaderColumn[] { new ReaderColumn(true, null, (r, _) => r.GetValue(0)) }; - var bufferedDataReader = new BufferedDataReader(reader); + var columns = new ReaderColumn[] { new ReaderColumn(true, null, null, (r, _) => r.GetValue(0)) }; + var bufferedDataReader = new BufferedDataReader(reader, false); if (async) { await bufferedDataReader.InitializeAsync(columns, CancellationToken.None); @@ -51,11 +51,11 @@ public async Task Manipulation_methods_perform_expected_actions(bool async) new List> { new[] { new object[] { 1, "a" } }, new object[0][] }); var columns = new ReaderColumn[] { - new ReaderColumn(false, null, (r, _) => r.GetInt32(0)), - new ReaderColumn(true, null, (r, _) => r.GetValue(1)) + new ReaderColumn(false, null, null,(r, _) => r.GetInt32(0)), + new ReaderColumn(true, null, null,(r, _) => r.GetValue(1)) }; - var bufferedDataReader = new BufferedDataReader(reader); + var bufferedDataReader = new BufferedDataReader(reader, false); Assert.False(bufferedDataReader.IsClosed); if (async) @@ -116,8 +116,8 @@ public async Task Manipulation_methods_perform_expected_actions(bool async) public async Task Initialize_is_idempotent(bool async) { var reader = new FakeDbDataReader(new[] { "name" }, new[] { new[] { new object() } }); - var columns = new ReaderColumn[] { new ReaderColumn(true, null, (r, _) => r.GetValue(0)) }; - var bufferedReader = new BufferedDataReader(reader); + var columns = new ReaderColumn[] { new ReaderColumn(true, null, null, (r, _) => r.GetValue(0)) }; + var bufferedReader = new BufferedDataReader(reader, false); Assert.False(reader.IsClosed); if (async) @@ -187,10 +187,10 @@ private async Task Verify_method_result( var columns = new[] { - ReaderColumn.Create(columnType, true, null, (Func)((r, _) => r.GetFieldValue(0))) + ReaderColumn.Create(columnType, true, null, null,(Func)((r, _) => r.GetFieldValue(0))) }; - var bufferedReader = new BufferedDataReader(reader); + var bufferedReader = new BufferedDataReader(reader, false); if (async) { await bufferedReader.InitializeAsync(columns, CancellationToken.None); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs index 73db7f8a4e9..c550dc1daf6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs @@ -786,54 +786,6 @@ UNION ALL ) AS [c0]"); } - [ConditionalFact(Skip = "Issue#20364")] - public override void Bad_data_error_handling_invalid_cast() - { - base.Bad_data_error_handling_invalid_cast(); - } - - [ConditionalFact(Skip = "Issue#20364")] - public override void Bad_data_error_handling_invalid_cast_key() - { - base.Bad_data_error_handling_invalid_cast_key(); - } - - [ConditionalFact(Skip = "Issue#20364")] - public override void Bad_data_error_handling_invalid_cast_no_tracking() - { - base.Bad_data_error_handling_invalid_cast_no_tracking(); - } - - [ConditionalFact(Skip = "Issue#20364")] - public override void Bad_data_error_handling_invalid_cast_projection() - { - base.Bad_data_error_handling_invalid_cast_projection(); - } - - [ConditionalFact(Skip = "Issue#20364")] - public override void Bad_data_error_handling_null() - { - base.Bad_data_error_handling_null(); - } - - [ConditionalFact(Skip = "Issue#20364")] - public override void Bad_data_error_handling_null_no_tracking() - { - base.Bad_data_error_handling_null_no_tracking(); - } - - [ConditionalFact(Skip = "Issue#20364")] - public override void Bad_data_error_handling_null_projection() - { - base.Bad_data_error_handling_null_projection(); - } - - [ConditionalFact(Skip = "Issue#20364")] - public override void FromSqlRaw_queryable_simple_columns_out_of_order_and_not_enough_columns_throws() - { - base.FromSqlRaw_queryable_simple_columns_out_of_order_and_not_enough_columns_throws(); - } - public override void Line_endings_after_Select() { base.Line_endings_after_Select(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index f4173e5736a..283f00a370c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -444,7 +444,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) #endregion - [ConditionalFact(Skip = "Issue#20364")] + [ConditionalFact] public void Query_when_null_key_in_database_should_throw() { using var testStore = SqlServerTestStore.CreateInitialized("QueryBugsTest"); @@ -452,7 +452,10 @@ public void Query_when_null_key_in_database_should_throw() @"CREATE TABLE ZeroKey (Id int); INSERT ZeroKey VALUES (NULL)"); - using var context = new NullKeyContext(Fixture.CreateOptions(testStore)); + var options = Fixture.CreateOptions(testStore); + options = new DbContextOptionsBuilder(options).EnableDetailedErrors().Options; + + using var context = new NullKeyContext(options); Assert.Equal( CoreStrings.ErrorMaterializingPropertyNullReference("ZeroKey", "Id", typeof(int)), Assert.Throws(() => context.ZeroKeys.ToList()).Message);