diff --git a/Masa.Framework.sln b/Masa.Framework.sln index 1bd48131d..7bb958301 100644 --- a/Masa.Framework.sln +++ b/Masa.Framework.sln @@ -699,14 +699,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.StackSdks.Isol EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.StackSdks.Isolation", "src\BuildingBlocks\StackSdks\Masa.BuildingBlocks.StackSdks.Isolation\Masa.BuildingBlocks.StackSdks.Isolation.csproj", "{4B175C6B-F5BE-4B65-BFCC-A949CA5BC6A3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.StackSdks.Tsc.OpenTelemetry", "src\Contrib\StackSdks\Masa.Contrib.StackSdks.Tsc.OpenTelemetry\Masa.Contrib.StackSdks.Tsc.OpenTelemetry.csproj", "{2B861EF5-124F-4D13-8291-F223B6588C35}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.StackSdks.Tsc.OpenTelemetry", "src\Contrib\StackSdks\Masa.Contrib.StackSdks.Tsc.OpenTelemetry\Masa.Contrib.StackSdks.Tsc.OpenTelemetry.csproj", "{2B861EF5-124F-4D13-8291-F223B6588C35}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.StackSdks.Tsc.OpenTelemetry.Tests", "src\Contrib\StackSdks\Tests\Masa.Contrib.StackSdks.Tsc.OpenTelemetry.Tests\Masa.Contrib.StackSdks.Tsc.OpenTelemetry.Tests.csproj", "{62D22ED4-76A6-41EE-9121-992608E3590D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.StackSdks.Tsc.OpenTelemetry.Tests", "src\Contrib\StackSdks\Tests\Masa.Contrib.StackSdks.Tsc.OpenTelemetry.Tests\Masa.Contrib.StackSdks.Tsc.OpenTelemetry.Tests.csproj", "{62D22ED4-76A6-41EE-9121-992608E3590D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tsc", "tsc", "{E4AD67C8-9255-4013-A3C4-962694399770}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tsc", "tsc", "{6042AE23-A07E-4F6F-B1C3-F17617AEB722}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DynamicsCRM", "DynamicsCRM", "{64B54122-44F1-4379-9422-953EF706A3A6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Utils.DynamicsCrm.Core", "src\Utils\DynamicsCrm\Masa.Utils.DynamicsCrm.Core\Masa.Utils.DynamicsCrm.Core.csproj", "{83310F46-E1C7-4438-B32A-9F6F7EA13FCF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Utils.DynamicsCrm.EntityFrameworkCore", "src\Utils\DynamicsCrm\Masa.Utils.DynamicsCrm.EntityFrameworkCore\Masa.Utils.DynamicsCrm.EntityFrameworkCore.csproj", "{8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -2547,6 +2553,22 @@ Global {62D22ED4-76A6-41EE-9121-992608E3590D}.Release|Any CPU.Build.0 = Release|Any CPU {62D22ED4-76A6-41EE-9121-992608E3590D}.Release|x64.ActiveCfg = Release|Any CPU {62D22ED4-76A6-41EE-9121-992608E3590D}.Release|x64.Build.0 = Release|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Debug|x64.ActiveCfg = Debug|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Debug|x64.Build.0 = Debug|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Release|Any CPU.Build.0 = Release|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Release|x64.ActiveCfg = Release|Any CPU + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF}.Release|x64.Build.0 = Release|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Debug|x64.Build.0 = Debug|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Release|Any CPU.Build.0 = Release|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Release|x64.ActiveCfg = Release|Any CPU + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2896,6 +2918,9 @@ Global {62D22ED4-76A6-41EE-9121-992608E3590D} = {E4AD67C8-9255-4013-A3C4-962694399770} {E4AD67C8-9255-4013-A3C4-962694399770} = {EC7A08E9-3355-486B-BA30-41A1F8CAC5F5} {6042AE23-A07E-4F6F-B1C3-F17617AEB722} = {383995FF-B661-4E15-A830-640FC5BA8A1F} + {64B54122-44F1-4379-9422-953EF706A3A6} = {5944A182-13B8-4DA6-AEE2-0A01E64A9648} + {83310F46-E1C7-4438-B32A-9F6F7EA13FCF} = {64B54122-44F1-4379-9422-953EF706A3A6} + {8A51A2A9-FBF4-40DC-AD89-AD3B9D3A50DC} = {64B54122-44F1-4379-9422-953EF706A3A6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Configurations/CrmConfiguration.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Configurations/CrmConfiguration.cs new file mode 100644 index 000000000..c6d924ca2 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Configurations/CrmConfiguration.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.DynamicsCrm.Core.Configurations; + +public class CrmConfiguration : ICrmConfiguration +{ + public Guid SystemUserId { get; set; } + + public Guid BusinessUnitId { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Configurations/ICrmConfiguration.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Configurations/ICrmConfiguration.cs new file mode 100644 index 000000000..05947cfc1 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Configurations/ICrmConfiguration.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.DynamicsCrm.Core.Configurations; + +public interface ICrmConfiguration +{ + Guid SystemUserId { get; set; } + + Guid BusinessUnitId { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmAuditEntity.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmAuditEntity.cs new file mode 100644 index 000000000..7c150925e --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmAuditEntity.cs @@ -0,0 +1,11 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public class CrmAuditEntity : CrmEntity, ICrmOwnerAudited +{ + public virtual Guid OwnerId { set; get; } + + public virtual int OwnerIdType { set; get; } + + public virtual Guid? OwningBusinessUnit { set; get; } +} + diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmEntity.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmEntity.cs new file mode 100644 index 000000000..cce6103f5 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmEntity.cs @@ -0,0 +1,12 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public class CrmEntity : Entity, ICrmEntity +{ + public virtual Guid? CreatedBy { set; get; } + + public virtual DateTime? CreatedOn { set; get; } + + public virtual Guid? ModifiedBy { set; get; } + + public virtual DateTime? ModifiedOn { set; get; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmEntityAuditingHelper.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmEntityAuditingHelper.cs new file mode 100644 index 000000000..d034b1c22 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmEntityAuditingHelper.cs @@ -0,0 +1,72 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public static class CrmEntityAuditingHelper +{ + public static void SetCreationAuditProperties( + object entityAsObj, + Guid systemUserId, + Guid ownerId, + Guid businessUnitId) + { + var entityWithCrmCreatedAudit = entityAsObj as ICrmCreationAudited; + if (entityWithCrmCreatedAudit == null) + { + return; + } + + if (entityWithCrmCreatedAudit.CreatedOn == default(DateTime) || !entityWithCrmCreatedAudit.CreatedOn.HasValue) + { + entityWithCrmCreatedAudit.CreatedOn = DateTime.UtcNow; + } + entityWithCrmCreatedAudit.CreatedBy = systemUserId; + if (entityAsObj is ICrmOwnerAudited ownerAudited) + { + if (ownerAudited.OwnerId == Guid.Empty) + { + ownerAudited.OwnerId = ownerId; + } + ownerAudited.OwnerIdType = 8; + if (ownerAudited.OwningBusinessUnit == null) + { + ownerAudited.OwningBusinessUnit = businessUnitId; + } + } + if (entityAsObj is ICrmDeletionAudited deletionAudited) + { + deletionAudited.StateCode = 0; + deletionAudited.StatusCode = 1; + } + + SetModificationAuditProperties(entityAsObj, systemUserId); + } + + public static void SetModificationAuditProperties( + object entityAsObj, + Guid systemUserId) + { + var entityWithCrmModifiedAudit = entityAsObj as ICrmModificationAudited; + if (entityWithCrmModifiedAudit == null) + { + return; + } + + entityWithCrmModifiedAudit.ModifiedOn = DateTime.UtcNow; + entityWithCrmModifiedAudit.ModifiedBy = systemUserId; + } + + public static void SetDeletionAuditProperties( + object entityAsObj, + Guid systemUserId) + { + var entityWithCrmDeletionAudited = entityAsObj as ICrmDeletionAudited; + if (entityWithCrmDeletionAudited == null) + { + return; + } + + entityWithCrmDeletionAudited.StateCode = 1; + entityWithCrmDeletionAudited.StatusCode = 2; + entityWithCrmDeletionAudited.ModifiedOn = DateTime.UtcNow; + entityWithCrmDeletionAudited.ModifiedBy = systemUserId; + } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmFullAuditCustomEntity.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmFullAuditCustomEntity.cs new file mode 100644 index 000000000..ff5319cc6 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmFullAuditCustomEntity.cs @@ -0,0 +1,7 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public class CrmFullAuditCustomEntity : CrmFullAuditEntity, ICrmCustomEntity +{ + [Column("new_name")] + public virtual string? Name { set; get; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmFullAuditEntity.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmFullAuditEntity.cs new file mode 100644 index 000000000..25fe4ca67 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/CrmFullAuditEntity.cs @@ -0,0 +1,9 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public class CrmFullAuditEntity : CrmAuditEntity, ICrmDeletionAudited +{ + public virtual int StateCode { set; get; } + public virtual int? StatusCode { set; get; } + [DatabaseGenerated(DatabaseGeneratedOption.Computed)] + public virtual byte[]? VersionNumber { set; get; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmCreationAudited.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmCreationAudited.cs new file mode 100644 index 000000000..33ef2597d --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmCreationAudited.cs @@ -0,0 +1,6 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmCreationAudited : ICrmHasCreationTime +{ + Guid? CreatedBy { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmCustomEntity.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmCustomEntity.cs new file mode 100644 index 000000000..ae9e42c24 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmCustomEntity.cs @@ -0,0 +1,6 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmCustomEntity +{ + string? Name { set; get; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmDeletionAudited.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmDeletionAudited.cs new file mode 100644 index 000000000..1a278d0f4 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmDeletionAudited.cs @@ -0,0 +1,5 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmDeletionAudited : ICrmState, ICrmModificationAudited +{ +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmEntity.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmEntity.cs new file mode 100644 index 000000000..9754974de --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmEntity.cs @@ -0,0 +1,5 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmEntity : ICrmCreationAudited, ICrmModificationAudited +{ +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmHasCreationTime.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmHasCreationTime.cs new file mode 100644 index 000000000..bb5463907 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmHasCreationTime.cs @@ -0,0 +1,6 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmHasCreationTime +{ + DateTime? CreatedOn { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmHasModificationTime.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmHasModificationTime.cs new file mode 100644 index 000000000..55c38ee22 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmHasModificationTime.cs @@ -0,0 +1,6 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmHasModificationTime +{ + DateTime? ModifiedOn { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmModificationAudited.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmModificationAudited.cs new file mode 100644 index 000000000..ef21d3fcc --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmModificationAudited.cs @@ -0,0 +1,6 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmModificationAudited : ICrmHasModificationTime +{ + Guid? ModifiedBy { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmOwnerAudited.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmOwnerAudited.cs new file mode 100644 index 000000000..e9f124516 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmOwnerAudited.cs @@ -0,0 +1,8 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmOwnerAudited +{ + Guid OwnerId { get; set; } + int OwnerIdType { get; set; } + Guid? OwningBusinessUnit { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmState.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmState.cs new file mode 100644 index 000000000..52364c542 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Domain/Entities/Auditing/ICrmState.cs @@ -0,0 +1,7 @@ +namespace Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; + +public interface ICrmState +{ + int StateCode { get; set; } + int? StatusCode { get; set; } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Identity/DynamicsCrmUser.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Identity/DynamicsCrmUser.cs new file mode 100644 index 000000000..2e904b5e4 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Identity/DynamicsCrmUser.cs @@ -0,0 +1,15 @@ +namespace Masa.Utils.DynamicsCrm.Core.Identity; + +public class DynamicsCrmUser : IIdentityUser +{ + public string Id { get; set; } = string.Empty; + public string? UserName { get; set; } + public string[] Roles { get; set; } = Array.Empty(); + + public Guid BusinessUnitId { get; set; } + + public string GetUserName() + { + return UserName ?? string.Empty; + } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Masa.Utils.DynamicsCrm.Core.csproj b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Masa.Utils.DynamicsCrm.Core.csproj new file mode 100644 index 000000000..7e4be98cb --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/Masa.Utils.DynamicsCrm.Core.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/_Imports.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/_Imports.cs new file mode 100644 index 000000000..3658e33f4 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.Core/_Imports.cs @@ -0,0 +1,3 @@ +global using System.ComponentModel.DataAnnotations.Schema; +global using Masa.BuildingBlocks.Ddd.Domain.Entities; +global using Masa.BuildingBlocks.Authentication.Identity; diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Filters/DynamicsCrmSaveChangeFilter.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Filters/DynamicsCrmSaveChangeFilter.cs new file mode 100644 index 000000000..57b7937a2 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Filters/DynamicsCrmSaveChangeFilter.cs @@ -0,0 +1,59 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.DynamicsCrm.EntityFrameworkCore.Filters; + +public class DynamicsCrmSaveChangeFilter : ISaveChangesFilter + where TDbContext : DbContext, IMasaDbContext +{ + private readonly IUserContext? _userContext; + private readonly ICrmConfiguration? _crmConfiguration; + + public DynamicsCrmSaveChangeFilter( + IUserContext? userContext = null, + ICrmConfiguration? crmConfiguration = null) + { + _userContext = userContext; + _crmConfiguration = crmConfiguration; + } + + public void OnExecuting(ChangeTracker changeTracker) + { + changeTracker.DetectChanges(); + + var user = _userContext?.GetUser(); + var userId = GetUserId(); + var businessUnitId = user?.BusinessUnitId ?? _crmConfiguration?.BusinessUnitId ?? Guid.Empty; + + foreach (var entity in changeTracker.Entries() + .Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified)) + { + AuditEntityHandler(entity, userId, businessUnitId); + } + } + + private static void AuditEntityHandler(EntityEntry entity, Guid systemUserId, Guid businessUnitId) + { + if (entity.State == EntityState.Added) + { + CrmEntityAuditingHelper.SetCreationAuditProperties(entity.Entity, systemUserId, systemUserId, businessUnitId); + } + else + { + CrmEntityAuditingHelper.SetModificationAuditProperties(entity.Entity, systemUserId); + } + } + + private Guid GetUserId() + { + if (_userContext == null) + return Guid.Empty; + + var userId = _userContext.GetUserId(); + + if (!(userId is Guid)) + return Guid.Empty; + + return userId as Guid? ?? _crmConfiguration?.SystemUserId ?? Guid.Empty; + } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Filters/DynamicsCrmSoftDeleteSaveChangesFilter.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Filters/DynamicsCrmSoftDeleteSaveChangesFilter.cs new file mode 100644 index 000000000..0560c350f --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Filters/DynamicsCrmSoftDeleteSaveChangesFilter.cs @@ -0,0 +1,93 @@ +namespace Masa.Utils.DynamicsCrm.EntityFrameworkCore.Filters; + +public class DynamicsCrmSoftDeleteSaveChangesFilter : ISaveChangesFilter + where TDbContext : DbContext, IMasaDbContext + where TUserId : IComparable +{ + private readonly IUserContext? _userContext; + private readonly ICrmConfiguration? _crmConfiguration; + private readonly TDbContext _context; + + public DynamicsCrmSoftDeleteSaveChangesFilter( + TDbContext dbContext, + IUserContext? userContext = null, + ICrmConfiguration? crmConfiguration = null) + { + _context = dbContext; + _userContext = userContext; + _crmConfiguration = crmConfiguration; + } + + public void OnExecuting(ChangeTracker changeTracker) + { + changeTracker.DetectChanges(); + + var userId = GetUserId(); + + var entries = changeTracker.Entries().Where(entry => entry is { State: EntityState.Deleted, Entity: ICrmDeletionAudited }); + foreach (var entity in entries) + { + var navigationEntries = entity.Navigations + .Where(navigationEntry => navigationEntry.Metadata is not ISkipNavigation && + !((IReadOnlyNavigation)navigationEntry.Metadata).IsOnDependent && + navigationEntry.CurrentValue != null && + entries.All(e => e.Entity != navigationEntry.CurrentValue)); + HandleNavigationEntry(navigationEntries); + + entity.State = EntityState.Modified; + CrmEntityAuditingHelper.SetDeletionAuditProperties(entity.Entity, userId); + } + } + + private void HandleNavigationEntry(IEnumerable navigationEntries) + { + foreach (var navigationEntry in navigationEntries) + { + if (navigationEntry is CollectionEntry collectionEntry) + { + foreach (var dependentEntry in collectionEntry.CurrentValue ?? new List()) + { + HandleDependent(dependentEntry); + } + } + else + { + var dependentEntry = navigationEntry.CurrentValue; + if (dependentEntry != null) + { + HandleDependent(dependentEntry); + } + } + } + } + + private void HandleDependent(object dependentEntry) + { + var userId = _userContext?.GetUserId() ?? _crmConfiguration?.SystemUserId ?? Guid.Empty; + + var entityEntry = _context.Entry(dependentEntry); + entityEntry.State = EntityState.Modified; + + CrmEntityAuditingHelper.SetDeletionAuditProperties(entityEntry.Entity, userId); + + var navigationEntries = entityEntry.Navigations + .Where(navigationEntry => navigationEntry.Metadata is not ISkipNavigation && + !((IReadOnlyNavigation)navigationEntry.Metadata).IsOnDependent && + navigationEntry.CurrentValue != null); + HandleNavigationEntry(navigationEntries); + } + + private Guid GetUserId() + { + if (_userContext == null) + return Guid.Empty; + + var userId = _userContext.GetUserId(); + + if (!(userId is Guid)) + return Guid.Empty; + + return userId as Guid? ?? _crmConfiguration?.SystemUserId ?? Guid.Empty; + } +} + diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Masa.Utils.DynamicsCrm.EntityFrameworkCore.csproj b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Masa.Utils.DynamicsCrm.EntityFrameworkCore.csproj new file mode 100644 index 000000000..a8158c296 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/Masa.Utils.DynamicsCrm.EntityFrameworkCore.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbContext.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbContext.cs new file mode 100644 index 000000000..89043b8eb --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbContext.cs @@ -0,0 +1,108 @@ +namespace Masa.Utils.DynamicsCrm.EntityFrameworkCore; + +public class MasaDynamicsCrmDbContext : MasaDbContext +{ + protected virtual string TableNameSuffix { get; set; } = "Base"; + + protected virtual string FieldNamePrefix { get; set; } = "New_"; + + protected virtual string TableNamePrefix { get; set; } = "New_"; + + protected MasaDynamicsCrmDbContext(MasaDbContextOptions options) : base(options) + { + } + + protected override void OnModelCreatingExecuting(ModelBuilder modelBuilder) + { + ConfigureDefaultEntity(modelBuilder, TableNameSuffix); + ConfigureCustomEntity(modelBuilder, TableNameSuffix, FieldNamePrefix, TableNamePrefix); + } + + protected override void OnBeforeSaveChangesByFilters() + { + foreach (var filter in Options!.SaveChangesFilters) + { + try + { + filter.OnExecuting(ChangeTracker); + } + catch (Exception ex) + { + throw new MasaException("An error occured when intercept SaveChanges() or SaveChangesAsync()", ex); + } + } + } + + protected virtual void ConfigureDefaultEntity(ModelBuilder modelBuilder, string tableNameSuffix) + { + modelBuilder.EntityTypes() + .Where(entity => entity.BaseType == null) + .Configure(entity => entity.SetTableName(entity.ClrType.Name + tableNameSuffix)); + + var crmPreProperties = modelBuilder.Properties() + .Where(p => p.PropertyInfo != null + && typeof(CrmEntity).IsAssignableFrom(p.PropertyInfo.ReflectedType) + && !typeof(CrmFullAuditEntity).IsAssignableFrom(p.PropertyInfo.ReflectedType) + ).ToList(); + + crmPreProperties + .Where(p => p.Name.Equals("Id")) + .Configure(prop => + { + prop.SetColumnName(prop.PropertyInfo?.ReflectedType?.Name + prop.PropertyInfo?.Name); + }); + + var crmPrePropertiesName = + typeof(CrmEntity).GetProperties() + .Select(x => x.Name).ToList(); + + crmPreProperties + .Where(p => !crmPrePropertiesName.Contains(p.Name)) + .Where(p => !p.Name.Equals("Id")) + .Where(p => p.FindAnnotation("Relational:ColumnName") == null) + .Configure(prop => prop.SetColumnName(prop.PropertyInfo?.Name)); + } + + protected virtual void ConfigureCustomEntity(ModelBuilder modelBuilder, string tableNameSuffix, string fieldNamePrefix, string tableNamePrefix) + { + modelBuilder.EntityTypes() + .Where(entity => entity.BaseType == null) + .Configure(entity => entity.SetTableName(tableNamePrefix + entity.ClrType.Name + tableNameSuffix)); + + var crmCustomPreProperties = modelBuilder.Properties() + .Where(p => + p.PropertyInfo != null && typeof(CrmFullAuditEntity).IsAssignableFrom(p.PropertyInfo.ReflectedType) + ).ToList(); + + crmCustomPreProperties + .Where(p => p.Name.Equals("Id")) + .Configure(prop => + { + prop.SetColumnName(fieldNamePrefix + prop.PropertyInfo?.ReflectedType?.Name + prop.PropertyInfo?.Name); + }); + + var crmCustomPrePropertiesName = + typeof(CrmFullAuditEntity).GetProperties() + .Select(x => x.Name).ToList(); + + crmCustomPreProperties + .Where(p => !crmCustomPrePropertiesName.Contains(p.Name)) + .Where(p => !p.Name.Equals("Id")) + .Where(p => p.FindAnnotation("Relational:ColumnName") == null) + .Configure(prop => prop.SetColumnName(fieldNamePrefix + prop.PropertyInfo?.Name)); + } + + protected override Expression>? CreateFilterExpression() + where TEntity : class + { + Expression>? expression = base.CreateFilterExpression(); + if (typeof(ICrmState).IsAssignableFrom(typeof(TEntity))) + { + Expression> crmStateCodeFilter = e => ((ICrmState)e).StateCode == 0; + + expression = crmStateCodeFilter.And(expression != null, expression); + } + + return expression; + } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbContextBuilderExtensions.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbContextBuilderExtensions.cs new file mode 100644 index 000000000..bfb3564b8 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbContextBuilderExtensions.cs @@ -0,0 +1,20 @@ +namespace Microsoft.EntityFrameworkCore; + +public static class MasaDynamicsCrmDbContextBuilderExtensions +{ + public static IMasaDbContextBuilder UseDynamicsCrmFilter( + this IMasaDbContextBuilder masaDbContextBuilder, + Action? options = null) where TDbContext : MasaDynamicsCrmDbContext, IMasaDbContext + => masaDbContextBuilder.UseFilter(options).UseDynamicsCrmFilterCore(); + + private static IMasaDbContextBuilder UseDynamicsCrmFilterCore(this IMasaDbContextBuilder masaDbContextBuilder) where TDbContext : MasaDynamicsCrmDbContext, IMasaDbContext + { + masaDbContextBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), + typeof(DynamicsCrmSaveChangeFilter), ServiceLifetime.Scoped)); + + masaDbContextBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), + typeof(DynamicsCrmSoftDeleteSaveChangesFilter), ServiceLifetime.Scoped)); + + return masaDbContextBuilder; + } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbModelBuilderExtensions.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbModelBuilderExtensions.cs new file mode 100644 index 000000000..14f555e9d --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/MasaDynamicsCrmDbModelBuilderExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.DynamicsCrm.EntityFrameworkCore; + +public static class MasaDynamicsCrmDbModelBuilderExtensions +{ + private static IEnumerable EntityTypes(this ModelBuilder builder) + { + return builder.Model.GetEntityTypes(); + } + + public static IEnumerable EntityTypes(this ModelBuilder builder) + { + return builder.Model.GetEntityTypes().Where(x => typeof(T).IsAssignableFrom(x.ClrType)); + } + + public static IEnumerable Properties(this ModelBuilder builder) + { + return builder.EntityTypes().SelectMany(entityType => entityType.GetProperties()); + } + + public static IEnumerable Properties(this ModelBuilder builder) + { + return builder.EntityTypes().SelectMany(entityType => entityType.GetProperties().Where(x => typeof(T).IsAssignableFrom(x.ClrType))); + } + + public static void Configure(this IEnumerable entityTypes, Action convention) + { + foreach (var entityType in entityTypes) + { + convention(entityType); + } + } + + public static void Configure(this IEnumerable propertyTypes, Action convention) + { + foreach (var propertyType in propertyTypes) + { + convention(propertyType); + } + } +} diff --git a/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/_Imports.cs b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/_Imports.cs new file mode 100644 index 000000000..c7bc770f0 --- /dev/null +++ b/src/Utils/DynamicsCrm/Masa.Utils.DynamicsCrm.EntityFrameworkCore/_Imports.cs @@ -0,0 +1,15 @@ +global using Microsoft.EntityFrameworkCore; +global using System.Linq.Expressions; +global using Masa.Utils.DynamicsCrm.Core.Domain.Entities.Auditing; +global using Masa.BuildingBlocks.Authentication.Identity; +global using Masa.BuildingBlocks.Ddd.Domain.Entities.Auditing; +global using Masa.Utils.DynamicsCrm.Core.Configurations; +global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.EntityFrameworkCore.Metadata; +global using Masa.BuildingBlocks.Data; +global using Masa.Utils.DynamicsCrm.EntityFrameworkCore.Filters; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Masa.Utils.DynamicsCrm.EntityFrameworkCore; +global using Masa.Contrib.Data.Contracts.Options; +global using Masa.Utils.DynamicsCrm.Core.Identity; \ No newline at end of file