diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs index 349e245e70c..92384cab6d7 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs @@ -72,6 +72,25 @@ public SqlServerByteArrayMethodTranslator([NotNull] ISqlExpressionFactory sqlExp _sqlExpressionFactory.Constant(0)); } + if (method.IsGenericMethod + && method.GetGenericMethodDefinition().Equals(EnumerableMethods.FirstWithoutPredicate) + && arguments[0].Type == typeof(byte[])) + { + return _sqlExpressionFactory.Convert( + _sqlExpressionFactory.Function( + "SUBSTRING", + new SqlExpression[] + { + arguments[0], + _sqlExpressionFactory.Constant(1), + _sqlExpressionFactory.Constant(1) + }, + nullable: true, + argumentsPropagateNullability: new[] { true, true, true }, + typeof(byte[])), + method.ReturnType); + } + return null; } } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs index a4338430569..f84250b6b63 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs @@ -64,6 +64,33 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) { Check.NotNull(binaryExpression, nameof(binaryExpression)); + if (binaryExpression.NodeType == ExpressionType.ArrayIndex + && binaryExpression.Left.Type == typeof(byte[])) + { + var left = Visit(binaryExpression.Left); + var right = Visit(binaryExpression.Right); + + if (left is SqlExpression leftSql + && right is SqlExpression rightSql) + { + return Dependencies.SqlExpressionFactory.Convert( + Dependencies.SqlExpressionFactory.Function( + "SUBSTRING", + new SqlExpression[] + { + leftSql, + Dependencies.SqlExpressionFactory.Add( + Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(rightSql), + Dependencies.SqlExpressionFactory.Constant(1)), + Dependencies.SqlExpressionFactory.Constant(1) + }, + nullable: true, + argumentsPropagateNullability: new[] { true, true, true }, + typeof(byte[])), + binaryExpression.Type); + } + } + return !(base.VisitBinary(binaryExpression) is SqlExpression visitedExpression) ? QueryCompilationContext.NotTranslatedExpression : visitedExpression is SqlBinaryExpression sqlBinary diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs index bc828f2c319..7630acc9d89 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; #nullable enable @@ -23,6 +24,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal public class SqliteByteArrayMethodTranslator : IMethodCallTranslator { private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IRelationalTypeMappingSource _typeMappingSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -30,9 +32,12 @@ public class SqliteByteArrayMethodTranslator : IMethodCallTranslator /// 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 SqliteByteArrayMethodTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory) + public SqliteByteArrayMethodTranslator( + [NotNull] ISqlExpressionFactory sqlExpressionFactory, + [NotNull] IRelationalTypeMappingSource typeMappingSource) { _sqlExpressionFactory = sqlExpressionFactory; + _typeMappingSource = typeMappingSource; } /// @@ -76,6 +81,32 @@ public SqliteByteArrayMethodTranslator([NotNull] ISqlExpressionFactory sqlExpres _sqlExpressionFactory.Constant(0)); } + // See issue#16428 + //if (method.IsGenericMethod + // && method.GetGenericMethodDefinition().Equals(EnumerableMethods.FirstWithoutPredicate) + // && arguments[0].Type == typeof(byte[])) + //{ + // return _sqlExpressionFactory.Function( + // "unicode", + // new SqlExpression[] + // { + // _sqlExpressionFactory.Function( + // "substr", + // new SqlExpression[] + // { + // arguments[0], + // _sqlExpressionFactory.Constant(1), + // _sqlExpressionFactory.Constant(1) + // }, + // nullable: true, + // argumentsPropagateNullability: new[] { true, true, true }, + // typeof(byte[])) + // }, + // nullable: true, + // argumentsPropagateNullability: new[] { true }, + // method.ReturnType); + //} + return null; } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs index 32b273e0d80..f58a5a74008 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs @@ -30,7 +30,7 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat AddTranslators( new IMethodCallTranslator[] { - new SqliteByteArrayMethodTranslator(sqlExpressionFactory), + new SqliteByteArrayMethodTranslator(sqlExpressionFactory, dependencies.RelationalTypeMappingSource), new SqliteCharMethodTranslator(sqlExpressionFactory), new SqliteDateTimeAddTranslator(sqlExpressionFactory), new SqliteGlobMethodTranslator(sqlExpressionFactory), diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index eeb239a8bcc..5a404f753cd 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -151,6 +151,40 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) { Check.NotNull(binaryExpression, nameof(binaryExpression)); + // See issue#16428 + //if (binaryExpression.NodeType == ExpressionType.ArrayIndex + // && binaryExpression.Left.Type == typeof(byte[])) + //{ + // var left = Visit(binaryExpression.Left); + // var right = Visit(binaryExpression.Right); + + // if (left is SqlExpression leftSql + // && right is SqlExpression rightSql) + // { + // return Dependencies.SqlExpressionFactory.Function( + // "unicode", + // new SqlExpression[] + // { + // Dependencies.SqlExpressionFactory.Function( + // "substr", + // new SqlExpression[] + // { + // leftSql, + // Dependencies.SqlExpressionFactory.Add( + // Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(rightSql), + // Dependencies.SqlExpressionFactory.Constant(1)), + // Dependencies.SqlExpressionFactory.Constant(1) + // }, + // nullable: true, + // argumentsPropagateNullability: new[] { true, true, true }, + // typeof(byte[])) + // }, + // nullable: true, + // argumentsPropagateNullability: new[] { true }, + // binaryExpression.Type); + // } + //} + if (!(base.VisitBinary(binaryExpression) is SqlExpression visitedExpression)) { return QueryCompilationContext.NotTranslatedExpression; diff --git a/test/EFCore.Specification.Tests/MonsterFixupTestBase.cs b/test/EFCore.Specification.Tests/MonsterFixupTestBase.cs index a93f88c6629..d7da06b1743 100644 --- a/test/EFCore.Specification.Tests/MonsterFixupTestBase.cs +++ b/test/EFCore.Specification.Tests/MonsterFixupTestBase.cs @@ -519,11 +519,17 @@ public virtual void Composite_fixup_happens_when_FKs_change_test() var productReview2 = context.ProductReviews.Single(e => e.Review.StartsWith("Good")); var productReview3 = context.ProductReviews.Single(e => e.Review.StartsWith("Eeky")); - // Issue #16428 - var productPhotos = context.ProductPhotos.ToList(); - var productPhoto1 = productPhotos.Single(e => e.Photo[0] == 101); - var productPhoto2 = productPhotos.Single(e => e.Photo[0] == 103); - var productPhoto3 = productPhotos.Single(e => e.Photo[0] == 105); + // See issue#16428 + var sqlite = context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite"; + var productPhoto1 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 101) + : context.ProductPhotos.Single(e => e.Photo[0] == 101); + var productPhoto2 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 103) + : context.ProductPhotos.Single(e => e.Photo[0] == 103); + var productPhoto3 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 105) + : context.ProductPhotos.Single(e => e.Photo[0] == 105); var productWebFeature1 = context.ProductWebFeatures.Single(e => e.Heading.StartsWith("Waffle")); var productWebFeature2 = context.ProductWebFeatures.Single(e => e.Heading.StartsWith("What")); @@ -834,10 +840,19 @@ protected void SimpleVerification() new[] { "Better than Tarqies!", "Eeky says yes!", "Good with maple syrup." }, context.ProductReviews.Select(c => c.Review).OrderBy(n => n)); - // Issue #16428 - Assert.Equal( - new[] { "101", "103", "105" }, - context.ProductPhotos.ToList().Select(c => c.Photo.First().ToString()).OrderBy(n => n)); + // See issue#16428 + if (context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite") + { + Assert.Equal( + new[] { "101", "103", "105" }, + context.ProductPhotos.ToList().Select(c => c.Photo.First().ToString()).OrderBy(n => n)); + } + else + { + Assert.Equal( + new[] { "101", "103", "105" }, + context.ProductPhotos.Select(c => c.Photo.First().ToString()).OrderBy(n => n)); + } Assert.Equal( new[] { "Waffle Style", "What does the waffle say?" }, @@ -847,7 +862,6 @@ protected void SimpleVerification() new[] { "Ants By Boris", "Trading As Trent" }, context.Suppliers.Select(c => c.Name).OrderBy(n => n)); - // Issue #16428 Assert.Equal( new[] { "201", "202" }, context.SupplierLogos.ToList().SelectMany(c => c.Logo).Select(l => l.ToString()).OrderBy(n => n)); @@ -1027,11 +1041,17 @@ protected void FkVerification() Assert.Equal(product1.ProductId, productReview2.ProductId); Assert.Equal(product2.ProductId, productReview3.ProductId); - // Issue #16428 - var productPhotos = context.ProductPhotos.ToList(); - var productPhoto1 = productPhotos.Single(e => e.Photo[0] == 101); - var productPhoto2 = productPhotos.Single(e => e.Photo[0] == 103); - var productPhoto3 = productPhotos.Single(e => e.Photo[0] == 105); + // See issue#16428 + var sqlite = context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite"; + var productPhoto1 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 101) + : context.ProductPhotos.Single(e => e.Photo[0] == 101); + var productPhoto2 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 103) + : context.ProductPhotos.Single(e => e.Photo[0] == 103); + var productPhoto3 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 105) + : context.ProductPhotos.Single(e => e.Photo[0] == 105); Assert.Equal(product1.ProductId, productPhoto1.ProductId); Assert.Equal(product1.ProductId, productPhoto2.ProductId); @@ -1050,8 +1070,9 @@ protected void FkVerification() var supplier1 = context.Suppliers.Single(e => e.Name.StartsWith("Trading")); var supplier2 = context.Suppliers.Single(e => e.Name.StartsWith("Ants")); - // Issue #16428 - var supplierLogo1 = context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201); + var supplierLogo1 = sqlite + ? context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201) + : context.SupplierLogos.Single(e => e.Logo[0] == 201); Assert.Equal(supplier1.SupplierId, supplierLogo1.SupplierId); @@ -1293,11 +1314,17 @@ protected void NavigationVerification() Assert.True(product3.Reviews == null || product3.Reviews.Count == 0); - // Issue #16428 - var productPhotos = context.ProductPhotos.ToList(); - var productPhoto1 = productPhotos.Single(e => e.Photo[0] == 101); - var productPhoto2 = productPhotos.Single(e => e.Photo[0] == 103); - var productPhoto3 = productPhotos.Single(e => e.Photo[0] == 105); + // See issue#16428 + var sqlite = context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite"; + var productPhoto1 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 101) + : context.ProductPhotos.Single(e => e.Photo[0] == 101); + var productPhoto2 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 103) + : context.ProductPhotos.Single(e => e.Photo[0] == 103); + var productPhoto3 = sqlite + ? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 105) + : context.ProductPhotos.Single(e => e.Photo[0] == 105); Assert.Equal( new[] { productPhoto1, productPhoto2 }, @@ -1327,8 +1354,9 @@ protected void NavigationVerification() var supplier1 = context.Suppliers.Single(e => e.Name.StartsWith("Trading")); var supplier2 = context.Suppliers.Single(e => e.Name.StartsWith("Ants")); - // Issue #16428 - var supplierLogo1 = context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201); + var supplierLogo1 = sqlite + ? context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201) + : context.SupplierLogos.Single(e => e.Logo[0] == 201); Assert.Same(supplierLogo1, supplier1.Logo);