diff --git a/src/Application/Common/Interfaces/Serialization/DefaultJsonSerializerOptions.cs b/src/Application/Common/Interfaces/Serialization/DefaultJsonSerializerOptions.cs index 0be28be0e..7ef86e35c 100644 --- a/src/Application/Common/Interfaces/Serialization/DefaultJsonSerializerOptions.cs +++ b/src/Application/Common/Interfaces/Serialization/DefaultJsonSerializerOptions.cs @@ -1,19 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Encodings.Web; +using System.Text.Encodings.Web; using System.Text.Json.Serialization; using System.Text.Json; using System.Text.Unicode; -using System.Threading.Tasks; -using Newtonsoft.Json; namespace CleanArchitecture.Blazor.Application.Common.Interfaces.Serialization; public class DefaultJsonSerializerOptions { public static JsonSerializerOptions Options => new() { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, diff --git a/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs b/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs index 67a05448b..9a8e1eae7 100644 --- a/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs +++ b/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs @@ -40,8 +40,7 @@ IMapper mapper _context = context; _mapper = mapper; } -#pragma warning disable CS8602 -#pragma warning disable CS8604 + public async Task> Handle(KeyValuesWithPaginationQuery request, CancellationToken cancellationToken) { diff --git a/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs b/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs index cd3a684d5..b186a74e4 100644 --- a/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs +++ b/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs @@ -42,7 +42,7 @@ public async Task> Handle(AddEditProductCommand request, Cancellatio var dto = _mapper.Map(request); if (request.Id > 0) { - var item = await _context.Products.FindAsync(new object[] { request.Id }, cancellationToken) ?? throw new NotFoundException($"Product with id: {request.Id} not found."); + var item = await _context.Products.Include(x=>x.Pictures).SingleOrDefaultAsync(x=>x.Id==request.Id) ?? throw new NotFoundException($"Product with id: {request.Id} not found."); item = _mapper.Map(dto, item); item.AddDomainEvent(new UpdatedEvent(item)); await _context.SaveChangesAsync(cancellationToken); diff --git a/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs b/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs index bdd40f315..d2a44b88a 100644 --- a/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs +++ b/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs @@ -54,6 +54,7 @@ public async Task> Handle(ExportProductsQuery request, Cancellati { var data = await _context.Products.ApplyOrder(request) .ProjectTo(_mapper.ConfigurationProvider) + .AsNoTracking() .ToListAsync(cancellationToken); diff --git a/src/Domain/Entities/Audit/AuditTrail.cs b/src/Domain/Entities/Audit/AuditTrail.cs index 3f937fe47..46021b0dd 100644 --- a/src/Domain/Entities/Audit/AuditTrail.cs +++ b/src/Domain/Entities/Audit/AuditTrail.cs @@ -17,7 +17,7 @@ public class AuditTrail : IEntity public DateTime DateTime { get; set; } public Dictionary? OldValues { get; set; } public Dictionary? NewValues { get; set; } - public ICollection? AffectedColumns { get; set; } + public List? AffectedColumns { get; set; } public Dictionary PrimaryKey { get; set; } = new(); public List TemporaryProperties { get; } = new(); diff --git a/src/Infrastructure/Persistence/Configurations/AuditTrailConfiguration.cs b/src/Infrastructure/Persistence/Configurations/AuditTrailConfiguration.cs index 575791a94..947ff33e5 100644 --- a/src/Infrastructure/Persistence/Configurations/AuditTrailConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/AuditTrailConfiguration.cs @@ -1,12 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Text.Encodings.Web; using System.Text.Json; -using System.Text.Unicode; using CleanArchitecture.Blazor.Application.Common.Interfaces.Serialization; -using CleanArchitecture.Blazor.Infrastructure.Services.Serialization; -using Microsoft.EntityFrameworkCore.ChangeTracking; +using CleanArchitecture.Blazor.Infrastructure.Persistence.Conversions; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace CleanArchitecture.Blazor.Infrastructure.Persistence.Configurations; @@ -22,31 +19,10 @@ public void Configure(EntityTypeBuilder builder) builder.Navigation(e => e.Owner).AutoInclude(); builder.Property(t => t.AuditType) .HasConversion(); - builder.Property(e => e.AffectedColumns) - .HasConversion( - v => JsonSerializer.Serialize(v, DefaultJsonSerializerOptions.Options), - v => JsonSerializer.Deserialize>(v, DefaultJsonSerializerOptions.Options), - new ValueComparer>( - (c1, c2) => c1.SequenceEqual(c2), - c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), - c => (ICollection)c.ToList())); - - builder.Property(u => u.OldValues) - .HasConversion( - d => JsonSerializer.Serialize(d, DefaultJsonSerializerOptions.Options), - s => JsonSerializer.Deserialize>(s, DefaultJsonSerializerOptions.Options) - ); - builder.Property(u => u.NewValues) - .HasConversion( - d => JsonSerializer.Serialize(d, DefaultJsonSerializerOptions.Options), - s => JsonSerializer.Deserialize>(s, DefaultJsonSerializerOptions.Options) - ); - builder.Property(u => u.PrimaryKey) - .HasConversion( - d => JsonSerializer.Serialize(d, DefaultJsonSerializerOptions.Options), - s => JsonSerializer.Deserialize>(s, DefaultJsonSerializerOptions.Options) - ); - + builder.Property(e => e.AffectedColumns).HasStringListConversion(); + builder.Property(u => u.OldValues).HasJsonConversion(); + builder.Property(u => u.NewValues).HasJsonConversion(); + builder.Property(u => u.PrimaryKey).HasJsonConversion(); builder.Ignore(x => x.TemporaryProperties); builder.Ignore(x => x.HasTemporaryProperties); } diff --git a/src/Infrastructure/Persistence/Configurations/ProductConfiguration.cs b/src/Infrastructure/Persistence/Configurations/ProductConfiguration.cs index 60f412e0b..03965b50f 100644 --- a/src/Infrastructure/Persistence/Configurations/ProductConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/ProductConfiguration.cs @@ -3,6 +3,7 @@ using System.Text.Json; using CleanArchitecture.Blazor.Application.Common.Interfaces.Serialization; +using CleanArchitecture.Blazor.Infrastructure.Persistence.Conversions; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -14,5 +15,7 @@ public void Configure(EntityTypeBuilder builder) { builder.Ignore(e => e.DomainEvents); builder.OwnsMany(e => e.Pictures, build => build.ToJson()); + //builder.Property(x=>x.Pictures).HasJsonConversion(); + } } diff --git a/src/Infrastructure/Persistence/Conversions/ValueConversionExtensions.cs b/src/Infrastructure/Persistence/Conversions/ValueConversionExtensions.cs new file mode 100644 index 000000000..7672400bc --- /dev/null +++ b/src/Infrastructure/Persistence/Conversions/ValueConversionExtensions.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using CleanArchitecture.Blazor.Application.Common.Interfaces.Serialization; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace CleanArchitecture.Blazor.Infrastructure.Persistence.Conversions; +public static class ValueConversionExtensions +{ + public static PropertyBuilder HasJsonConversion(this PropertyBuilder propertyBuilder) + { + ValueConverter converter = new ValueConverter( + v => JsonSerializer.Serialize(v, DefaultJsonSerializerOptions.Options), + v => string.IsNullOrEmpty(v)? default: JsonSerializer.Deserialize(v, DefaultJsonSerializerOptions.Options)); + + ValueComparer comparer = new ValueComparer( + (l, r) => JsonSerializer.Serialize(l, DefaultJsonSerializerOptions.Options) == JsonSerializer.Serialize(r, DefaultJsonSerializerOptions.Options), + v => v == null ? 0 : JsonSerializer.Serialize(v, DefaultJsonSerializerOptions.Options).GetHashCode(), + v => JsonSerializer.Deserialize(JsonSerializer.Serialize(v, DefaultJsonSerializerOptions.Options), DefaultJsonSerializerOptions.Options)); + + propertyBuilder.HasConversion(converter); + propertyBuilder.Metadata.SetValueConverter(converter); + propertyBuilder.Metadata.SetValueComparer(comparer); + return propertyBuilder; + } + + public static PropertyBuilder> HasStringListConversion(this PropertyBuilder> propertyBuilder) + { + ValueConverter, String> converter = new ValueConverter, String>( + v => JsonSerializer.Serialize(v, DefaultJsonSerializerOptions.Options), + v => string.IsNullOrEmpty(v) ? default : JsonSerializer.Deserialize>(v, DefaultJsonSerializerOptions.Options)); + + ValueComparer> comparer = new ValueComparer>( + (l, r) => JsonSerializer.Serialize(l, DefaultJsonSerializerOptions.Options) == JsonSerializer.Serialize(r, DefaultJsonSerializerOptions.Options), + v => v == null ? 0 : JsonSerializer.Serialize(v, DefaultJsonSerializerOptions.Options).GetHashCode(), + v => JsonSerializer.Deserialize>(JsonSerializer.Serialize(v, DefaultJsonSerializerOptions.Options), DefaultJsonSerializerOptions.Options)); + + propertyBuilder.HasConversion(converter); + propertyBuilder.Metadata.SetValueConverter(converter); + propertyBuilder.Metadata.SetValueComparer(comparer); + return propertyBuilder; + } +}