Skip to content

Commit fad3c60

Browse files
committed
Query: Properly generate expression to read from BufferedDataReader
Resolves #23282 Resolves #23104 Issue: When buffering is enabled, first time when we read particular property slot, we create expression to read into buffer and out of the buffer. We mistakenly bypassed changing expression to read from value buffer when same property slot was being read again, causing cast errors Also matches the type being read out of the buffered data reader
1 parent a07bee7 commit fad3c60

File tree

2 files changed

+155
-22
lines changed

2 files changed

+155
-22
lines changed

src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -994,33 +994,77 @@ Expression valueExpression
994994
indexExpression);
995995

996996
var buffering = false;
997-
if (_readerColumns != null
998-
&& _readerColumns[index] == null)
997+
998+
if (AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue23282", out var isEnabled) && isEnabled)
999999
{
1000-
buffering = true;
1001-
var bufferedReaderLambdaExpression = valueExpression;
1002-
var columnType = bufferedReaderLambdaExpression.Type;
1003-
if (!columnType.IsValueType
1004-
|| !BufferedDataReader.IsSupportedValueType(columnType))
1000+
if (_readerColumns != null
1001+
&& _readerColumns[index] == null)
10051002
{
1006-
columnType = typeof(object);
1007-
bufferedReaderLambdaExpression = Expression.Convert(bufferedReaderLambdaExpression, columnType);
1008-
}
1003+
buffering = true;
1004+
var bufferedReaderLambdaExpression = valueExpression;
1005+
var columnType = bufferedReaderLambdaExpression.Type;
1006+
if (!columnType.IsValueType
1007+
|| !BufferedDataReader.IsSupportedValueType(columnType))
1008+
{
1009+
columnType = typeof(object);
1010+
bufferedReaderLambdaExpression = Expression.Convert(bufferedReaderLambdaExpression, columnType);
1011+
}
10091012

1010-
_readerColumns[index] = ReaderColumn.Create(
1011-
columnType,
1012-
nullable,
1013-
_indexMapParameter != null ? ((ColumnExpression)_selectExpression.Projection[index].Expression).Name : null,
1014-
property,
1015-
Expression.Lambda(
1016-
bufferedReaderLambdaExpression,
1017-
dbDataReader,
1018-
_indexMapParameter ?? Expression.Parameter(typeof(int[]))).Compile());
1019-
1020-
if (getMethod.DeclaringType != typeof(DbDataReader))
1013+
_readerColumns[index] = ReaderColumn.Create(
1014+
columnType,
1015+
nullable,
1016+
_indexMapParameter != null ? ((ColumnExpression)_selectExpression.Projection[index].Expression).Name : null,
1017+
property,
1018+
Expression.Lambda(
1019+
bufferedReaderLambdaExpression,
1020+
dbDataReader,
1021+
_indexMapParameter ?? Expression.Parameter(typeof(int[]))).Compile());
1022+
1023+
if (getMethod.DeclaringType != typeof(DbDataReader))
1024+
{
1025+
valueExpression = Expression.Call(
1026+
dbDataReader, RelationalTypeMapping.GetDataReaderMethod(columnType), indexExpression);
1027+
}
1028+
}
1029+
}
1030+
else
1031+
{
1032+
if (_readerColumns != null)
10211033
{
1034+
buffering = true;
1035+
var columnType = valueExpression.Type;
1036+
var bufferedColumnType = columnType;
1037+
if (!bufferedColumnType.IsValueType
1038+
|| !BufferedDataReader.IsSupportedValueType(bufferedColumnType))
1039+
{
1040+
bufferedColumnType = typeof(object);
1041+
}
1042+
1043+
if (_readerColumns[index] == null)
1044+
{
1045+
var bufferedReaderLambdaExpression = valueExpression;
1046+
if (columnType != bufferedColumnType)
1047+
{
1048+
bufferedReaderLambdaExpression = Expression.Convert(bufferedReaderLambdaExpression, bufferedColumnType);
1049+
}
1050+
1051+
_readerColumns[index] = ReaderColumn.Create(
1052+
bufferedColumnType,
1053+
nullable,
1054+
_indexMapParameter != null ? ((ColumnExpression)_selectExpression.Projection[index].Expression).Name : null,
1055+
property,
1056+
Expression.Lambda(
1057+
bufferedReaderLambdaExpression,
1058+
dbDataReader,
1059+
_indexMapParameter ?? Expression.Parameter(typeof(int[]))).Compile());
1060+
}
1061+
10221062
valueExpression = Expression.Call(
1023-
dbDataReader, RelationalTypeMapping.GetDataReaderMethod(columnType), indexExpression);
1063+
dbDataReader, RelationalTypeMapping.GetDataReaderMethod(bufferedColumnType), indexExpression);
1064+
if (valueExpression.Type != columnType)
1065+
{
1066+
valueExpression = Expression.Convert(valueExpression, columnType);
1067+
}
10241068
}
10251069
}
10261070

