diff --git a/sample/Tenant.Api.Contracts/DDD/Domain/Consts/TenantConnectionStringConsts.cs b/sample/Tenant.Api.Contracts/DDD/Domain/Consts/TenantConnectionStringConsts.cs
new file mode 100644
index 0000000..cff4313
--- /dev/null
+++ b/sample/Tenant.Api.Contracts/DDD/Domain/Consts/TenantConnectionStringConsts.cs
@@ -0,0 +1,14 @@
+namespace Tenant.Api.DDD.Domain.Consts;
+
+public static class TenantConnectionStringConsts
+{
+ ///
+ /// Default value: 64
+ ///
+ public static int MaxNameLength { get; set; } = 64;
+
+ ///
+ /// Default value: 1024
+ ///
+ public static int MaxValueLength { get; set; } = 1024;
+}
diff --git a/sample/Tenant.Api.Contracts/DDD/Domain/Consts/TenantConsts.cs b/sample/Tenant.Api.Contracts/DDD/Domain/Consts/TenantConsts.cs
new file mode 100644
index 0000000..b209623
--- /dev/null
+++ b/sample/Tenant.Api.Contracts/DDD/Domain/Consts/TenantConsts.cs
@@ -0,0 +1,9 @@
+namespace Tenant.Api.DDD.Domain.Consts;
+
+public static class TenantConsts
+{
+ ///
+ /// Default value: 64
+ ///
+ public static int MaxNameLength { get; set; } = 64;
+}
diff --git a/sample/Tenant.Api.Contracts/DDD/Domain/Shared/TenantEto.cs b/sample/Tenant.Api.Contracts/DDD/Domain/Shared/TenantEto.cs
new file mode 100644
index 0000000..6d166bd
--- /dev/null
+++ b/sample/Tenant.Api.Contracts/DDD/Domain/Shared/TenantEto.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Tenant.Api.DDD.Domain.Shared;
+
+[Serializable]
+public class TenantEto
+{
+ public Guid Id { get; set; }
+
+ public string Name { get; set; }
+}
diff --git a/sample/Tenant.Api.Contracts/Tenant.Api.Contracts.csproj b/sample/Tenant.Api.Contracts/Tenant.Api.Contracts.csproj
new file mode 100644
index 0000000..c1179db
--- /dev/null
+++ b/sample/Tenant.Api.Contracts/Tenant.Api.Contracts.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/sample/Tenant.Api/DDD/Application/TenantAppService.cs b/sample/Tenant.Api/DDD/Application/TenantAppService.cs
new file mode 100644
index 0000000..8b43f6e
--- /dev/null
+++ b/sample/Tenant.Api/DDD/Application/TenantAppService.cs
@@ -0,0 +1,7 @@
+using Easy.DDD.Application;
+
+namespace Tenant.Api.DDD.Application;
+
+public class TenantAppService : IApiService
+{
+}
diff --git a/sample/Tenant.Api/DDD/Domain/TenantAggregation/ITenantManager.cs b/sample/Tenant.Api/DDD/Domain/TenantAggregation/ITenantManager.cs
new file mode 100644
index 0000000..fd87f75
--- /dev/null
+++ b/sample/Tenant.Api/DDD/Domain/TenantAggregation/ITenantManager.cs
@@ -0,0 +1,8 @@
+namespace Tenant.Api.DDD.Domain.TenantAggregate;
+
+public interface ITenantManager
+{
+ Task CreateAsync(string name);
+
+ Task ChangeNameAsync(Tenant tenant, string name);
+}
diff --git a/sample/Tenant.Api/DDD/Domain/TenantAggregation/Tenant.cs b/sample/Tenant.Api/DDD/Domain/TenantAggregation/Tenant.cs
new file mode 100644
index 0000000..4e72417
--- /dev/null
+++ b/sample/Tenant.Api/DDD/Domain/TenantAggregation/Tenant.cs
@@ -0,0 +1,56 @@
+using Easy;
+using Easy.DDD.Domain.Entities;
+using Tenant.Api.DDD.Domain.Consts;
+
+namespace Tenant.Api.DDD.Domain.TenantAggregation;
+
+public class Tenant : AggregateRoot
+{
+ public virtual string Name { get; protected set; }
+
+ public virtual List ConnectionStrings { get; protected set; }
+
+ protected Tenant()
+ {
+
+ }
+
+ protected internal Tenant(Guid id, string name)
+ : base(id)
+ {
+ SetName(name);
+
+ ConnectionStrings = new List();
+ }
+
+
+ public virtual void SetConnectionString(string name, string connectionString)
+ {
+ var tenantConnectionString = ConnectionStrings.FirstOrDefault(x => x.Name == name);
+
+ if (tenantConnectionString != null)
+ {
+ tenantConnectionString.SetValue(connectionString);
+ }
+ else
+ {
+ ConnectionStrings.Add(new TenantConnectionString(Id, name, connectionString));
+ }
+ }
+
+
+ public virtual void RemoveConnectionString(string name)
+ {
+ var tenantConnectionString = ConnectionStrings.FirstOrDefault(x => x.Name == name);
+
+ if (tenantConnectionString != null)
+ {
+ ConnectionStrings.Remove(tenantConnectionString);
+ }
+ }
+
+ protected internal virtual void SetName(string name)
+ {
+ Name = Check.NotNullOrWhiteSpace(name, nameof(name), TenantConsts.MaxNameLength);
+ }
+}
diff --git a/sample/Tenant.Api/DDD/Domain/TenantAggregation/TenantConnectionString.cs b/sample/Tenant.Api/DDD/Domain/TenantAggregation/TenantConnectionString.cs
new file mode 100644
index 0000000..9eeea0d
--- /dev/null
+++ b/sample/Tenant.Api/DDD/Domain/TenantAggregation/TenantConnectionString.cs
@@ -0,0 +1,36 @@
+using Easy;
+using Easy.DDD.Domain.Entities;
+using Tenant.Api.DDD.Domain.Consts;
+
+namespace Tenant.Api.DDD.Domain.TenantAggregation;
+
+public class TenantConnectionString : Entity
+{
+ public virtual Guid TenantId { get; protected set; }
+
+ public virtual string Name { get; protected set; }
+
+ public virtual string Value { get; protected set; }
+
+ protected TenantConnectionString()
+ {
+
+ }
+
+ public TenantConnectionString(Guid tenantId, string name, string value)
+ {
+ TenantId = tenantId;
+ Name = Check.NotNullOrWhiteSpace(name, nameof(name), TenantConnectionStringConsts.MaxNameLength);
+ SetValue(value);
+ }
+
+ public virtual void SetValue(string value)
+ {
+ Value = Check.NotNullOrWhiteSpace(value, nameof(value), TenantConnectionStringConsts.MaxValueLength);
+ }
+
+ public override object[] GetKeys()
+ {
+ return new object[] { TenantId, Name };
+ }
+}
diff --git a/sample/Tenant.Api/DDD/Domain/TenantAggregation/TenantManager.cs b/sample/Tenant.Api/DDD/Domain/TenantAggregation/TenantManager.cs
new file mode 100644
index 0000000..9af193b
--- /dev/null
+++ b/sample/Tenant.Api/DDD/Domain/TenantAggregation/TenantManager.cs
@@ -0,0 +1,44 @@
+using Easy;
+using Easy.DDD.Domain.Repositories;
+using Easy.Guids;
+using Microsoft.EntityFrameworkCore;
+
+namespace Tenant.Api.DDD.Domain.TenantAggregation;
+
+public class TenantManager
+{
+ public IRepository TenantRepository { get; }
+ protected IGuidGenerator GuidGenerator { get; }
+ public TenantManager(IRepository tenantRepository)
+ {
+ TenantRepository = tenantRepository;
+ }
+ public virtual async Task CreateAsync(string name)
+ {
+ Check.NotNull(name, nameof(name));
+
+ await ValidateNameAsync(name);
+ return new Tenant(GuidGenerator.Create(), name);
+ }
+
+ public virtual async Task ChangeNameAsync(Tenant tenant, string name)
+ {
+ Check.NotNull(tenant, nameof(tenant));
+ Check.NotNull(name, nameof(name));
+
+ await ValidateNameAsync(name, tenant.Id);
+ tenant.SetName(name);
+ }
+
+ protected virtual async Task ValidateNameAsync(string name, Guid? expectedId = null)
+ {
+ var tenant = await TenantRepository
+ .Where(o => o.Name == name)
+ .Include(x => x.ConnectionStrings)
+ .OrderBy(t => t.Id)
+ .FirstOrDefaultAsync();
+
+ When.Is(tenant != null && tenant.Id != expectedId, "重复的租户名称: " + name);
+
+ }
+}
diff --git a/sample/Tenant.Api/TenantMappingProfile.cs b/sample/Tenant.Api/TenantMappingProfile.cs
new file mode 100644
index 0000000..e0a4d8d
--- /dev/null
+++ b/sample/Tenant.Api/TenantMappingProfile.cs
@@ -0,0 +1,13 @@
+using AutoMapper;
+using Tenant.Api.DDD.Domain.Shared;
+
+namespace Tenant.Api.DDD.Domain;
+
+public class TenantMappingProfile : Profile
+{
+ public TenantMappingProfile()
+ {
+
+ CreateMap();
+ }
+}