diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs index 9e52d1abfd..3bec827dac 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Entity.cs @@ -54,3 +54,55 @@ public abstract class Entity : Entity, IEntity return $"[ENTITY: {GetType().Name}] Id = {Id}"; } } + +/// +[Serializable] +public abstract class SharedEntity : Dictionary, IEntity +{ + protected SharedEntity() + { + EntityHelper.TrySetTenantId(this); + } + + /// + public override string ToString() + { + return $"[ENTITY: {GetType().Name}] Keys = {GetKeys().JoinAsString(", ")}"; + } + + public abstract object?[] GetKeys(); + + public bool EntityEquals(IEntity other) + { + return EntityHelper.EntityEquals(this, other); + } +} + +/// +[Serializable] +public abstract class SharedEntity : SharedEntity, IEntity +{ + /// + public virtual TKey Id { get; protected set; } = default!; + + protected SharedEntity() + { + + } + + protected SharedEntity(TKey id) + { + Id = id; + } + + public override object?[] GetKeys() + { + return new object?[] { Id }; + } + + /// + public override string ToString() + { + return $"[ENTITY: {GetType().Name}] Id = {Id}"; + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SharedEntity_Repository_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SharedEntity_Repository_Tests.cs new file mode 100644 index 0000000000..699266a69d --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/SharedEntity_Repository_Tests.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.Repositories; + +public class SharedEntity_Repository_Tests : EntityFrameworkCoreTestBase +{ + protected readonly IRepository TestSharedTypeEntityRepository; + protected readonly ICurrentTenant CurrentTenant; + protected readonly IDataFilter dataFilter; + + public SharedEntity_Repository_Tests() + { + TestSharedTypeEntityRepository = GetRequiredService>(); + CurrentTenant = GetRequiredService(); + dataFilter = GetRequiredService>(); + } + + [Fact] + public async Task SharedEntity_Test() + { + await WithUnitOfWorkAsync(async () => + { + TestSharedTypeEntityRepository.SetCustomEntityName("TestSharedEntity1"); + + var tenantId = Guid.NewGuid(); + await TestSharedTypeEntityRepository.InsertManyAsync(new List() + { + new TestSharedEntity(Guid.NewGuid()) + { + TenantId = null, + IsDeleted = false, + + ["Name"] = "Test Person1", + ["Age"] = 10, + ["Birthday"] = DateTime.Now + }.SetProperty("testProperty", "Test Value1"), + new TestSharedEntity(Guid.NewGuid()) + { + TenantId = tenantId, + IsDeleted = false, + + ["Name"] = "Test Person2", + ["Age"] = 20, + ["Birthday"] = DateTime.Now + }, + new TestSharedEntity(Guid.NewGuid()) + { + TenantId = tenantId, + IsDeleted = true, + + ["Name"] = "Test Person3", + ["Age"] = 30, + ["Birthday"] = DateTime.Now + }, + new TestSharedEntity(Guid.NewGuid()) + { + TenantId = null, + IsDeleted = true, + + ["Name"] = "Test Person4", + ["Age"] = 40, + ["Birthday"] = DateTime.Now + } + }, true); + + var entities = (await TestSharedTypeEntityRepository.GetListAsync()).OrderBy(x => x["Name"]).ToList(); + entities.Count.ShouldBe(1); + entities[0].TenantId.ShouldBeNull(); + entities[0].IsDeleted.ShouldBe(false); + entities[0]["Name"].ShouldBe("Test Person1"); + entities[0]["Age"].ShouldBe(10); + entities[0].GetProperty("testProperty").ShouldBe("Test Value1"); + + using (CurrentTenant.Change(tenantId)) + { + entities = (await TestSharedTypeEntityRepository.GetListAsync()).OrderBy(x => x["Name"]).ToList(); + entities.Count.ShouldBe(1); + entities[0].TenantId.ShouldBe(tenantId); + entities[0].IsDeleted.ShouldBe(false); + entities[0]["Name"].ShouldBe("Test Person2"); + entities[0]["Age"].ShouldBe(20); + } + + using (dataFilter.Disable()) + { + entities = (await TestSharedTypeEntityRepository.GetListAsync()).OrderBy(x => x["Name"]).ToList(); + entities.Count.ShouldBe(2); + + entities[0].TenantId.ShouldBeNull(); + entities[0].IsDeleted.ShouldBe(false); + entities[0]["Name"].ShouldBe("Test Person1"); + entities[0]["Age"].ShouldBe(10); + + entities[1].TenantId.ShouldBeNull(); + entities[1].IsDeleted.ShouldBe(true); + entities[1]["Name"].ShouldBe("Test Person4"); + entities[1]["Age"].ShouldBe(40); + } + + using (CurrentTenant.Change(tenantId)) + { + using (dataFilter.Disable()) + { + entities = (await TestSharedTypeEntityRepository.GetListAsync()).OrderBy(x => x["Name"]).ToList(); + entities.Count.ShouldBe(2); + + entities[0].TenantId.ShouldBe(tenantId); + entities[0].IsDeleted.ShouldBe(false); + entities[0]["Name"].ShouldBe("Test Person2"); + entities[0]["Age"].ShouldBe(20); + + entities[1].TenantId.ShouldBe(tenantId); + entities[1].IsDeleted.ShouldBe(true); + entities[1]["Name"].ShouldBe("Test Person3"); + entities[1]["Age"].ShouldBe(30); + } + } + + TestSharedTypeEntityRepository.SetCustomEntityName("TestSharedEntity2"); + await TestSharedTypeEntityRepository.InsertManyAsync(new List() + { + new TestSharedEntity(Guid.NewGuid()) + { + ["Name"] = "Test Person1 from Second Table", + ["Age"] = 110, + ["Birthday"] = DateTime.Now + } + }, true); + + var entitiesFromSecondTable = (await TestSharedTypeEntityRepository.GetListAsync()).OrderBy(x => x["Name"]).ToList(); + entitiesFromSecondTable.Count.ShouldBe(1); + entitiesFromSecondTable[0].TenantId.ShouldBeNull(); + entitiesFromSecondTable[0].IsDeleted.ShouldBe(false); + entitiesFromSecondTable[0]["Name"].ShouldBe("Test Person1 from Second Table"); + entitiesFromSecondTable[0]["Age"].ShouldBe(110); + }); + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index d05e367ff7..20524cd662 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -1,5 +1,6 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.TestApp.SecondContext; using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext; @@ -33,6 +34,9 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet Blogs { get; set; } public DbSet BlogPosts { get; set; } + public DbSet TestSharedEntity => Set("TestSharedEntity1"); + public DbSet TestSharedEntity2 => Set("TestSharedEntity2"); + public TestMigrationsDbContext(DbContextOptions options) : base(options) { @@ -41,8 +45,23 @@ public class TestMigrationsDbContext : AbpDbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { + // Owned and SharedTypeEntity should be configured before the base OnModelCreating call + modelBuilder.Owned(); + Action> sharedEntityBuildAction = b => + { + b.ConfigureByConvention(); + b.Property("Id"); + b.Property("TenantId"); + b.Property("IsDeleted"); + b.Property("Name"); + b.Property("Age"); + b.Property("Birthday"); + }; + modelBuilder.SharedTypeEntity("TestSharedEntity1", sharedEntityBuildAction); + modelBuilder.SharedTypeEntity("TestSharedEntity2", sharedEntityBuildAction); + base.OnModelCreating(modelBuilder); modelBuilder.Entity(b => diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index 242809bf7c..bc43a9ba02 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -1,6 +1,7 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; using Volo.Abp.EntityFrameworkCore; @@ -40,6 +41,9 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet Blogs { get; set; } public DbSet BlogPosts { get; set; } + public DbSet TestSharedEntity => Set("TestSharedEntity1"); + public DbSet TestSharedEntity2 => Set("TestSharedEntity2"); + public TestAppDbContext(DbContextOptions options) : base(options) { @@ -54,8 +58,23 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, protected override void OnModelCreating(ModelBuilder modelBuilder) { + // Owned and SharedTypeEntity should be configured before the base OnModelCreating call + modelBuilder.Owned(); + Action> sharedEntityBuildAction = b => + { + b.ConfigureByConvention(); + b.Property("Id"); + b.Property("TenantId"); + b.Property("IsDeleted"); + b.Property("Name"); + b.Property("Age"); + b.Property("Birthday"); + }; + modelBuilder.SharedTypeEntity("TestSharedEntity1", sharedEntityBuildAction); + modelBuilder.SharedTypeEntity("TestSharedEntity2", sharedEntityBuildAction); + base.OnModelCreating(modelBuilder); modelBuilder.Entity(b => diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/TestSharedTypeEntity.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/TestSharedTypeEntity.cs new file mode 100644 index 0000000000..df8f5e17fa --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/TestSharedTypeEntity.cs @@ -0,0 +1,28 @@ +using System; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.TestApp.Domain; + +public class TestSharedEntity : SharedEntity, IMultiTenant, ISoftDelete, IHasExtraProperties +{ + public Guid? TenantId { get; set; } + + public bool IsDeleted { get; set; } + + public TestSharedEntity() + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public TestSharedEntity(Guid id) + : base(id) + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public ExtraPropertyDictionary ExtraProperties { get; set; } +}