test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Microsoft.Extensions.Caching.Memory;
2424
using Microsoft.Extensions.DependencyInjection;
2525
using Microsoft.Extensions.Logging;
26+
using NetTopologySuite.Geometries;
2627
using Xunit;
2728
using Xunit.Abstractions;
2829

@@ -9290,6 +9291,94 @@ private SqlServerTestStore CreateDatabase23211()
92909291

92919292
#endregion
92929293

9294+
#region Issue23282
9295+
9296+
[ConditionalFact]
9297+
public virtual void Can_query_point_with_buffered_data_reader()
9298+
{
9299+
var (options, testSqlLoggerFactory) = CreateOptions23282();
9300+
using var context = new MyContext23282(options);
9301+
9302+
var testUser = context.Locations.FirstOrDefault(x => x.Name == "My Location");
9303+
9304+
Assert.NotNull(testUser);
9305+
9306+
testSqlLoggerFactory.AssertBaseline(
9307+
new[] {
9308+
@"SELECT TOP(1) [l].[Id], [l].[Name], [l].[Address_County], [l].[Address_Line1], [l].[Address_Line2], [l].[Address_Point], [l].[Address_Postcode], [l].[Address_Town]
9309+
FROM [Locations] AS [l]
9310+
WHERE [l].[Name] = N'My Location'" });
9311+
}
9312+
9313+
[Owned]
9314+
private class Address23282
9315+
{
9316+
public string Line1 { get; set; }
9317+
public string Line2 { get; set; }
9318+
public string Town { get; set; }
9319+
public string County { get; set; }
9320+
public string Postcode { get; set; }
9321+
9322+
public Point Point { get; set; }
9323+
}
9324+
9325+
private class Location23282
9326+
{
9327+
[Key]
9328+
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
9329+
public Guid Id { get; set; }
9330+
9331+
public string Name { get; set; }
9332+
public Address23282 Address { get; set; }
9333+
}
9334+
9335+
private class MyContext23282 : DbContext
9336+
{
9337+
public DbSet<Location23282> Locations { get; set; }
9338+
9339+
public MyContext23282(DbContextOptions options)
9340+
: base(options)
9341+
{
9342+
}
9343+
}
9344+
9345+
private (DbContextOptions, TestSqlLoggerFactory) CreateOptions23282()
9346+
{
9347+
var testStore = SqlServerTestStore.CreateInitialized("QueryBugsTest");
9348+
var testSqlLoggerFactory = new TestSqlLoggerFactory();
9349+
var serviceProvider = new ServiceCollection().AddSingleton<ILoggerFactory>(testSqlLoggerFactory).BuildServiceProvider();
9350+
9351+
var optionsBuilder = Fixture.AddOptions(new DbContextOptionsBuilder()
9352+
.UseSqlServer(testStore.ConnectionString, b => b.EnableRetryOnFailure().UseNetTopologySuite()))
9353+
.EnableDetailedErrors()
9354+
.EnableServiceProviderCaching(false)
9355+
.UseApplicationServiceProvider(serviceProvider);
9356+
9357+
var context = new MyContext23282(optionsBuilder.Options);
9358+
if (context.Database.EnsureCreatedResiliently())
9359+
{
9360+
context.Locations.Add(new Location23282
9361+
{
9362+
Name = "My Location",
9363+
Address = new Address23282
9364+
{
9365+
Line1 = "1 Fake Street",
9366+
Town = "Fake Town",
9367+
County = "Fakeshire",
9368+
Postcode = "PO57 0DE",
9369+
Point = new Point(115.7930, 37.2431) { SRID = 4326 }
9370+
}
9371+
});
9372+
context.SaveChanges();
9373+
}
9374+
9375+
testSqlLoggerFactory.Clear();
9376+
9377+
return (optionsBuilder.Options, testSqlLoggerFactory);
9378+
}
9379+
9380+
#endregion
9381+
92939382
private DbContextOptions _options;
92949383

92959384
private SqlServerTestStore CreateTestStore<TContext>(

0 commit comments

Comments
 (0)