mirror of https://github.com/abpframework/abp.git
committed by
GitHub
14 changed files with 377 additions and 17 deletions
@ -0,0 +1,48 @@ |
|||
using System.Collections.Generic; |
|||
using Microsoft.EntityFrameworkCore.Infrastructure; |
|||
using Microsoft.EntityFrameworkCore.Storage; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.MySQL; |
|||
|
|||
/* Registers ABP services into the EF Core internal service provider to patch |
|||
* MySQL provider issues (currently the Guid[] type mapping plugin). */ |
|||
internal sealed class AbpMySQLDbContextOptionsExtension : IDbContextOptionsExtension |
|||
{ |
|||
public DbContextOptionsExtensionInfo Info => new ExtensionInfo(this); |
|||
|
|||
public void ApplyServices(IServiceCollection services) |
|||
{ |
|||
services.TryAddEnumerable( |
|||
ServiceDescriptor.Singleton<IRelationalTypeMappingSourcePlugin, MySQLGuidArrayTypeMappingSourcePlugin>()); |
|||
} |
|||
|
|||
public void Validate(IDbContextOptions options) |
|||
{ |
|||
} |
|||
|
|||
private sealed class ExtensionInfo : DbContextOptionsExtensionInfo |
|||
{ |
|||
public ExtensionInfo(IDbContextOptionsExtension extension) |
|||
: base(extension) |
|||
{ |
|||
} |
|||
|
|||
public override bool IsDatabaseProvider => false; |
|||
|
|||
public override string LogFragment => "using AbpMySQL "; |
|||
|
|||
public override int GetServiceProviderHashCode() => 0; |
|||
|
|||
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) |
|||
{ |
|||
return other is ExtensionInfo; |
|||
} |
|||
|
|||
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo) |
|||
{ |
|||
debugInfo["Volo.Abp.EntityFrameworkCore.MySQL"] = "1"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Data; |
|||
using Microsoft.EntityFrameworkCore.Storage; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.MySQL; |
|||
|
|||
/* MySql.EntityFrameworkCore (up to 10.0.9) maps Guid[] query parameters to its |
|||
* scalar GUID mapping and throws NullReferenceException at parameter binding. |
|||
* This plugin runs before the provider's own lookup and returns the collection |
|||
* mapping the provider already builds for List<Guid>. Remove once the provider |
|||
* handles Guid[] parameters. */ |
|||
internal sealed class MySQLGuidArrayTypeMappingSourcePlugin : IRelationalTypeMappingSourcePlugin |
|||
{ |
|||
public RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo) |
|||
{ |
|||
if (mappingInfo.ClrType == typeof(Guid[]) && mappingInfo.ElementTypeMapping is not null) |
|||
{ |
|||
return new StringTypeMapping("longtext", DbType.String).Clone( |
|||
clrType: typeof(Guid[]), |
|||
elementMapping: mappingInfo.ElementTypeMapping); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Infrastructure; |
|||
using Microsoft.EntityFrameworkCore.Storage; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.EntityFrameworkCore.DependencyInjection; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.MySQL; |
|||
|
|||
public class MySQLGuidArrayTypeMappingSourcePlugin_Tests |
|||
{ |
|||
/* Runs without a MySQL server: type mapping lookup is metadata-only, no |
|||
* connection is opened. When upgrading the provider, verify its native |
|||
* Guid[] mapping without the plugin registered; once the provider handles |
|||
* Guid[] itself, remove MySQLGuidArrayTypeMappingSourcePlugin together |
|||
* with this test. */ |
|||
[Fact] |
|||
public void UseMySQL_Should_Map_Guid_Array_Parameter_To_A_Collection_Mapping() |
|||
{ |
|||
var services = new ServiceCollection(); |
|||
services.AddLogging(); |
|||
|
|||
var configurationContext = new AbpDbContextConfigurationContext( |
|||
"Server=localhost;Database=_;Uid=_;Pwd=_;", |
|||
services.BuildServiceProvider(), |
|||
null, |
|||
null); |
|||
configurationContext.UseMySQL(); |
|||
|
|||
using var dbContext = new PluginTestDbContext(configurationContext.DbContextOptions.Options); |
|||
var typeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>(); |
|||
var elementMapping = typeMappingSource.FindMapping(typeof(Guid))!; |
|||
|
|||
var mapping = typeMappingSource.FindMapping(typeof(Guid[]), dbContext.Model, elementMapping); |
|||
|
|||
mapping.ShouldNotBeNull(); |
|||
mapping.ClrType.ShouldBe(typeof(Guid[])); |
|||
mapping.StoreType.ShouldBe("longtext"); |
|||
mapping.ElementTypeMapping.ShouldBe(elementMapping); |
|||
} |
|||
|
|||
private class PluginTestDbContext : DbContext |
|||
{ |
|||
public PluginTestDbContext(DbContextOptions options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
|
|||
public DbSet<PluginTestEntity> Entities => Set<PluginTestEntity>(); |
|||
} |
|||
|
|||
private class PluginTestEntity |
|||
{ |
|||
public Guid Id { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
using System; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Mapperly; |
|||
|
|||
public class MapExtraPropertiesDefaultSeed_Tests : AbpIntegratedTest<MapperlyTestModule> |
|||
{ |
|||
private readonly IObjectMapper _objectMapper; |
|||
|
|||
public MapExtraPropertiesDefaultSeed_Tests() |
|||
{ |
|||
_objectMapper = ServiceProvider.GetRequiredService<IObjectMapper>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Single_Parameter_Map_Should_Preserve_Constructor_Seeded_Properties() |
|||
{ |
|||
var entity = new ExtensibleSeededEntity { Id = Guid.NewGuid() } |
|||
.SetProperty("Tag", "ok"); |
|||
|
|||
var dto = _objectMapper.Map<ExtensibleSeededEntity, ExtensibleSeededDto>(entity); |
|||
|
|||
dto.GetProperty<string>("Tag").ShouldBe("ok"); //Defined in both classes
|
|||
dto.GetProperty<string>("CreatedBy").ShouldBe("system"); //Set by the ExtensibleSeededDto constructor
|
|||
dto.HasProperty("DtoOnly").ShouldBeTrue(); //Not defined in the source, but was set to the default value by the ExtensibleObject constructor
|
|||
dto.GetProperty("DtoOnly").ShouldBeNull(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Single_Parameter_Map_Should_Not_Change_The_Source_Extra_Properties() |
|||
{ |
|||
var entity = new ExtensibleSeededEntity { Id = Guid.NewGuid() } |
|||
.SetProperty("Tag", "ok") |
|||
.SetProperty("CreatedBy", "leaked"); |
|||
var originalReference = entity.ExtraProperties; |
|||
|
|||
_objectMapper.Map<ExtensibleSeededEntity, ExtensibleSeededDto>(entity); |
|||
|
|||
ReferenceEquals(entity.ExtraProperties, originalReference).ShouldBeTrue(); |
|||
entity.GetProperty<string>("Tag").ShouldBe("ok"); |
|||
entity.GetProperty<string>("CreatedBy").ShouldBe("leaked"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Single_Parameter_Map_Should_Not_Seed_Defaults_When_Destination_Disables_Them() |
|||
{ |
|||
var entity = new ExtensibleSeededEntity { Id = Guid.NewGuid() } |
|||
.SetProperty("Tag", "ok"); |
|||
|
|||
var dto = _objectMapper.Map<ExtensibleSeededEntity, ExtensibleNonSeededDto>(entity); |
|||
|
|||
dto.GetProperty<string>("Tag").ShouldBe("ok"); //Defined in both classes
|
|||
dto.HasProperty("DtoOnly").ShouldBeFalse(); //ExtensibleNonSeededDto constructor disables the default values seeding
|
|||
} |
|||
|
|||
[Fact] |
|||
public void Single_Parameter_Map_Should_Not_Leak_Filtered_Source_Values_Into_Registered_Keys() |
|||
{ |
|||
var entity = new ExtensibleSeededEntity { Id = Guid.NewGuid() } |
|||
.SetProperty("Tag", "ok") |
|||
.SetProperty("DtoOnly", "leaked"); |
|||
|
|||
var dto = _objectMapper.Map<ExtensibleSeededEntity, ExtensibleNonSeededDto>(entity); |
|||
|
|||
dto.GetProperty<string>("Tag").ShouldBe("ok"); //Defined in both classes
|
|||
dto.GetProperty("DtoOnly").ShouldBeNull(); //Not defined in the source, the source value must not leak
|
|||
} |
|||
|
|||
[Fact] |
|||
public void Single_Parameter_Map_Should_Not_Invoke_Default_Value_Factories_Without_MapExtraProperties_Attribute() |
|||
{ |
|||
var entity = new ExtensibleSeededEntity { Id = Guid.NewGuid() } |
|||
.SetProperty("Tag", "ok"); |
|||
|
|||
var callsBefore = ExtensibleNoAttributeDto.CountedDefaultValueFactoryCalls; |
|||
|
|||
var dto = _objectMapper.Map<ExtensibleSeededEntity, ExtensibleNoAttributeDto>(entity); |
|||
|
|||
(ExtensibleNoAttributeDto.CountedDefaultValueFactoryCalls - callsBefore).ShouldBe(1); //Only the ExtensibleNoAttributeDto constructor seeding
|
|||
dto.HasProperty("Counted").ShouldBeTrue(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue