Skip to content

Commit dee56a9

Browse files
authored
v3.31.0 (#136)
* `JsonMergePatch` enhancements. * - *Enhancement:* Refactored the `CosmosDb` capabilities such that the `CosmosDbContainer` and `CosmosDbModelContainer` are model type independent, with underlying type support implemented at the method level for greater flexibility and control. The typed `CosmosDbContainer<T, TModel>` etc. remain and are accessed from the type independent containers as required. - The existing `IMultiSetArgs` operations have been moved (and renamed) from `CosmosDb` to `CosmosDbContainer` and `CosmosDbModelContainer` as these are single container-specific. - The existing `CosmosDb.UseAuthorizeFilter` operations have been moved to `CosmosDbContainer` as these are single container-specific. * - *Enhancement:* Added `Cleaner.PrepareCreate` and `Cleaner.PrepareUpdate` to encapsulate `ChangeLog.PrepareCreated` and `ChangeLog.PrepareUpdated`, and `Cleaner.ResetTenantId` to ensure consistent handling throughout _CoreEx_. - *Enhancement:* Added `SystemTime.Timestamp` as the standard means to access the current timestamp (uses `Cleaner.Clean`) to ensure consistency throughout _CoreEx_. Therefore, the likes of `DateTime.UtcNow` should be replaced with `SystemTime.Timestamp`. The previous `ExecutionContent.SystemTime` has been removed as it was not consistent with the `ExecutionContext` pattern. * - *Enhancement:* Updated the `IExtendedException.ShouldBeLogged` implementations to check `SettingsBase` configuration to enable/disable. * - Simply CosmosDB configuration; removes need for lazy instantiation - Update packages * ICU fix? * ICU again? * ICU again? * ICU again? * ICU again * ICU again? * ICU again? * ICU again? * ICU again? * ICU again?
1 parent 366165a commit dee56a9

File tree

138 files changed

+6975
-2805
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+6975
-2805
lines changed

.github/workflows/CI.yml

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ jobs:
4040
uses: actions/setup-dotnet@v1
4141
with:
4242
dotnet-version: |
43-
3.1.x
4443
6.0.x
4544
8.0.x
4645
9.0.x

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
Represents the **NuGet** versions.
44

5+
## v3.31.0
6+
- *Enhancement:* Moved existing reflection-based `JsonMergePatch` to `Extended.JsonMergePatchEx`; this remains the `AddJsonMergePatch` default implementation.
7+
- *Enhancement:* Added new `JsonMergePatch` that leverages `JsonElement` and `Utf8JsonWriter` without underlying reflection; useful in scenarios where the value type is not known. This is also not as performant as the reflection-based `JsonMergePatchEx` version and the primary reason why it is not the new default.
8+
- *Enhancement:* Refactored the `CosmosDb` capabilities such that the `CosmosDbContainer` and `CosmosDbModelContainer` are model type independent, with underlying type support implemented at the method level for greater flexibility and control. The typed `CosmosDbContainer<T, TModel>` etc. remain and are accessed from the type independent containers as required.
9+
- The existing `IMultiSetArgs` operations have been moved (and renamed) from `CosmosDb` to `CosmosDbContainer` and `CosmosDbModelContainer` as these are single container-specific.
10+
- The existing `CosmosDb.UseAuthorizeFilter` operations have been moved to `CosmosDbContainer` as these are single container-specific.
11+
- *Enhancement:* Added `Cleaner.PrepareCreate` and `Cleaner.PrepareUpdate` to encapsulate `ChangeLog.PrepareCreated` and `ChangeLog.PrepareUpdated`, and `Cleaner.ResetTenantId` to ensure consistent handling throughout _CoreEx_.
12+
- *Enhancement:* Added `SystemTime.Timestamp` as the standard means to access the current timestamp (uses `Cleaner.Clean`) to ensure consistency throughout _CoreEx_. Therefore, the likes of `DateTime.UtcNow` should be replaced with `SystemTime.Timestamp`. The previous `ExecutionContent.SystemTime` has been removed as it was not consistent with the `ExecutionContext` pattern.
13+
- *Enhancement:* Updated the `IExtendedException.ShouldBeLogged` implementations to check `SettingsBase` configuration to enable/disable.
14+
- *Enhancement:* Updated dependencies to latest; including transitive where applicable.
15+
516
## v3.30.2
617
- *Fixed:* Missing `QueryArgs.IncludeText` added to set the `$text=true` equivalent.
718
- *Fixed:* Simplification of creating and setting the `QueryArgs.Filter` using an implict string operator.

Common.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>3.30.2</Version>
3+
<Version>3.31.0</Version>
44
<LangVersion>preview</LangVersion>
55
<Authors>Avanade</Authors>
66
<Company>Avanade</Company>

samples/My.Hr/My.Hr.Api/Startup.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using CoreEx.Azure.ServiceBus.HealthChecks;
99
using Azure.Messaging.ServiceBus;
1010
using Microsoft.Extensions.DependencyInjection;
11+
using CoreEx.Json.Merge;
1112

1213
namespace My.Hr.Api;
1314

@@ -34,7 +35,7 @@ public void ConfigureServices(IServiceCollection services)
3435
.AddAzureServiceBusSender()
3536
.AddAzureServiceBusPurger()
3637
.AddJsonMergePatch()
37-
.AddWebApi((_, webapi) => webapi.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? webapi.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
38+
.AddWebApi((_, webapi) => webapi.UnhandledExceptionAsync = (ex, logger, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? webapi.CreateActionResultFromExtendedException(new ConcurrencyException(null, ex), logger) : null))
3839
.AddReferenceDataContentWebApi()
3940
.AddRequestCache();
4041

samples/My.Hr/My.Hr.Api/appsettings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"AbsoluteExpirationRelativeToNow": "03:00:00",
1616
"SlidingExpiration": "00:45:00"
1717
}
18-
}
18+
},
19+
"LogConcurrencyException": true
1920
},
2021
"ServiceBusConnection": {
2122
"fullyQualifiedNamespace": "Endpoint=sb://top-secret.servicebus.windows.net/;SharedAccessKeyName=top-secret;SharedAccessKey=top-encrypted-secret"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace My.Hr.Business.Data;
2+
3+
public class Employee2Configuration : IEntityTypeConfiguration<Employee2>
4+
{
5+
public void Configure(EntityTypeBuilder<Employee2> builder)
6+
{
7+
builder.ToTable("Employee2", "Hr");
8+
builder.Property(p => p.Id).HasColumnName("EmployeeId").HasColumnType("UNIQUEIDENTIFIER");
9+
builder.Property(p => p.Email).HasColumnType("NVARCHAR(250)");
10+
builder.Property(p => p.FirstName).HasColumnType("NVARCHAR(100)");
11+
builder.Property(p => p.LastName).HasColumnType("NVARCHAR(100)");
12+
builder.Property(p => p.Gender).HasColumnName("GenderCode").HasColumnType("NVARCHAR(50)").HasConversion(v => v!.Code, v => (Gender?)v);
13+
builder.Property(p => p.Birthday).HasColumnType("DATE");
14+
builder.Property(p => p.StartDate).HasColumnType("DATE");
15+
builder.Property(p => p.TerminationDate).HasColumnType("DATE");
16+
builder.Property(p => p.TerminationReasonCode).HasColumnType("NVARCHAR(50)");
17+
builder.Property(p => p.PhoneNo).HasColumnType("NVARCHAR(50)");
18+
builder.Property(p => p.ETag).HasColumnName("RowVersion").IsRowVersion().HasConversion(s => s == null ? Array.Empty<byte>() : Convert.FromBase64String(s), d => Convert.ToBase64String(d));
19+
builder.Property(p => p.IsDeleted).HasColumnType("BIT");
20+
builder.HasKey("Id");
21+
}
22+
}

samples/My.Hr/My.Hr.Business/Data/HrDbContext.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
1818
{
1919
modelBuilder
2020
.ApplyConfiguration(new UsStateConfiguration())
21-
.ApplyConfiguration(new EmployeeConfiguration());
21+
.ApplyConfiguration(new EmployeeConfiguration())
22+
.ApplyConfiguration(new Employee2Configuration());
2223

2324
base.OnModelCreating(modelBuilder);
2425
}

samples/My.Hr/My.Hr.Business/Data/HrEfDb.cs

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ public interface IHrEfDb : IEfDb
99
/// Gets the <see cref="Employee"/> entity.
1010
/// </summary>
1111
EfDbEntity<Employee, Employee> Employees { get; }
12+
13+
/// <summary>
14+
/// Gets the <see cref="Employee2"/> entity.
15+
/// </summary>
16+
EfDbEntity<Employee2, Employee2> Employees2 { get; }
1217
}
1318

1419
/// <summary>
@@ -27,5 +32,10 @@ public HrEfDb(HrDbContext dbContext, IMapper mapper) : base(dbContext, mapper) {
2732
/// Gets the <see cref="Employee"/> encapsulated entity.
2833
/// </summary>
2934
public EfDbEntity<Employee, Employee> Employees => new(this);
35+
36+
/// <summary>
37+
/// Gets the <see cref="Employee"/> encapsulated entity.
38+
/// </summary>
39+
public EfDbEntity<Employee2, Employee2> Employees2 => new(this);
3040
}
3141
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Diagnostics;
2+
3+
namespace My.Hr.Business.Models;
4+
5+
/// <summary>
6+
/// Represents the Entity Framework (EF) model for database object 'Hr.Employee2'.
7+
/// </summary>
8+
public class Employee2 : IIdentifier<Guid>, IETag, ILogicallyDeleted
9+
{
10+
/// <summary>
11+
/// Gets or sets the 'EmployeeId' column value.
12+
/// </summary>
13+
public Guid Id { get; set; }
14+
15+
/// <summary>
16+
/// Gets or sets the 'Email' column value.
17+
/// </summary>
18+
public string? Email { get; set; }
19+
20+
/// <summary>
21+
/// Gets or sets the 'FirstName' column value.
22+
/// </summary>
23+
public string? FirstName { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the 'LastName' column value.
27+
/// </summary>
28+
public string? LastName { get; set; }
29+
30+
/// <summary>
31+
/// Gets or sets the 'GenderCode' column value.
32+
/// </summary>
33+
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
34+
public Gender? Gender { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the 'Birthday' column value.
38+
/// </summary>
39+
public DateTime? Birthday { get; set; }
40+
41+
/// <summary>
42+
/// Gets or sets the 'StartDate' column value.
43+
/// </summary>
44+
public DateTime? StartDate { get; set; }
45+
46+
/// <summary>
47+
/// Gets or sets the 'TerminationDate' column value.
48+
/// </summary>
49+
public DateTime? TerminationDate { get; set; }
50+
51+
/// <summary>
52+
/// Gets or sets the 'TerminationReasonCode' column value.
53+
/// </summary>
54+
public string? TerminationReasonCode { get; set; }
55+
56+
/// <summary>
57+
/// Gets or sets the 'PhoneNo' column value.
58+
/// </summary>
59+
public string? PhoneNo { get; set; }
60+
61+
/// <summary>
62+
/// Gets or sets the 'RowVersion' column value.
63+
/// </summary>
64+
public string? ETag { get; set; }
65+
66+
/// <summary>
67+
/// Gets or sets the 'IsDeleted' column value.
68+
/// </summary>
69+
public bool? IsDeleted { get; set; }
70+
}
71+
72+
public class Employee2Collection : List<Employee2> { }
73+
74+
public class Employee2CollectionResult : CollectionResult<Employee2Collection, Employee2> { }

samples/My.Hr/My.Hr.Business/Services/EmployeeResultService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public Task<Result> VerifyEmployeeAsync(Guid id) => Result
3636
var verification = new EmployeeVerificationRequest
3737
{
3838
Name = employee!.FirstName,
39-
Age = DateTime.UtcNow.Subtract(employee.Birthday.GetValueOrDefault()).Days / 365,
39+
Age = SystemTime.Timestamp.Subtract(employee.Birthday.GetValueOrDefault()).Days / 365,
4040
Gender = employee.Gender?.Code
4141
};
4242

samples/My.Hr/My.Hr.Business/Services/EmployeeService.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public async Task<Employee> UpdateEmployeeAsync(Employee employee, Guid id)
5656
throw new NotFoundException();
5757

5858
employee.Id = id;
59+
60+
_dbContext.ChangeTracker.Clear(); // Different employee instance (result of using CoreEx.Json.JsonMergePatch vs CoreEx.Json.Extended.JsonMergePatchEx); therefore, clear the change tracker prior to update attempt.
5961
_dbContext.Employees.Update(employee);
6062
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
6163
return employee;
@@ -80,7 +82,7 @@ public async Task VerifyEmployeeAsync(Guid id)
8082
var verification = new EmployeeVerificationRequest
8183
{
8284
Name = employee.FirstName,
83-
Age = DateTime.UtcNow.Subtract(employee.Birthday.GetValueOrDefault()).Days / 365,
85+
Age = SystemTime.Timestamp.Subtract(employee.Birthday.GetValueOrDefault()).Days / 365,
8486
Gender = employee.Gender?.Code
8587
};
8688

samples/My.Hr/My.Hr.Business/Services/EmployeeService2.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public async Task VerifyEmployeeAsync(Guid id)
3636
var verification = new EmployeeVerificationRequest
3737
{
3838
Name = employee.FirstName,
39-
Age = DateTime.UtcNow.Subtract(employee.Birthday.GetValueOrDefault()).Days / 365,
39+
Age = SystemTime.Timestamp.Subtract(employee.Birthday.GetValueOrDefault()).Days / 365,
4040
Gender = employee.Gender?.Code
4141
};
4242

samples/My.Hr/My.Hr.Business/Validators/EmployeeValidator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public EmployeeValidator()
1010
RuleFor(x => x.FirstName).NotNull().MaximumLength(100);
1111
RuleFor(x => x.LastName).NotNull().MaximumLength(100);
1212
RuleFor(x => x.Gender).NotNull().IsValid();
13-
RuleFor(x => x.Birthday).NotNull().LessThanOrEqualTo(DateTime.UtcNow.AddYears(-18)).WithMessage("Birthday is invalid as the Employee must be at least 18 years of age.");
13+
RuleFor(x => x.Birthday).NotNull().LessThanOrEqualTo(SystemTime.Timestamp.AddYears(-18)).WithMessage("Birthday is invalid as the Employee must be at least 18 years of age.");
1414
RuleFor(x => x.StartDate).NotNull().GreaterThanOrEqualTo(new DateTime(1999, 01, 01, 0, 0, 0, DateTimeKind.Utc)).WithMessage("January 1, 1999");
1515
RuleFor(x => x.PhoneNo).NotNull().MaximumLength(50);
1616
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-- Migration Script
2+
3+
BEGIN TRANSACTION
4+
5+
CREATE TABLE [Hr].[Employee2] (
6+
[EmployeeId] UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWSEQUENTIALID()) PRIMARY KEY, -- This is the primary key
7+
[Email] NVARCHAR(250) NULL UNIQUE, -- This is the employee's unique email address
8+
[FirstName] NVARCHAR(100) NULL,
9+
[LastName] NVARCHAR(100) NULL,
10+
[GenderCode] NVARCHAR(50) NULL, -- This is the related Gender code; see Ref.Gender table
11+
[Birthday] DATE NULL,
12+
[StartDate] DATE NULL,
13+
[TerminationDate] DATE NULL,
14+
[TerminationReasonCode] NVARCHAR(50) NULL, -- This is the related Termination Reason code; see Ref.TerminationReason table
15+
[PhoneNo] NVARCHAR(50) NULL,
16+
[AddressJson] NVARCHAR(500) NULL, -- This is the full address persisted as JSON.
17+
[IsDeleted] BIT NULL, -- Logical delete
18+
[RowVersion] TIMESTAMP NOT NULL, -- This is used for concurrency version checking.
19+
[CreatedBy] NVARCHAR(250) NULL, -- The following are standard audit columns.
20+
[CreatedDate] DATETIME2 NULL,
21+
[UpdatedBy] NVARCHAR(250) NULL,
22+
[UpdatedDate] DATETIME2 NULL
23+
);
24+
25+
COMMIT TRANSACTION

samples/My.Hr/My.Hr.Functions/Startup.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using CoreEx.Database;
66
using CoreEx.Database.HealthChecks;
77
using CoreEx.Http.HealthChecks;
8+
using CoreEx.Json.Merge;
89
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
910
using Microsoft.EntityFrameworkCore;
1011
using Microsoft.Extensions.DependencyInjection;
@@ -40,8 +41,8 @@ public override void Configure(IFunctionsHostBuilder builder)
4041
.AddEventPublisher()
4142
.AddSingleton(sp => new Az.ServiceBusClient(sp.GetRequiredService<HrSettings>().ServiceBusConnection__fullyQualifiedNamespace))
4243
.AddAzureServiceBusSender()
43-
.AddWebApi((_, webapi) => webapi.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? webapi.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
44-
.AddJsonMergePatch()
44+
.AddWebApi((_, webapi) => webapi.UnhandledExceptionAsync = (ex, logger, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? webapi.CreateActionResultFromExtendedException(new ConcurrencyException(), logger) : null))
45+
.AddJsonMergePatch(sp => new JsonMergePatch())
4546
.AddWebApiPublisher()
4647
.AddAzureServiceBusSubscriber();
4748

samples/My.Hr/My.Hr.UnitTest/Data/Data.yaml

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@
77
- EmergencyContact:
88
- { EmergencyContactId: 201, EmployeeId: 2, FirstName: Garth, LastName: Smith, PhoneNo: (443) 678 1827, RelationshipTypeCode: PAR }
99
- { EmergencyContactId: 202, EmployeeId: 2, FirstName: Sarah, LastName: Smith, PhoneNo: (443) 234 3837, RelationshipTypeCode: PAR }
10-
- { EmergencyContactId: 401, EmployeeId: 4, FirstName: Michael, LastName: Manners, PhoneNo: (234) 297 9834, RelationshipTypeCode: FRD }
10+
- { EmergencyContactId: 401, EmployeeId: 4, FirstName: Michael, LastName: Manners, PhoneNo: (234) 297 9834, RelationshipTypeCode: FRD }
11+
- Employee2:
12+
- { EmployeeId: 1, Email: [email protected], FirstName: Wendy, LastName: Jones, GenderCode: F, Birthday: 1985-03-18, StartDate: 2000-12-11, PhoneNo: (425) 612 8113 }
13+
- { EmployeeId: 2, Email: [email protected], FirstName: Brian, LastName: Smith, GenderCode: M, Birthday: 1994-11-07, StartDate: 2013-08-06, TerminationDate: 2015-04-08, TerminationReasonCode: RE, PhoneNo: (429) 120 0098, IsDeleted: false }
14+
- { EmployeeId: 3, Email: [email protected], FirstName: Rachael, LastName: Browne, GenderCode: F, Birthday: 1972-06-28, StartDate: 2019-11-06, PhoneNo: (421) 783 2343, IsDeleted: true }
15+
- { EmployeeId: 4, Email: [email protected], FirstName: Waylon, LastName: Smithers, GenderCode: M, Birthday: 1952-02-21, StartDate: 2001-01-22, PhoneNo: (428) 893 2793, AddressJson: '{ "street1": "8365 851 PL NE", "city": "Redmond", "state": "WA", "postCode": "98052" }' }

0 commit comments

Comments
 (0)