From 5760f6d420e7742a222e28b3c110b22201fb884e Mon Sep 17 00:00:00 2001 From: thepirat000 Date: Wed, 25 Sep 2024 17:06:02 -0600 Subject: [PATCH] Audit.EntityFramework.Core: Fixing issue with Complex Type properties when using the EF DataProvider Property Matching (#697) --- CHANGELOG.md | 3 + Directory.Build.props | 2 +- .../Providers/EntityFrameworkDataProvider.cs | 21 +++++- .../Contexts.cs | 14 ++++ .../EfCoreTests.cs | 68 +++++++++++++++++++ 5 files changed, 105 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1914bd7b..fa091a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to Audit.NET and its extensions will be documented in this f The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). +## [27.0.3] - 2024-09-25: +- Audit.EntityFramework.Core: Fixing issue with Complex Type properties when using the EF DataProvider Property Matching (#697) + ## [27.0.2] - 2024-09-18: - Audit.NET.PostgreSql: Fixing issue with the PostgreSql data provider when using String data column (#695) diff --git a/Directory.Build.props b/Directory.Build.props index 8ed05d02..06e4b449 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 27.0.2 + 27.0.3 false diff --git a/src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs b/src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs index b65b5d62..5a590b4d 100644 --- a/src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs +++ b/src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs @@ -310,9 +310,13 @@ private void SetAuditEntityMatchedProperties(Type definingType, EventEntry entry var entity = entry.Entry.Entity; var auditFields = GetPropertiesToSet(auditType); var columnValues = entry.ColumnValues; + #if EF_CORE - var entityFields = entry.GetEntry().Metadata.GetProperties(); - foreach (var prop in entityFields.Where(af => auditFields.ContainsKey(af.Name))) + // Map scalar properties + var entityProperties = entry.GetEntry().Metadata.GetProperties() + .Where(prop => auditFields.ContainsKey(prop.Name)); + + foreach (var prop in entityProperties) { var colName = DbContextHelper.GetColumnName(prop); var value = columnValues.TryGetValue(colName, out var columnValue) ? columnValue : prop.PropertyInfo?.GetValue(entity); @@ -326,6 +330,19 @@ private void SetAuditEntityMatchedProperties(Type definingType, EventEntry entry auditFields[prop.Key].SetValue(auditEntity, value); } #endif + +#if EF_CORE_8_OR_GREATER + // Map complex properties + var entityComplexProperties = entry.GetEntry().Metadata.GetComplexProperties() + .Where(prop => auditFields.ContainsKey(prop.Name)); + + foreach (var prop in entityComplexProperties) + { + var value = prop.PropertyInfo?.GetValue(entity); + + auditFields[prop.Name].SetValue(auditEntity, value); + } +#endif } private Dictionary GetPropertiesToSet(Type type) diff --git a/test/Audit.EntityFramework.Core.UnitTest/Contexts.cs b/test/Audit.EntityFramework.Core.UnitTest/Contexts.cs index 312a045e..90b5bb39 100644 --- a/test/Audit.EntityFramework.Core.UnitTest/Contexts.cs +++ b/test/Audit.EntityFramework.Core.UnitTest/Contexts.cs @@ -37,7 +37,20 @@ public record Country public string Alias { get; init; } } + [AuditIgnore] + public class AuditLog + { + [Key] + public int AuditId { get; set; } + public string TableName { get; set; } + public string Action { get; set; } + [Required] + public Address Address { get; set; } + public string Name { get; set; } + } + public DbSet People { get; set; } + public DbSet AuditLogs { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -51,6 +64,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().ComplexProperty(e => e.Address).ComplexProperty(a => a.Country); + modelBuilder.Entity().ComplexProperty(e => e.Address).ComplexProperty(a => a.Country); } } #endif diff --git a/test/Audit.EntityFramework.Core.UnitTest/EfCoreTests.cs b/test/Audit.EntityFramework.Core.UnitTest/EfCoreTests.cs index bbf1660d..4468369f 100644 --- a/test/Audit.EntityFramework.Core.UnitTest/EfCoreTests.cs +++ b/test/Audit.EntityFramework.Core.UnitTest/EfCoreTests.cs @@ -30,6 +30,13 @@ public void Setup() new DemoContext().Database.EnsureCreated(); } + [OneTimeTearDown] + public void TearDown() + { + new BlogsContext().Database.EnsureDeleted(); + new DemoContext().Database.EnsureDeleted(); + } + #if EF_CORE_8_OR_GREATER [Test] public void Test_EF_ComplexType() @@ -88,7 +95,62 @@ public void Test_EF_ComplexType() Assert.That(evs[1].Entries[0].Changes.FirstOrDefault(ch => ch.ColumnName == "Address_Country_Alias")?.OriginalValue, Is.EqualTo("AU")); Assert.That(evs[1].Entries[0].Changes.FirstOrDefault(ch => ch.ColumnName == "Address_Country_Alias")?.NewValue, Is.EqualTo("NEWALIAS")); + + context.Database.EnsureDeleted(); } + + [Test] + public void Test_EF_ComplexType_EntityFrameworkDataProvider_PropertyMatching() + { + // Arrange + Audit.Core.Configuration.Setup().UseEntityFramework(ef => ef + .AuditTypeExplicitMapper(m => m + .Map() + .AuditEntityAction((ev, entry, auditLog) => + { + auditLog.TableName = entry.Table; + auditLog.Action = entry.Action; + })) + .IgnoreMatchedProperties(false)); + + using var context = new Context_ComplexTypes(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + + var name = Guid.NewGuid().ToString(); + var city = "Rosario"; + var alias = "alias"; + + // Act + var person = new Context_ComplexTypes.Person() + { + Id = 10, + Name = name, + Address = new Context_ComplexTypes.Address() + { + Country = new Context_ComplexTypes.Country() { Name = "Argentina", Alias = alias }, + City = city, + Line1 = "Street", + PostCode = "1234" + } + }; + + context.People.Add(person); + context.SaveChanges(); + var auditLogs = context.AuditLogs.ToList(); + + context.Database.EnsureDeleted(); + + // Assert + Assert.That(auditLogs, Has.Count.EqualTo(1)); + Assert.That(auditLogs[0].Action, Is.EqualTo("Insert")); + Assert.That(auditLogs[0].Name, Is.EqualTo(name)); + Assert.That(auditLogs[0].Address, Is.Not.Null); + Assert.That(auditLogs[0].Address.City, Is.EqualTo(city)); + Assert.That(auditLogs[0].Address.Country, Is.Not.Null); + Assert.That(auditLogs[0].Address.Country.Alias, Is.EqualTo(alias)); + } + #endif #if EF_CORE_5_OR_GREATER @@ -173,6 +235,8 @@ public void Test_EF_Core_ManyToMany_NoJoinEntity() }; context.Posts.Add(post); context.SaveChanges(); + + context.Database.EnsureDeleted(); } Assert.That(evs.Count, Is.EqualTo(1)); @@ -246,6 +310,8 @@ public void Test_EF_Core_ManyToMany_NoJoinEntity_EFProvider() Assert.True(context.Audit_PostTags.Any(pt => pt.TagsId == 101 && pt.PostsId == "10" && pt.Action == "Insert" && pt.Extra == "extra")); Assert.True(context.Audit_PostTags.Any(pt => pt.TagsId == 102 && pt.PostsId == "10" && pt.Action == "Insert" && pt.Extra == "extra")); Assert.True(context.Audit_PostTags.Any(pt => pt.TagsId == 101 && pt.PostsId == "10" && pt.Action == "Delete" && pt.Extra == "extra")); + + context.Database.EnsureDeleted(); } } @@ -278,6 +344,8 @@ public void Test_EF_Core_OwnedSingleMultiple() }); context.SaveChanges(); + + context.Database.EnsureDeleted(); } Assert.That(evs.Count, Is.EqualTo(1));