From dd44082617cd0e922abc490fa041918870928400 Mon Sep 17 00:00:00 2001 From: anpete Date: Mon, 21 Nov 2016 15:14:33 -0800 Subject: [PATCH] Fix: #7048 - EF Core 1.1 .ToString() translation causes exception Adding bad data error handling logic to materialization introduced the possibility of hitting a known expression compiler limitation (as described here: https://github.com/dotnet/corefx/pull/13126). This change works around the limitation by moving the read-value try-catch to a helper method, thus we no longer insert a try-catch directly into the output ET. --- .../GearsOfWarQueryTestBase.cs | 12 ++++ .../Internal/EntityMaterializerSource.cs | 56 +++++++++++-------- .../QueryLoggingSqlServerTest.cs | 24 ++++++-- 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs index bb4cae98ebc..311f44a4d1e 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/GearsOfWarQueryTestBase.cs @@ -44,6 +44,18 @@ public virtual void Include_multiple_one_to_one_and_one_to_many() } } + [ConditionalFact] + public virtual void ToString_guid_property_projection() + { + using (var context = CreateContext()) + { + var query = context.Tags.Select(ct => new { A = ct.GearNickName, B = ct.Id.ToString() }); + var result = query.ToList(); + + Assert.Equal(6, result.Count); + } + } + [ConditionalFact] public virtual void Include_multiple_one_to_one_and_one_to_many_self_reference() { diff --git a/src/Microsoft.EntityFrameworkCore/Metadata/Internal/EntityMaterializerSource.cs b/src/Microsoft.EntityFrameworkCore/Metadata/Internal/EntityMaterializerSource.cs index 3761ad93a4b..bea07e9f6fb 100644 --- a/src/Microsoft.EntityFrameworkCore/Metadata/Internal/EntityMaterializerSource.cs +++ b/src/Microsoft.EntityFrameworkCore/Metadata/Internal/EntityMaterializerSource.cs @@ -29,34 +29,42 @@ private static readonly MethodInfo _readValue /// directly from your code. This API may change or be removed in future releases. /// public virtual Expression CreateReadValueExpression( - Expression valueBuffer, - Type type, + Expression valueBuffer, + Type type, + int index, + IProperty property = null) + => Expression.Call( + _tryReadValueMethod.MakeGenericMethod(type), + valueBuffer, + Expression.Constant(index), + Expression.Constant(property, typeof(IPropertyBase))); + + private static readonly MethodInfo _tryReadValueMethod + = typeof(EntityMaterializerSource).GetTypeInfo() + .GetDeclaredMethod(nameof(TryReadValue)); + + private static TValue TryReadValue( + ValueBuffer valueBuffer, int index, - IProperty property = null) + IPropertyBase property = null) { - var readValueExpression - = Expression.Convert(CreateReadValueCallExpression(valueBuffer, index), type); - - var exceptionParameter - = Expression.Parameter(typeof(Exception), "e"); - - var catchBlock - = Expression - .Catch(exceptionParameter, - Expression.Call( - _createReadValueException.MakeGenericMethod(readValueExpression.Type), - exceptionParameter, - CreateReadValueCallExpression(valueBuffer, index), - Expression.Constant(property, typeof(IPropertyBase)))); - - return Expression.TryCatch(readValueExpression, catchBlock); - } + object untypedValue = null; - private static readonly MethodInfo _createReadValueException - = typeof(EntityMaterializerSource).GetTypeInfo() - .GetDeclaredMethod(nameof(ThrowReadValueException)); + try + { + untypedValue = valueBuffer[index]; - private static TValue ThrowReadValueException( + return (TValue)untypedValue; + } + catch (Exception e) + { + ThrowReadValueException(e, untypedValue, property); + } + + return default(TValue); + } + + private static void ThrowReadValueException( Exception exception, object value, IPropertyBase property = null) { var expectedType = typeof(TValue); diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryLoggingSqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryLoggingSqlServerTest.cs index 3b9a838a7c5..07f5863a51b 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryLoggingSqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryLoggingSqlServerTest.cs @@ -131,10 +131,26 @@ ORDER BY [o].[CustomerID] materializer: (ValueBuffer valueBuffer) => { var3 = new Order() - var3.k__BackingField = try { (int) object valueBuffer.get_Item(0) } catch (Exception) { ... } - var3.k__BackingField = try { (string) object valueBuffer.get_Item(1) } catch (Exception) { ... } - var3.k__BackingField = try { (Nullable) object valueBuffer.get_Item(2) } catch (Exception) { ... } - var3.k__BackingField = try { (Nullable) object valueBuffer.get_Item(3) } catch (Exception) { ... } + var3.k__BackingField = int TryReadValue( + valueBuffer: valueBuffer, + index: 0, + property: OrderID + ) + var3.k__BackingField = string TryReadValue( + valueBuffer: valueBuffer, + index: 1, + property: CustomerID + ) + var3.k__BackingField = Nullable TryReadValue( + valueBuffer: valueBuffer, + index: 2, + property: EmployeeID + ) + var3.k__BackingField = Nullable TryReadValue( + valueBuffer: valueBuffer, + index: 3, + property: OrderDate + ) return instance } )