diff --git a/README.md b/README.md index ec83d365..1914f8bc 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,13 @@ Entity Framework Core Generator supports generating [Read](https://efg.loresoft. ## Change Log +### Version 6.0 + +- upgrade to .net 8 +- add option to turn off temporal table mapping +- add rowversion options, ByteArray|Long|ULong +- add script template merge + ### Version 5.0 - add support for navigation property renames diff --git a/docs/configuration.md b/docs/configuration.md index f544eb7b..16062729 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -102,6 +102,9 @@ data: directory: '{Project.Directory}\Data\Mapping' # the mapping class output directory #include XML documentation document: false + + temporal: false # if temporal table mapping is enabled. Default true + rowVersion: ByteArray|Long|ULong # How row versions should be mapped. Default ByteArray # query extension class file configuration query: @@ -191,6 +194,7 @@ script: namespace: '{Project.Namespace}.Domain.Context' baseClass: ContextScriptBase overwrite: true # overwrite existing file + merge: true # merge regions with existing file # collection of script template with current Entity as a variable entity: - templatePath: '.\templates\entity.csx' # path to script file @@ -199,6 +203,7 @@ script: namespace: '{Project.Namespace}.Domain.Entity' baseClass: EntityScriptBase overwrite: true # overwrite existing file + merge: true # merge regions with existing file # collection script template with current Model as a variable model: - templatePath: '.\templates\model.csx' # path to script file @@ -207,10 +212,12 @@ script: namespace: '{Project.Namespace}.Domain.Models' baseClass: ModelScriptBase overwrite: true # overwrite existing file + merge: true # merge regions with existing file - templatePath: '.\templates\sample.csx' # path to script file fileName: '{Model.Name}Sample.cs' # filename to save script output directory: '{Project.Directory}\Domain\Models' # directory to save script output namespace: '{Project.Namespace}.Domain.Models' baseClass: ModelSampleBase overwrite: true # overwrite existing file + merge: true # merge regions with existing file ``` diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/AuditMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/AuditMap.cs index b99bee7d..4db84ef1 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/AuditMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/AuditMap.cs @@ -79,6 +79,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/PriorityMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/PriorityMap.cs index 73c6e816..71e72d9b 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/PriorityMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/PriorityMap.cs @@ -44,13 +44,14 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.DisplayOrder) .IsRequired() .HasColumnName("DisplayOrder") - .HasColumnType("int"); + .HasColumnType("int") + .HasDefaultValue(0); builder.Property(t => t.IsActive) .IsRequired() .HasColumnName("IsActive") .HasColumnType("bit") - .HasDefaultValueSql("((1))"); + .HasDefaultValue(true); builder.Property(t => t.Created) .IsRequired() @@ -77,6 +78,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/RoleMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/RoleMap.cs index 77fa9639..e2706008 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/RoleMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/RoleMap.cs @@ -65,6 +65,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/StatusMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/StatusMap.cs index b1c86342..ddb48bbf 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/StatusMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/StatusMap.cs @@ -44,13 +44,14 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.DisplayOrder) .IsRequired() .HasColumnName("DisplayOrder") - .HasColumnType("int"); + .HasColumnType("int") + .HasDefaultValueSql("((0))"); builder.Property(t => t.IsActive) .IsRequired() .HasColumnName("IsActive") .HasColumnType("bit") - .HasDefaultValueSql("((1))"); + .HasDefaultValue(true); builder.Property(t => t.Created) .IsRequired() @@ -77,6 +78,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/TaskExtendedMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/TaskExtendedMap.cs index cb1cfaed..82c3e0ed 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/TaskExtendedMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/TaskExtendedMap.cs @@ -68,6 +68,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/TaskMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/TaskMap.cs index 8a7a8b00..123a6754 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/TaskMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/TaskMap.cs @@ -108,6 +108,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/TenantMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/TenantMap.cs index aab2cec0..82f255dc 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/TenantMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/TenantMap.cs @@ -44,7 +44,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType .IsRequired() .HasColumnName("IsActive") .HasColumnType("bit") - .HasDefaultValueSql("((1))"); + .HasDefaultValue(true); builder.Property(t => t.Created) .IsRequired() @@ -71,6 +71,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/UserLoginMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/UserLoginMap.cs index b5d3dc8b..283f9f62 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/UserLoginMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/UserLoginMap.cs @@ -77,7 +77,8 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.IsSuccessful) .IsRequired() .HasColumnName("IsSuccessful") - .HasColumnType("bit"); + .HasColumnType("bit") + .HasDefaultValue(false); builder.Property(t => t.FailureMessage) .HasColumnName("FailureMessage") @@ -109,6 +110,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Data/Mapping/UserMap.cs b/sample/Tracker/Tracker.Core/Data/Mapping/UserMap.cs index 3e4bc6cc..1c9b8570 100644 --- a/sample/Tracker/Tracker.Core/Data/Mapping/UserMap.cs +++ b/sample/Tracker/Tracker.Core/Data/Mapping/UserMap.cs @@ -39,7 +39,8 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.IsEmailAddressConfirmed) .IsRequired() .HasColumnName("IsEmailAddressConfirmed") - .HasColumnType("bit"); + .HasColumnType("bit") + .HasDefaultValue(false); builder.Property(t => t.DisplayName) .IsRequired() @@ -62,12 +63,14 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.AccessFailedCount) .IsRequired() .HasColumnName("AccessFailedCount") - .HasColumnType("int"); + .HasColumnType("int") + .HasDefaultValueSql("((0))"); builder.Property(t => t.LockoutEnabled) .IsRequired() .HasColumnName("LockoutEnabled") - .HasColumnType("bit"); + .HasColumnType("bit") + .HasDefaultValue(false); builder.Property(t => t.LockoutEnd) .HasColumnName("LockoutEnd") @@ -80,7 +83,8 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.IsDeleted) .IsRequired() .HasColumnName("IsDeleted") - .HasColumnType("bit"); + .HasColumnType("bit") + .HasDefaultValue(false); builder.Property(t => t.Created) .IsRequired() @@ -107,6 +111,7 @@ public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityType builder.Property(t => t.RowVersion) .IsRequired() .IsRowVersion() + .IsConcurrencyToken() .HasColumnName("RowVersion") .HasColumnType("rowversion") .HasMaxLength(8) diff --git a/sample/Tracker/Tracker.Core/Tracker.Core.csproj b/sample/Tracker/Tracker.Core/Tracker.Core.csproj index 7abee753..fe62b2b8 100644 --- a/sample/Tracker/Tracker.Core/Tracker.Core.csproj +++ b/sample/Tracker/Tracker.Core/Tracker.Core.csproj @@ -8,7 +8,7 @@ - + diff --git a/sample/Tracker/Tracker.Scaffold/Priority.cs b/sample/Tracker/Tracker.Scaffold/Priority.cs index 1f9c620b..3f846390 100644 --- a/sample/Tracker/Tracker.Scaffold/Priority.cs +++ b/sample/Tracker/Tracker.Scaffold/Priority.cs @@ -13,7 +13,7 @@ public partial class Priority public int DisplayOrder { get; set; } - public bool? IsActive { get; set; } + public bool IsActive { get; set; } public DateTimeOffset Created { get; set; } diff --git a/sample/Tracker/Tracker.Scaffold/Status.cs b/sample/Tracker/Tracker.Scaffold/Status.cs index 757a7d33..9d87505a 100644 --- a/sample/Tracker/Tracker.Scaffold/Status.cs +++ b/sample/Tracker/Tracker.Scaffold/Status.cs @@ -13,7 +13,7 @@ public partial class Status public int DisplayOrder { get; set; } - public bool? IsActive { get; set; } + public bool IsActive { get; set; } public DateTimeOffset Created { get; set; } diff --git a/sample/Tracker/Tracker.Scaffold/Tenant.cs b/sample/Tracker/Tracker.Scaffold/Tenant.cs index b761e0c6..c1d36daf 100644 --- a/sample/Tracker/Tracker.Scaffold/Tenant.cs +++ b/sample/Tracker/Tracker.Scaffold/Tenant.cs @@ -11,7 +11,7 @@ public partial class Tenant public string? Description { get; set; } - public bool? IsActive { get; set; } + public bool IsActive { get; set; } public DateTimeOffset Created { get; set; } diff --git a/sample/Tracker/Tracker.Scaffold/Tracker.Scaffold.csproj b/sample/Tracker/Tracker.Scaffold/Tracker.Scaffold.csproj index 6758f12f..d42b6e1b 100644 --- a/sample/Tracker/Tracker.Scaffold/Tracker.Scaffold.csproj +++ b/sample/Tracker/Tracker.Scaffold/Tracker.Scaffold.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/sample/Tracker/Tracker.Scaffold/TrackerContext.cs b/sample/Tracker/Tracker.Scaffold/TrackerContext.cs index 7d258676..99c78864 100644 --- a/sample/Tracker/Tracker.Scaffold/TrackerContext.cs +++ b/sample/Tracker/Tracker.Scaffold/TrackerContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.EntityFrameworkCore; @@ -36,7 +36,8 @@ public TrackerContext(DbContextOptions options) public virtual DbSet UserLogins { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer("Data Source=(local)\\sql2022;Initial Catalog=Tracker;Integrated Security=True;TrustServerCertificate=True"); +#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. + => optionsBuilder.UseSqlServer("Data Source=(local);Initial Catalog=Tracker;Integrated Security=True;TrustServerCertificate=True"); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -64,9 +65,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.Created).HasDefaultValueSql("(sysutcdatetime())"); entity.Property(e => e.CreatedBy).HasMaxLength(100); entity.Property(e => e.Description).HasMaxLength(255); - entity.Property(e => e.IsActive) - .IsRequired() - .HasDefaultValueSql("((1))"); + entity.Property(e => e.IsActive).HasDefaultValue(true); entity.Property(e => e.Name).HasMaxLength(100); entity.Property(e => e.RowVersion) .IsRowVersion() @@ -108,9 +107,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.Created).HasDefaultValueSql("(sysutcdatetime())"); entity.Property(e => e.CreatedBy).HasMaxLength(100); entity.Property(e => e.Description).HasMaxLength(255); - entity.Property(e => e.IsActive) - .IsRequired() - .HasDefaultValueSql("((1))"); + entity.Property(e => e.IsActive).HasDefaultValue(true); entity.Property(e => e.Name).HasMaxLength(100); entity.Property(e => e.RowVersion) .IsRowVersion() @@ -152,8 +149,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.Updated).HasDefaultValueSql("(sysutcdatetime())"); entity.Property(e => e.UpdatedBy).HasMaxLength(100); - - entity.HasOne(d => d.Assigned).WithMany(p => p.Tasks).HasForeignKey(d => d.AssignedId); entity.HasOne(d => d.Priority).WithMany(p => p.Tasks).HasForeignKey(d => d.PriorityId); @@ -196,9 +191,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.Id).HasDefaultValueSql("(newsequentialid())"); entity.Property(e => e.Created).HasDefaultValueSql("(sysutcdatetime())"); entity.Property(e => e.CreatedBy).HasMaxLength(100); - entity.Property(e => e.IsActive) - .IsRequired() - .HasDefaultValueSql("((1))"); + entity.Property(e => e.IsActive).HasDefaultValue(true); entity.Property(e => e.Name).HasMaxLength(256); entity.Property(e => e.RowVersion) .IsRowVersion() diff --git a/sample/Tracker/Tracker.Scaffold/scaffold.cmd b/sample/Tracker/Tracker.Scaffold/scaffold.cmd new file mode 100644 index 00000000..b6797d58 --- /dev/null +++ b/sample/Tracker/Tracker.Scaffold/scaffold.cmd @@ -0,0 +1 @@ +dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=Tracker;Integrated Security=True;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer --force diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5642ac00..795e7eca 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -34,7 +34,7 @@ - + diff --git a/src/EntityFrameworkCore.Generator.Core/EntityFrameworkCore.Generator.Core.csproj b/src/EntityFrameworkCore.Generator.Core/EntityFrameworkCore.Generator.Core.csproj index cc040c59..e3a1fc77 100644 --- a/src/EntityFrameworkCore.Generator.Core/EntityFrameworkCore.Generator.Core.csproj +++ b/src/EntityFrameworkCore.Generator.Core/EntityFrameworkCore.Generator.Core.csproj @@ -1,32 +1,21 @@ - + - net6.0;net8.0 + net8.0 EntityFrameworkCore.Generator 1591,EF1001 - + - - - - - - - - - - - - - + - + + diff --git a/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs b/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs index 41613dc4..2771a1ff 100644 --- a/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs +++ b/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using System.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; @@ -36,6 +36,8 @@ public class Property : ModelBase public bool? IsRowVersion { get; set; } + public bool? IsConcurrencyToken { get; set; } + public bool? IsUnique { get; set; } [Obsolete("Value no longer used, will be deleted")] @@ -49,7 +51,9 @@ public class Property : ModelBase [Obsolete("Value no longer used, will be deleted")] public int? Scale { get; set; } + public object DefaultValue { get; set; } + public string Default { get; set; } public ValueGenerated? ValueGenerated { get; set; } -} \ No newline at end of file +} diff --git a/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs b/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs index 7b956804..bb435262 100644 --- a/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs +++ b/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -12,12 +9,17 @@ using Humanizer; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; +using static Npgsql.Replication.PgOutput.Messages.RelationMessage; + +using Model = EntityFrameworkCore.Generator.Metadata.Generation.Model; +using Property = EntityFrameworkCore.Generator.Metadata.Generation.Property; using PropertyCollection = EntityFrameworkCore.Generator.Metadata.Generation.PropertyCollection; namespace EntityFrameworkCore.Generator; @@ -96,7 +98,7 @@ private Entity GetEntity(EntityContext entityContext, DatabaseTable tableSchema, ?? CreateEntity(entityContext, tableSchema); if (!entity.Properties.IsProcessed) - CreateProperties(entity, tableSchema.Columns); + CreateProperties(entity, tableSchema); if (processRelationships && !entity.Relationships.IsProcessed) CreateRelationships(entityContext, entity, tableSchema); @@ -148,7 +150,7 @@ private Entity CreateEntity(EntityContext entityContext, DatabaseTable tableSche entity.IsView = tableSchema is DatabaseView; bool? isTemporal = tableSchema[SqlServerAnnotationNames.IsTemporal] as bool?; - if (isTemporal == true) + if (isTemporal == true && _options.Data.Mapping.Temporal) { entity.TemporalTableName = tableSchema[SqlServerAnnotationNames.TemporalHistoryTableName] as string; entity.TemporalTableSchema = tableSchema[SqlServerAnnotationNames.TemporalHistoryTableSchema] as string; @@ -170,8 +172,9 @@ private Entity CreateEntity(EntityContext entityContext, DatabaseTable tableSche } - private void CreateProperties(Entity entity, IEnumerable columns) + private void CreateProperties(Entity entity, DatabaseTable tableSchema) { + var columns = tableSchema.Columns; foreach (var column in columns) { var table = column.Table; @@ -212,6 +215,7 @@ private void CreateProperties(Entity entity, IEnumerable columns property.IsNullable = column.IsNullable; property.IsRowVersion = column.IsRowVersion(); + property.IsConcurrencyToken = (bool?)column[ScaffoldingAnnotationNames.ConcurrencyToken] == true; property.IsPrimaryKey = table.PrimaryKey?.Columns.Contains(column) == true; property.IsForeignKey = table.ForeignKeys.Any(c => c.Columns.Contains(column)); @@ -219,7 +223,9 @@ private void CreateProperties(Entity entity, IEnumerable columns property.IsUnique = table.UniqueConstraints.Any(c => c.Columns.Contains(column)) || table.Indexes.Where(i => i.IsUnique).Any(c => c.Columns.Contains(column)); + property.DefaultValue = column.DefaultValue; property.Default = column.DefaultValueSql; + property.ValueGenerated = column.ValueGenerated; if (property.ValueGenerated == null && !string.IsNullOrWhiteSpace(column.ComputedColumnSql)) @@ -231,10 +237,67 @@ private void CreateProperties(Entity entity, IEnumerable columns property.SystemType = mapping.ClrType; property.Size = mapping.Size; + // overwrite row version type + if (property.IsRowVersion == true && _options.Data.Mapping.RowVersion != RowVersionMapping.ByteArray && property.SystemType == typeof(byte[])) + { + property.SystemType = _options.Data.Mapping.RowVersion switch + { + RowVersionMapping.ByteArray => typeof(byte[]), + RowVersionMapping.Long => typeof(long), + RowVersionMapping.ULong => typeof(ulong), + _ => typeof(byte[]) + }; + } + property.IsProcessed = true; } entity.Properties.IsProcessed = true; + + + bool? isTemporal = tableSchema[SqlServerAnnotationNames.IsTemporal] as bool?; + if (isTemporal != true || _options.Data.Mapping.Temporal) + return; + + // add temporal period columns + var temporalStartColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodStartColumnName] as string + ?? tableSchema[SqlServerAnnotationNames.TemporalPeriodStartPropertyName] as string; + + var temporalStart = entity.Properties.ByColumn(temporalStartColumn); + + if (temporalStart == null) + { + temporalStart = new Property { Entity = entity, ColumnName = temporalStartColumn }; + entity.Properties.Add(temporalStart); + } + + temporalStart.PropertyName = ToPropertyName(entity.EntityClass, temporalStartColumn); + temporalStart.ValueGenerated = ValueGenerated.OnAddOrUpdate; + temporalStart.StoreType = "datetime2"; + temporalStart.DataType = DbType.DateTime2; + temporalStart.SystemType = typeof(DateTime); + + temporalStart.IsProcessed = true; + + + var temporalEndColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodEndColumnName] as string + ?? tableSchema[SqlServerAnnotationNames.TemporalPeriodEndPropertyName] as string; + + var temporalEnd = entity.Properties.ByColumn(temporalEndColumn); + + if (temporalEnd == null) + { + temporalEnd = new Property { Entity = entity, ColumnName = temporalEndColumn }; + entity.Properties.Add(temporalEnd); + } + + temporalEnd.PropertyName = ToPropertyName(entity.EntityClass, temporalEndColumn); + temporalEnd.ValueGenerated = ValueGenerated.OnAddOrUpdate; + temporalEnd.StoreType = "datetime2"; + temporalEnd.DataType = DbType.DateTime2; + temporalEnd.SystemType = typeof(DateTime); + + temporalEnd.IsProcessed = true; } diff --git a/src/EntityFrameworkCore.Generator.Core/OptionMapper.cs b/src/EntityFrameworkCore.Generator.Core/OptionMapper.cs index aa2691dc..f2c0587c 100644 --- a/src/EntityFrameworkCore.Generator.Core/OptionMapper.cs +++ b/src/EntityFrameworkCore.Generator.Core/OptionMapper.cs @@ -155,6 +155,8 @@ private static void MapMapping(MappingClassOptions option, MappingClass mapping) option.Namespace = mapping.Namespace; option.Directory = mapping.Directory; option.Document = mapping.Document; + option.Temporal = mapping.Temporal; + option.RowVersion = mapping.RowVersion; } private static void MapEntity(EntityClassOptions option, EntityClass entity) @@ -267,6 +269,7 @@ private static TemplateOptions MapTemplate(VariableDictionary variables, Templat BaseClass = template.BaseClass, Directory = template.Directory, Overwrite = template.Overwrite, + Merge = template.Merge, }; if (template.Parameters == null || template.Parameters.Count == 0) diff --git a/src/EntityFrameworkCore.Generator.Core/Options/ClassOptionsBase.cs b/src/EntityFrameworkCore.Generator.Core/Options/ClassOptionsBase.cs index aab4e498..2c34ba57 100644 --- a/src/EntityFrameworkCore.Generator.Core/Options/ClassOptionsBase.cs +++ b/src/EntityFrameworkCore.Generator.Core/Options/ClassOptionsBase.cs @@ -51,13 +51,4 @@ public string Directory /// [DefaultValue(false)] public bool Document { get; set; } - - /// - /// Gets or sets a value indicating whether to use file-scoped namespace. - /// - /// - /// true to use file-coped namespace; otherwise, false. - /// - [Obsolete("Use ProjectOptions.FileScopedNamespace instead")] - public bool? FileScopedNamespace { get; set; } } diff --git a/src/EntityFrameworkCore.Generator.Core/Options/ContextNaming.cs b/src/EntityFrameworkCore.Generator.Core/Options/ContextNaming.cs index 4c29e7a7..1a18a510 100644 --- a/src/EntityFrameworkCore.Generator.Core/Options/ContextNaming.cs +++ b/src/EntityFrameworkCore.Generator.Core/Options/ContextNaming.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace EntityFrameworkCore.Generator.Options; @@ -21,4 +21,4 @@ public enum ContextNaming /// Add 'DataSet' to the end of the entity name. /// Suffix = 2 -} \ No newline at end of file +} diff --git a/src/EntityFrameworkCore.Generator.Core/Options/MappingClassOptions.cs b/src/EntityFrameworkCore.Generator.Core/Options/MappingClassOptions.cs index 1fd84530..ae47b405 100644 --- a/src/EntityFrameworkCore.Generator.Core/Options/MappingClassOptions.cs +++ b/src/EntityFrameworkCore.Generator.Core/Options/MappingClassOptions.cs @@ -1,4 +1,6 @@ -namespace EntityFrameworkCore.Generator.Options; +using System.ComponentModel; + +namespace EntityFrameworkCore.Generator.Options; /// /// EntityFramework mapping class generation options @@ -15,4 +17,24 @@ public MappingClassOptions(VariableDictionary variables, string prefix) Namespace = "{Project.Namespace}.Data.Mapping"; Directory = @"{Project.Directory}\Data\Mapping"; } -} \ No newline at end of file + + /// + /// Gets or sets if temporal table mapping is enabled. Default true + /// + /// + /// If temporal table mapping is enabled. + /// + [DefaultValue(true)] + public bool Temporal { get; set; } = true; + + /// + /// Gets or sets how row version properties should be mapped. + /// + /// + /// How row version properties should be mapped. + /// + /// + [DefaultValue(RowVersionMapping.ByteArray)] + public RowVersionMapping RowVersion { get; set; } = RowVersionMapping.ByteArray; + +} diff --git a/src/EntityFrameworkCore.Generator.Core/Options/RowVersionMapping.cs b/src/EntityFrameworkCore.Generator.Core/Options/RowVersionMapping.cs new file mode 100644 index 00000000..818af7ee --- /dev/null +++ b/src/EntityFrameworkCore.Generator.Core/Options/RowVersionMapping.cs @@ -0,0 +1,20 @@ +namespace EntityFrameworkCore.Generator.Options; + +/// +/// How row versions should be mapped +/// +public enum RowVersionMapping +{ + /// + /// Map as byte array, default + /// + ByteArray = 0, + /// + /// Map as a long + /// + Long = 1, + /// + /// Map as a ulong + /// + ULong = 2, +} diff --git a/src/EntityFrameworkCore.Generator.Core/Options/TemplateOptions.cs b/src/EntityFrameworkCore.Generator.Core/Options/TemplateOptions.cs index ed215b1a..6f6be757 100644 --- a/src/EntityFrameworkCore.Generator.Core/Options/TemplateOptions.cs +++ b/src/EntityFrameworkCore.Generator.Core/Options/TemplateOptions.cs @@ -10,7 +10,7 @@ public class TemplateOptions : OptionsBase /// /// Initializes a new instance of the class. /// - /// The shared variables dictionary. + /// The shared variable dictionary. /// The variable key prefix. public TemplateOptions(VariableDictionary variables, string prefix) : base(variables, AppendPrefix(prefix, "Template")) @@ -80,13 +80,21 @@ public string Directory } /// - /// Gets or sets a value indicating whether the generated file will be over written. + /// Gets or sets a value indicating whether the generated file will be overwritten. /// /// /// true to overwrite generated file; otherwise, false. /// public bool Overwrite { get; set; } + /// + /// Gets or sets a value indicating whether the generated file will be merged via region replacement. + /// + /// + /// true to merged via region replacement; otherwise, false. + /// + public bool Merge { get; set; } + /// /// Gets or sets the template parameters. /// diff --git a/src/EntityFrameworkCore.Generator.Core/Parsing/RegionReplace.cs b/src/EntityFrameworkCore.Generator.Core/Parsing/RegionReplace.cs new file mode 100644 index 00000000..93bc1eb6 --- /dev/null +++ b/src/EntityFrameworkCore.Generator.Core/Parsing/RegionReplace.cs @@ -0,0 +1,59 @@ +using System.Text; + +namespace EntityFrameworkCore.Generator.Parsing; + +public class RegionReplace +{ + public RegionReplace(RegionParser regionParser = null) + { + RegionParser = regionParser ?? new RegionParser(); + } + + protected RegionParser RegionParser { get; } + + public void MergeFile(string fullPath, string outputContent) + { + var originalContent = File.ReadAllText(fullPath); + + var finalContent = MergeContent(originalContent, outputContent); + + File.WriteAllText(fullPath, finalContent); + } + + public string MergeContent(string originalContent, string outputContent) + { + var outputRegions = RegionParser.ParseRegions(outputContent); + + var originalRegions = RegionParser.ParseRegions(originalContent); + var originalBuilder = new StringBuilder(originalContent); + + int offset = 0; + foreach (var pair in outputRegions) + { + var outputRegion = pair.Value; + if (!originalRegions.TryGetValue(pair.Key, out var originalRegion)) + { + // log error + continue; + } + + int startIndex = originalRegion.StartIndex + offset; + int beforeReplace = originalBuilder.Length; + int length = (originalRegion.EndIndex + offset) - startIndex; + + originalBuilder.Remove(startIndex, length); + originalBuilder.Insert(startIndex, outputRegion.Content); + + int afterReplace = originalBuilder.Length; + + offset += (afterReplace - beforeReplace); + } + + var finalContent = originalBuilder.ToString(); + + if (originalContent == finalContent) + return finalContent; + + return finalContent; + } +} diff --git a/src/EntityFrameworkCore.Generator.Core/Scripts/ContextScriptVariables.cs b/src/EntityFrameworkCore.Generator.Core/Scripts/ContextScriptVariables.cs index 8a4777d3..a8460544 100644 --- a/src/EntityFrameworkCore.Generator.Core/Scripts/ContextScriptVariables.cs +++ b/src/EntityFrameworkCore.Generator.Core/Scripts/ContextScriptVariables.cs @@ -1,4 +1,4 @@ -using EntityFrameworkCore.Generator.Metadata.Generation; +using EntityFrameworkCore.Generator.Metadata.Generation; using EntityFrameworkCore.Generator.Options; namespace EntityFrameworkCore.Generator.Scripts; @@ -12,4 +12,4 @@ public ContextScriptVariables(EntityContext entityContext, GeneratorOptions gene } public EntityContext EntityContext { get; } -} \ No newline at end of file +} diff --git a/src/EntityFrameworkCore.Generator.Core/Scripts/ScriptTemplateBase.cs b/src/EntityFrameworkCore.Generator.Core/Scripts/ScriptTemplateBase.cs index 70d909ee..fdca6c91 100644 --- a/src/EntityFrameworkCore.Generator.Core/Scripts/ScriptTemplateBase.cs +++ b/src/EntityFrameworkCore.Generator.Core/Scripts/ScriptTemplateBase.cs @@ -1,7 +1,9 @@ -using System.IO; +using System.IO; +using System.Text; using EntityFrameworkCore.Generator.Extensions; using EntityFrameworkCore.Generator.Options; +using EntityFrameworkCore.Generator.Parsing; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; @@ -23,14 +25,20 @@ protected ScriptTemplateBase(ILoggerFactory loggerFactory, GeneratorOptions gene TemplateOptions = templateOptions; GeneratorOptions = generatorOptions; + RegionReplace = new RegionReplace(); + } protected ILogger Logger { get; } + protected RegionReplace RegionReplace { get; } + + public TemplateOptions TemplateOptions { get; } public GeneratorOptions GeneratorOptions { get; } + protected virtual void WriteCode() { var templatePath = TemplateOptions.TemplatePath; @@ -51,7 +59,7 @@ protected virtual void WriteCode() var exists = File.Exists(path); - if (exists && !TemplateOptions.Overwrite) + if (exists && !(TemplateOptions.Merge || TemplateOptions.Overwrite)) { Logger.LogDebug("Skipping template '{template}' because output '{fileName}' already exists.", templatePath, fileName); return; @@ -70,7 +78,10 @@ protected virtual void WriteCode() return; } - File.WriteAllText(path, content); + if (exists && TemplateOptions.Merge && !TemplateOptions.Overwrite) + RegionReplace.MergeFile(path, content); + else + File.WriteAllText(path, content); } protected virtual string ExecuteScript() @@ -140,4 +151,4 @@ protected Script LoadScript(string scriptPath) return _scriptTemplate; } -} \ No newline at end of file +} diff --git a/src/EntityFrameworkCore.Generator.Core/Serialization/MappingClass.cs b/src/EntityFrameworkCore.Generator.Core/Serialization/MappingClass.cs index 27007d98..6665ee6b 100644 --- a/src/EntityFrameworkCore.Generator.Core/Serialization/MappingClass.cs +++ b/src/EntityFrameworkCore.Generator.Core/Serialization/MappingClass.cs @@ -1,3 +1,7 @@ +using System.ComponentModel; + +using EntityFrameworkCore.Generator.Options; + namespace EntityFrameworkCore.Generator.Serialization; /// @@ -13,4 +17,24 @@ public MappingClass() Namespace = "{Project.Namespace}.Data.Mapping"; Directory = @"{Project.Directory}\Data\Mapping"; } + + /// + /// Gets or sets if temporal table mapping is enabled. Default true + /// + /// + /// If temporal table mapping is enabled. + /// + [DefaultValue(true)] + public bool Temporal { get; set; } = true; + + /// + /// Gets or sets how row version properties should be mapped. + /// + /// + /// How row version properties should be mapped. + /// + /// + [DefaultValue(RowVersionMapping.ByteArray)] + public RowVersionMapping RowVersion { get; set; } = RowVersionMapping.ByteArray; + } diff --git a/src/EntityFrameworkCore.Generator.Core/Serialization/TemplateModel.cs b/src/EntityFrameworkCore.Generator.Core/Serialization/TemplateModel.cs index 1e1fc205..57da6b35 100644 --- a/src/EntityFrameworkCore.Generator.Core/Serialization/TemplateModel.cs +++ b/src/EntityFrameworkCore.Generator.Core/Serialization/TemplateModel.cs @@ -3,7 +3,7 @@ namespace EntityFrameworkCore.Generator.Serialization; /// -/// Sript template options +/// Script template options /// public class TemplateModel { @@ -49,13 +49,21 @@ public class TemplateModel public string Directory { get; set; } /// - /// Gets or sets a value indicating whether the generated file will be over written. + /// Gets or sets a value indicating whether the generated file will be overwritten. /// /// /// true to overwrite generated file; otherwise, false. /// public bool Overwrite { get; set; } + /// + /// Gets or sets a value indicating whether the generated file will be merged via region replacement. + /// + /// + /// true to merged via region replacement; otherwise, false. + /// + public bool Merge { get; set; } + /// /// Gets or sets the template parameters. /// diff --git a/src/EntityFrameworkCore.Generator.Core/Templates/CodeTemplateBase.cs b/src/EntityFrameworkCore.Generator.Core/Templates/CodeTemplateBase.cs index 1649ead3..b9d4239c 100644 --- a/src/EntityFrameworkCore.Generator.Core/Templates/CodeTemplateBase.cs +++ b/src/EntityFrameworkCore.Generator.Core/Templates/CodeTemplateBase.cs @@ -15,12 +15,12 @@ protected CodeTemplateBase(GeneratorOptions options) { Options = options; CodeBuilder = new IndentedStringBuilder(); - RegionParser = new RegionParser(); + RegionReplace = new RegionReplace(); } public GeneratorOptions Options { get; } - protected RegionParser RegionParser { get; } + protected RegionReplace RegionReplace { get; } protected IndentedStringBuilder CodeBuilder { get; } @@ -35,47 +35,10 @@ public virtual void WriteCode(string path) var output = WriteCode(); if (File.Exists(fullPath)) - MergeOutput(fullPath, output); + RegionReplace.MergeFile(fullPath, output); else File.WriteAllText(fullPath, output); } public abstract string WriteCode(); - - protected virtual void MergeOutput(string fullPath, string outputContent) - { - var outputRegions = RegionParser.ParseRegions(outputContent); - - var originalContent = File.ReadAllText(fullPath); - var originalRegions = RegionParser.ParseRegions(originalContent); - var originalBuilder = new StringBuilder(originalContent); - - int offset = 0; - foreach (var pair in outputRegions) - { - var outputRegion = pair.Value; - if (!originalRegions.TryGetValue(pair.Key, out var originalRegion)) - { - // log error - continue; - } - - int startIndex = originalRegion.StartIndex + offset; - int beforeReplace = originalBuilder.Length; - int length = (originalRegion.EndIndex + offset) - startIndex; - - originalBuilder.Remove(startIndex, length); - originalBuilder.Insert(startIndex, outputRegion.Content); - - int afterReplace = originalBuilder.Length; - - offset = offset + (afterReplace - beforeReplace); - } - - var finalContent = originalBuilder.ToString(); - if (originalContent == finalContent) - return; - - File.WriteAllText(fullPath, finalContent); - } } diff --git a/src/EntityFrameworkCore.Generator.Core/Templates/MappingClassTemplate.cs b/src/EntityFrameworkCore.Generator.Core/Templates/MappingClassTemplate.cs index d9b37d52..8a75642f 100644 --- a/src/EntityFrameworkCore.Generator.Core/Templates/MappingClassTemplate.cs +++ b/src/EntityFrameworkCore.Generator.Core/Templates/MappingClassTemplate.cs @@ -1,5 +1,5 @@ +using System.Data; using System.Globalization; -using System.Linq; using EntityFrameworkCore.Generator.Extensions; using EntityFrameworkCore.Generator.Metadata.Generation; @@ -263,10 +263,21 @@ private void GeneratePropertyMapping(Property property) if (property.IsRowVersion == true) { + if (property.DataType == DbType.Binary && property.SystemType != typeof(byte[])) + { + CodeBuilder.AppendLine(); + CodeBuilder.Append(".HasConversion()"); + } CodeBuilder.AppendLine(); CodeBuilder.Append(".IsRowVersion()"); } + if (property.IsConcurrencyToken == true) + { + CodeBuilder.AppendLine(); + CodeBuilder.Append(".IsConcurrencyToken()"); + } + CodeBuilder.AppendLine(); CodeBuilder.Append($".HasColumnName({property.ColumnName.ToLiteral()})"); @@ -282,7 +293,13 @@ private void GeneratePropertyMapping(Property property) CodeBuilder.Append($".HasMaxLength({property.Size.Value.ToString(CultureInfo.InvariantCulture)})"); } - if (!string.IsNullOrEmpty(property.Default)) + // only use for simple types + if (property.DefaultValue is bool or int or long or byte or double or float or short) + { + CodeBuilder.AppendLine(); + CodeBuilder.Append($".HasDefaultValue({property.DefaultValue?.ToString()?.ToLowerInvariant()})"); + } + else if (!string.IsNullOrEmpty(property.Default)) { CodeBuilder.AppendLine(); CodeBuilder.Append($".HasDefaultValueSql({property.Default.ToLiteral()})"); diff --git a/src/EntityFrameworkCore.Generator.Core/Templates/QueryExtensionTemplate.cs b/src/EntityFrameworkCore.Generator.Core/Templates/QueryExtensionTemplate.cs index 6a8f3c3f..effcf480 100644 --- a/src/EntityFrameworkCore.Generator.Core/Templates/QueryExtensionTemplate.cs +++ b/src/EntityFrameworkCore.Generator.Core/Templates/QueryExtensionTemplate.cs @@ -158,6 +158,10 @@ private void GenerateUniqueMethod(Method method, bool async = false) CodeBuilder.AppendLine("/// "); CodeBuilder.AppendLine("/// An to filter."); AppendDocumentation(method); + + if (async) + CodeBuilder.AppendLine("/// A to observe while waiting for the task to complete."); + CodeBuilder.AppendLine($"/// An instance of or null if not found."); } @@ -210,6 +214,10 @@ private void GenerateKeyMethod(Method method, bool async = false) CodeBuilder.AppendLine("/// "); CodeBuilder.AppendLine("/// An to filter."); AppendDocumentation(method); + + if (async) + CodeBuilder.AppendLine("/// A to observe while waiting for the task to complete."); + CodeBuilder.AppendLine($"/// An instance of or null if not found."); } diff --git a/src/EntityFrameworkCore.Generator.Core/Templates/ValidatorClassTemplate.cs b/src/EntityFrameworkCore.Generator.Core/Templates/ValidatorClassTemplate.cs index aeff3ba8..dfba69f5 100644 --- a/src/EntityFrameworkCore.Generator.Core/Templates/ValidatorClassTemplate.cs +++ b/src/EntityFrameworkCore.Generator.Core/Templates/ValidatorClassTemplate.cs @@ -105,7 +105,7 @@ private void GenerateConstructor() if (property.IsRequired && property.SystemType == typeof(string)) CodeBuilder.AppendLine($"RuleFor(p => p.{propertyName}).NotEmpty();"); - if (property.Size.HasValue && property.SystemType == typeof(string)) + if (property.Size.HasValue && property.SystemType == typeof(string) && property.Size > 0) CodeBuilder.AppendLine($"RuleFor(p => p.{propertyName}).MaximumLength({property.Size});"); } diff --git a/src/EntityFrameworkCore.Generator/EntityFrameworkCore.Generator.csproj b/src/EntityFrameworkCore.Generator/EntityFrameworkCore.Generator.csproj index 4a08c3b0..d53549cc 100644 --- a/src/EntityFrameworkCore.Generator/EntityFrameworkCore.Generator.csproj +++ b/src/EntityFrameworkCore.Generator/EntityFrameworkCore.Generator.csproj @@ -1,7 +1,7 @@ Exe - net6.0;net8.0 + net8.0 efg true 1591,EF1001 @@ -10,7 +10,7 @@ - + diff --git a/src/EntityFrameworkCore.Generator/InitializeCommand.cs b/src/EntityFrameworkCore.Generator/InitializeCommand.cs index 64a11601..9f359ac2 100644 --- a/src/EntityFrameworkCore.Generator/InitializeCommand.cs +++ b/src/EntityFrameworkCore.Generator/InitializeCommand.cs @@ -94,11 +94,13 @@ private Serialization.GeneratorModel CreateOptionsFile(string optionsFile) { var options = new Serialization.GeneratorModel(); - options.Project.Namespace = Directory.CreateDirectory(Environment.CurrentDirectory)?.Name ?? "Project.Core"; + options.Project.Namespace = Directory.CreateDirectory(Environment.CurrentDirectory).Name ?? "Project.Core"; options.Project.Directory = ".\\"; options.Project.Nullable = true; options.Project.FileScopedNamespace = true; + options.Data.Mapping.RowVersion = RowVersionMapping.Long; + // default all to generate options.Data.Query.Generate = true; options.Model.Read.Generate = true; @@ -106,7 +108,7 @@ private Serialization.GeneratorModel CreateOptionsFile(string optionsFile) options.Model.Update.Generate = true; options.Model.Validator.Generate = true; options.Model.Mapper.Generate = true; - + Logger.LogInformation($"Creating options file: {optionsFile}"); return options; diff --git a/test/EntityFrameworkCore.Generator.Core.Tests/EntityFrameworkCore.Generator.Core.Tests.csproj b/test/EntityFrameworkCore.Generator.Core.Tests/EntityFrameworkCore.Generator.Core.Tests.csproj index 2974bc5d..bcf2c47b 100644 --- a/test/EntityFrameworkCore.Generator.Core.Tests/EntityFrameworkCore.Generator.Core.Tests.csproj +++ b/test/EntityFrameworkCore.Generator.Core.Tests/EntityFrameworkCore.Generator.Core.Tests.csproj @@ -1,6 +1,6 @@ - + - net6.0 + net8.0 false 1591,EF1001 @@ -27,8 +27,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs b/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs index fbf0654b..61535c84 100644 --- a/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs +++ b/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Json; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.Logging.Abstractions; @@ -570,9 +571,8 @@ private static SqlServerTypeMappingSource CreateTypeMappingSource() #pragma warning disable EF1001 // Internal EF Core API usage. var sqlServerTypeMappingSource = new SqlServerTypeMappingSource( new TypeMappingSourceDependencies( - new ValueConverterSelector( - new ValueConverterSelectorDependencies() - ), + new ValueConverterSelector(new ValueConverterSelectorDependencies()), + new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), Enumerable.Empty() ), new RelationalTypeMappingSourceDependencies(Enumerable.Empty())