Skip to content

Incorrect value method HasDefaultValue for indexer property #23191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
voronov-maxim opened this issue Nov 4, 2020 · 5 comments · Fixed by #24620
Closed

Incorrect value method HasDefaultValue for indexer property #23191

voronov-maxim opened this issue Nov 4, 2020 · 5 comments · Fixed by #24620

Comments

@voronov-maxim
Copy link
Contributor

The ClrPropertyGetter.HasDefaultValue method for an indexer property always returns false, although it should return true.
Perhaps this is due to the fact that the property has the wrong type "Object", but must be "Int32", "String", etc.
As a result, when I try to add a new entity to the database with the "DatabaseGeneratedOption.Identity" indexer property I get an exception:

Unhandled exception. Microsoft.Data.SqlClient.SqlException:: Cannot insert explicit value for identity column in "Categories" table when IDENTITY_INSERT is OFF,
   at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)

EF Core version: 5.0.0-rtm.20509.3
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 5.0
Operating system: Windows 10 Pro 2004
IDE: Visual Studio 2019 16.8.0 Preview 6.0

@ajcvickers
Copy link
Contributor

@voronov-maxim I am not able to reproduce this--see my code below. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

public class BlogContext : DbContext
{
    public DbSet<Dictionary<string, object>> Blogs 
        => Set<Dictionary<string, object>>("Blog");

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(Your.ConnectionString)
            .EnableSensitiveDataLogging()
            .LogTo(Console.WriteLine, LogLevel.Information);

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Blog", b =>
        {
            b.Property<int>("Id");
        });
    }
}

public class Program
{
    public static void Main()
    {
        using (var context = new BlogContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            context.Blogs.Add(new Dictionary<string, object>());
            context.SaveChanges();
        }

        using (var context = new BlogContext())
        {
            Console.WriteLine(context.Blogs.Single()["Id"]);
        }
    }
}

Output:

/home/ajcvickers/.dotnet/dotnet /home/ajcvickers/AllTogetherNow/Daily/bin/Debug/netcoreapp3.1/Daily.dll
warn: 11/5/2020 14:44:01.305 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 11/5/2020 14:44:01.399 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Entity Framework Core 5.0.0-rtm.20509.3 initialized 'BlogContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: SensitiveDataLoggingEnabled 
info: 11/5/2020 14:44:01.636 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 11/5/2020 14:44:01.681 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (8ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [Test] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
      END;
info: 11/5/2020 14:44:01.718 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (37ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      DROP DATABASE [Test];
info: 11/5/2020 14:44:02.027 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (288ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      CREATE DATABASE [Test];
info: 11/5/2020 14:44:02.148 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (120ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [Test] SET READ_COMMITTED_SNAPSHOT ON;
      END;
info: 11/5/2020 14:44:02.158 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 11/5/2020 14:44:02.218 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [Blog] (
          [Id] int NOT NULL IDENTITY,
          CONSTRAINT [PK_Blog] PRIMARY KEY ([Id])
      );
info: 11/5/2020 14:44:02.316 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      INSERT INTO [Blog]
      DEFAULT VALUES;
      SELECT [Id]
      FROM [Blog]
      WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
info: 11/5/2020 14:44:02.342 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Entity Framework Core 5.0.0-rtm.20509.3 initialized 'BlogContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: SensitiveDataLoggingEnabled 
info: 11/5/2020 14:44:02.474 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT TOP(2) [b].[Id]
      FROM [Blog] AS [b]
1

@voronov-maxim
Copy link
Contributor Author

voronov-maxim commented Nov 6, 2020

public class BlogContext : DbContext
{
    public DbSet<Dictionary<string, object>> Blogs
        => Set<Dictionary<string, object>>("Blog");
    public DbSet<Dictionary<string, object>> Post
        => Set<Dictionary<string, object>>("Post");

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(@"Server=.\sqlexpress;Initial Catalog=Test;Trusted_Connection=Yes;")
            .EnableSensitiveDataLogging()
            .LogTo(Console.WriteLine, LogLevel.Information);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Blog", b =>
        {
            b.IndexerProperty<int>("Id");
        });

        modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Post", b =>
        {
            b.IndexerProperty<int>("Id");
            b.IndexerProperty<int>("BlogId");

            b.HasOne("Blog", null).WithMany();
        });

        IMutableEntityType blogEntityType = modelBuilder.Model.FindEntityType("Blog");
        //FixHasDefaultValue(blogEntityType.GetProperty("Id"));
    }

    private static void FixHasDefaultValue(IProperty efProperty)
    {
        IClrPropertyGetter propertyGetter = efProperty.GetGetter();
        FieldInfo hasDefaultValue = propertyGetter.GetType().GetField("_hasDefaultValue", BindingFlags.Instance | BindingFlags.NonPublic)!;
        hasDefaultValue.SetValue(propertyGetter, GetDefaultValueFunc(efProperty));
    }

    private static Delegate GetDefaultValueFunc(IProperty property)
    {
        ParameterExpression parameterExpression = Expression.Parameter(property.DeclaringEntityType.ClrType);
        Expression expression = PropertyBase.CreateMemberAccess(property, parameterExpression, property.PropertyInfo);
        expression = Expression.Convert(expression, property.ClrType);
        expression = expression.MakeHasDefaultValue(property);
        return Expression.Lambda(expression, new[] { parameterExpression }).Compile();
    }
}

class Program
{
    public static void Main()
    {
        using (var context = new BlogContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            EntityEntry<Dictionary<string, object>> blog = context.Blogs.Add(new Dictionary<string, object>() { { "Id", -1 } });
            blog.Property("Id").IsTemporary = true;
            context.Post.Add(new Dictionary<string, object>() { { "BlogId", -1 } });

            context.SaveChanges();
        }

        using (var context = new BlogContext())
        {
            Console.WriteLine("Id = " + context.Blogs.Single()["Id"] + "; BlogId = " + context.Post.Single()["BlogId"]);
        }
    }
}

Fix bug uncomment //FixHasDefaultValue(blogEntityType.GetProperty("Id"));
I can make PR to fix this bug.

@ajcvickers
Copy link
Contributor

Note for triage: the root cause here is an inconsistency in "default value" checking. The actual value is set to zero (the metadata property type default) when configuring the current value as temporary and then checked against null (the CLR default).

@ajcvickers ajcvickers self-assigned this Nov 9, 2020
@ajcvickers ajcvickers added this to the 6.0.0 milestone Nov 9, 2020
@umitkavala
Copy link
Contributor

@ajcvickers Is it related with #23226 ?

@ajcvickers
Copy link
Contributor

@umitkavala I'm not sure. I haven't investigated #23226 deeply.

@ajcvickers ajcvickers modified the milestones: 6.0.0, 6.0.0-preview4 Apr 8, 2021
ajcvickers added a commit that referenced this issue Apr 8, 2021
This allows values explicitly set by the application as temporary to be stored in and obtained from the entity instance.

Fixes #23191
Fixes #24245
ajcvickers added a commit that referenced this issue Apr 19, 2021
This allows values explicitly set by the application as temporary to be stored in and obtained from the entity instance.

Fixes #23191
Fixes #24245
ajcvickers added a commit that referenced this issue Apr 19, 2021
This allows values explicitly set by the application as temporary to be stored in and obtained from the entity instance.

Fixes #23191
Fixes #24245
@ajcvickers ajcvickers modified the milestones: 6.0.0-preview4, 6.0.0 Nov 8, 2021
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants