Skip to content

EF Core 7.0: Wrong SQL generated for string.IsNullOrWhitespace #296

Closed
@arthur-liberman

Description

@arthur-liberman

The provider generates wrong sql when mapper contains string.IsNullOrWhiteSpace and NCLOB.

return dbContext.Items.Select(i => new Dto
{
    Id = i.Id,
    Name = i.Name,
    Data = string.IsNullOrWhiteSpace(i.Data) ? "N/A" : ProcessData(i.Data),
    Colors = $"[ {string.Join(", ", i.Colors)} ]"
}).ToList();

We get the following error: ORA-00932: inconsistent datatypes: expected - got NCLOB
The generated SQL:

SELECT "i"."id", "i"."name", CASE
    WHEN (("i"."data" IS NULL) OR (LTRIM(RTRIM("i"."data")) = N'')) THEN 1
    ELSE 0
END, "i"."data", "c"."id", "c"."ItemId", "c"."name"
FROM "Item" "i"
LEFT JOIN "Color" "c" ON "i"."id" = "c"."ItemId"
ORDER BY "i"."id"

A 3rd party provider doesn't exhibit similar issues.
Full code to reproduce:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;

namespace Oracle_Guid
{
    internal class Program
    {
        static void Main(string[] args)
        {
            test_oracle_isnull_lob();
            Console.WriteLine("Hello, World!");
        }

        private static void test_oracle_isnull_lob()
        {
            new isnull_lob_issue.Seeder().Seed();
            var items = new isnull_lob_issue.Logic().Query();
        }
    }

    internal class isnull_lob_issue
    {
        public class Logic
        {
            public IList<Dto> Query()
            {
                using (var dbContext = new MyDbContext())
                {
                    return dbContext.Items.Select(i => new Dto
                    {
                        Id = i.Id,
                        Name = i.Name,
                        Data = string.IsNullOrWhiteSpace(i.Data) ? "N/A" : ProcessData(i.Data),
                        Colors = $"[ {string.Join(", ", i.Colors)} ]"
                    }).ToList();
                }
            }

            private static string ProcessData(string data) => data;
        }

        public class Seeder
        {
            public void Seed()
            {
                using (var dbContext = new MyDbContext())
                {
                    var rdc = dbContext.Database.GetService<IRelationalDatabaseCreator>();
                    if (rdc.Exists())
                        rdc.EnsureDeleted();
                }
                using (var dbContext = new MyDbContext())
                {
                    var rdc = dbContext.Database.GetService<IRelationalDatabaseCreator>();
                    rdc.EnsureCreated();
                    if (!dbContext.Items.Any())
                    {
                        for (int i = 0; i < 10; i++)
                        {
                            var rand = new Random();
                            int cat = rand.Next() % 3;
                            int colors = rand.Next() % 4;
                            var item = new Item { Uid = Guid.NewGuid(), CategoryId = cat, Name = $"Item {i}", Data = cat % 2 != 0 ? $"Data for Item {i}" : " " };
                            for (int c = 0; c <= colors; c++)
                            {
                                item.AddColor(c);
                            }
                            dbContext.Items.Add(item);
                        }
                    }
                    if (!dbContext.Categories.Any())
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            var category = new Category { Name = $"Category {i}" };
                            dbContext.Categories.Add(category);
                        }
                    }
                    dbContext.SaveChanges();
                }
            }
        }

        public class Dto
        {
            public int Id { get; set; }
            public Guid Uid { get; set; }
            public string Name { get; set; }
            public string Colors { get; set; }
            public string Data { get; set; }
        }

        public class Item
        {
            private readonly List<Color> _colors = new List<Color>();
            public IReadOnlyCollection<Color> Colors => _colors;
            public int Id { get; set; }
            public Guid? Uid { get; set; }
            public int CategoryId { get; set; }
            public string Name { get; set; }
            public string Data { get; set; }

            internal void AddColor(int i)
            {
                var color = new Color { Name = $"Color {i}" };
                _colors.Add(color);
            }
        }

        public class Color
        {
            public int ItemId { get; private set; }
            public virtual Item Item { get; private set; }
            public int Id { get; set; }
            public string Name { get; set; }
        }

        public class Category
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

        public class MyDbContext : DbContext
        {
            public static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole();
            });

            public DbSet<Item> Items { get; set; }
            public DbSet<Color> Colors { get; set; }
            public DbSet<Category> Categories { get; set; }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.ApplyConfiguration(new ItemConfiguration());
                modelBuilder.ApplyConfiguration(new CategoryConfiguration());
                modelBuilder.ApplyConfiguration(new ColorConfiguration());
            }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder
                    .UseLoggerFactory(loggerFactory)
                    .EnableSensitiveDataLogging()
                    .UseOracle(OracleConstants.ORACLE_CONNECTION);
            }
        }

        public class ItemConfiguration : IEntityTypeConfiguration<Item>
        {
            public void Configure(EntityTypeBuilder<Item> builder)
            {
                builder.ToTable("Item");
                builder.HasKey(o => o.Id);
                builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
                builder.Property(t => t.Uid).HasColumnName("uid").IsRequired(false);
                builder.Property(t => t.CategoryId).HasColumnName("categoryId");
                builder.Property(t => t.Name).HasColumnName("name").HasMaxLength(255);
                builder.Property(t => t.Data).HasColumnName("data").HasMaxLength(int.MaxValue).IsRequired(false);
            }
        }

        public class ColorConfiguration : IEntityTypeConfiguration<Color>
        {
            public void Configure(EntityTypeBuilder<Color> builder)
            {
                builder.ToTable("Color");
                builder.HasKey(o => o.Id);
                builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
                builder.Property(t => t.Name).HasColumnName("name").HasMaxLength(255);

                builder.HasOne(s => s.Item)
                    .WithMany(s => s.Colors)
                    .HasForeignKey(s => s.ItemId);
            }
        }

        public class CategoryConfiguration : IEntityTypeConfiguration<Category>
        {
            public void Configure(EntityTypeBuilder<Category> builder)
            {
                builder.ToTable("Category");
                builder.HasKey(o => o.Id);
                builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
                builder.Property(t => t.Name).HasColumnName("name").HasMaxLength(255);
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions