Skip to content
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

Dynamic component with nullable property always causes an update on flush #3421

Open
nfplee opened this issue Aug 30, 2023 · 2 comments
Open

Comments

@nfplee
Copy link

nfplee commented Aug 30, 2023

I've noticed whilst profiling recently that I keep getting random calls to update particular entities when I flush changes to the database, even if they haven't changed.

After looking into this further, i've discovered this seems to happen when the entity has a dynamic component with nullable properties.

For example, say I have the following application:

using var sessionFactory = BuildConfiguration().BuildSessionFactory();
using var session = sessionFactory.OpenSession();

var products = await session.Query<Product>().ToListAsync();

await session.FlushAsync();

Console.ReadLine();

Configuration BuildConfiguration() {
    var configuration = new Configuration();

    configuration.DataBaseIntegration(db => {
        db.ConnectionString = @"Data Source=TDB2008R2;Initial Catalog=KIT5-TEMP;Persist Security Info=True;User ID=sa;Password=m3spexec4@sadrAb;Connect Timeout=30";
        db.Dialect<MsSql2008Dialect>();
    });

    var mapper = new ModelMapper();
    mapper.AddMappings(Assembly.GetCallingAssembly().ExportedTypes);
    configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());

    return configuration;
}

With the following model and mapping:

public class Product {
    public virtual int Id { get; set; }
    public virtual string Name { get; set; } = default!;

    private IDictionary<string, object?>? _attributes;
    public virtual IDictionary<string, object?> Attributes {
        get => _attributes ??= new Dictionary<string, object?>();
        set => _attributes = value;
    }
}

public class ProductMapping : ClassMapping<Product> {
    public ProductMapping() {
        Table("Products");
        Id(x => x.Id, m => m.Generator(Generators.Identity));
        Property(x => x.Name);
        Component(x => x.Attributes, new {
            Sku = (string?)null
        }, dc => {
            dc.Property(x => x.Sku);
        });
    }
}

Here's the SQL to setup the database table with some sample data:

CREATE TABLE [dbo].[Products](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
    [Sku] [nvarchar](50) NULL,
    CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED ([Id] ASC)
)

INSERT INTO [dbo].[Products] ([Name], [Sku]) VALUES ('Test 1', NULL)
INSERT INTO [dbo].[Products] ([Name], [Sku]) VALUES ('Test 2', 'ABC')
INSERT INTO [dbo].[Products] ([Name], [Sku]) VALUES ('Test 3', NULL)

Now when profiling the application, notice how it will issue 2 updates (only the products where the Sku is null) to the database when you flush the changes.

This seems like a bug to me but I thought I'd check before creating a test case.

I have discovered that this isn't an issue if I change my Attributes property to the following:

public virtual IDictionary<string, object?> Attributes { get; set; } = new Dictionary<string, object?>();

However this will still set the Attributes to null against the products where all the values of the Attributes are null and will likely lead to much bigger problems.

@nfplee nfplee changed the title Dynamic component with nullable property always causes a update on flush Dynamic component with nullable property always causes an update on flush Aug 30, 2023
@bahusoid
Copy link
Member

bahusoid commented Sep 1, 2023

However this will still set the Attributes to null against the products where all the values of the Attributes are null and will likely lead to much bigger problems.

#2112 looks related

@nfplee
Copy link
Author

nfplee commented Mar 6, 2024

Please see the following test case:

GH3421.zip

I used an SqlInterceptor to monitor the queries sent to the database. The test confirms that when flushing changes it will trigger an update for any entity where all the values are null against a dynamic component, even if it hasn't changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants