mirror of https://github.com/abpframework/abp.git
25 changed files with 231 additions and 421 deletions
@ -0,0 +1,61 @@ |
|||||
|
# Dapper Integration |
||||
|
|
||||
|
Because Dapper's idea is that the sql statement takes precedence, and mainly provides some extension methods for the `IDbConnection` interface. |
||||
|
|
||||
|
Abp does not encapsulate too many functions for Dapper. Abp Dapper provides a `DapperRepository<TDbContext>` base class based on Abp EntityFrameworkCore, which provides the `IDbConnection` and `IDbTransaction` properties required by Dapper. |
||||
|
|
||||
|
These two properties can work well with [Unit-Of-Work](Unit-Of-Work.md). |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
Please install and configure EF Core according to [EF Core's integrated documentation](Entity-Framework-Core.md). |
||||
|
|
||||
|
`Volo.Abp.Dapper` is the main nuget package for the Dapper integration. Install it to your project (for a layered application, to your data/infrastructure layer): |
||||
|
|
||||
|
```shell |
||||
|
Install-Package Volo.Abp.Dapper |
||||
|
``` |
||||
|
|
||||
|
Then add `AbpDapperModule` module dependency (`DependsOn` attribute) to your [module](Module-Development-Basics.md): |
||||
|
|
||||
|
````C# |
||||
|
using Volo.Abp.Dapper; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace MyCompany.MyProject |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpDapperModule))] |
||||
|
public class MyModule : AbpModule |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## Implement Dapper Repository |
||||
|
|
||||
|
The following code implements the `Person` repository, which requires EF Core's `DbContext` (MyAppDbContext). You can inject `PersonDapperRepository` to call its methods. |
||||
|
|
||||
|
`DbConnection` and `DbTransaction` are from the `DapperRepository` base class. |
||||
|
|
||||
|
```C# |
||||
|
public class PersonDapperRepository : DapperRepository<MyAppDbContext>, ITransientDependency |
||||
|
{ |
||||
|
public PersonDapperRepository(IDbContextProvider<MyAppDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<string>> GetAllPersonNames() |
||||
|
{ |
||||
|
return (await DbConnection.QueryAsync<string>("select Name from People", transaction: DbTransaction)) |
||||
|
.ToList(); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<int> UpdatePersonNames(string name) |
||||
|
{ |
||||
|
return await DbConnection.ExecuteAsync("update People set Name = @NewName", new { NewName = name }, |
||||
|
DbTransaction); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
@ -0,0 +1,61 @@ |
|||||
|
# Dapper 集成 |
||||
|
|
||||
|
由于Dapper的思想是sql语句优先, 且主要为`IDbConnection`接口提供了一些扩展方法. |
||||
|
|
||||
|
Abp并没有为Dapper封装太多功能. Abp Dapper在Abp EntityFrameworkCore的基础上提供了`DapperRepository<TDbContext>`基类, 在其中提供了Dapper需要的`IDbConnection`和`IDbTransaction`属性. |
||||
|
|
||||
|
这两个属性可以和[工作单元](Unit-Of-Work.md)很好的配合. |
||||
|
|
||||
|
## 安装 |
||||
|
|
||||
|
请先根据[EF Core的集成文档](Entity-Framework-Core.md)安装并配置好EF Core. |
||||
|
|
||||
|
`Volo.Abp.Dapper`是Dapper集成的主要nuget包. 将其安装到你的项目中(在分层应用程序中适用于 数据访问/基础设施层): |
||||
|
|
||||
|
```shell |
||||
|
Install-Package Volo.Abp.Dapper |
||||
|
``` |
||||
|
|
||||
|
然后添加 `AbpDapperModule` 模块依赖项(`DependsOn` Attribute) 到 [module](Module-Development-Basics.cn.md)(项目中的Mudole类): |
||||
|
|
||||
|
````C# |
||||
|
using Volo.Abp.Dapper; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace MyCompany.MyProject |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpDapperModule))] |
||||
|
public class MyModule : AbpModule |
||||
|
{ |
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## 实现Dapper仓储 |
||||
|
|
||||
|
下面的代码实现了`Person`仓储, 它需要EF Core的`DbContext`(MyAppDbContext). 你可以注入`PersonDapperRepository`来调用它的方法. |
||||
|
|
||||
|
`DbConnection`和`DbTransaction`来自于`DapperRepository`基类. |
||||
|
|
||||
|
```C# |
||||
|
public class PersonDapperRepository : DapperRepository<MyAppDbContext>, ITransientDependency |
||||
|
{ |
||||
|
public PersonDapperRepository(IDbContextProvider<MyAppDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<string>> GetAllPersonNames() |
||||
|
{ |
||||
|
return (await DbConnection.QueryAsync<string>("select Name from People", transaction: DbTransaction)) |
||||
|
.ToList(); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<int> UpdatePersonNames(string name) |
||||
|
{ |
||||
|
return await DbConnection.ExecuteAsync("update People set Name = @NewName", new { NewName = name }, |
||||
|
DbTransaction); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
@ -1,29 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using AutoMapper; |
|
||||
|
|
||||
namespace Volo.Abp.AutoMapper |
|
||||
{ |
|
||||
public class AutoMapAttribute : AutoMapAttributeBase |
|
||||
{ |
|
||||
public AutoMapAttribute(params Type[] targetTypes) |
|
||||
: base(targetTypes) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public override void CreateMap(IMapperConfigurationExpression configuration, Type type) |
|
||||
{ |
|
||||
if (TargetTypes.IsNullOrEmpty()) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
foreach (var targetType in TargetTypes) |
|
||||
{ |
|
||||
configuration.CreateMap(type, targetType, MemberList.Source); |
|
||||
configuration.CreateMap(targetType, type, MemberList.Destination); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
using System; |
|
||||
using AutoMapper; |
|
||||
|
|
||||
namespace Volo.Abp.AutoMapper |
|
||||
{ |
|
||||
public abstract class AutoMapAttributeBase : Attribute |
|
||||
{ |
|
||||
public Type[] TargetTypes { get; } |
|
||||
|
|
||||
protected AutoMapAttributeBase(params Type[] targetTypes) |
|
||||
{ |
|
||||
TargetTypes = targetTypes; |
|
||||
} |
|
||||
|
|
||||
public abstract void CreateMap(IMapperConfigurationExpression configuration, Type type); |
|
||||
} |
|
||||
} |
|
||||
@ -1,36 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using AutoMapper; |
|
||||
|
|
||||
namespace Volo.Abp.AutoMapper |
|
||||
{ |
|
||||
public class AutoMapFromAttribute : AutoMapAttributeBase |
|
||||
{ |
|
||||
public MemberList MemberList { get; set; } = MemberList.Destination; |
|
||||
|
|
||||
public AutoMapFromAttribute(params Type[] targetTypes) |
|
||||
: base(targetTypes) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public AutoMapFromAttribute(MemberList memberList, params Type[] targetTypes) |
|
||||
: this(targetTypes) |
|
||||
{ |
|
||||
MemberList = memberList; |
|
||||
} |
|
||||
|
|
||||
public override void CreateMap(IMapperConfigurationExpression configuration, Type type) |
|
||||
{ |
|
||||
if (TargetTypes.IsNullOrEmpty()) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
foreach (var targetType in TargetTypes) |
|
||||
{ |
|
||||
configuration.CreateMap(targetType, type, MemberList); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,36 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using AutoMapper; |
|
||||
|
|
||||
namespace Volo.Abp.AutoMapper |
|
||||
{ |
|
||||
public class AutoMapToAttribute : AutoMapAttributeBase |
|
||||
{ |
|
||||
public MemberList MemberList { get; set; } = MemberList.Source; |
|
||||
|
|
||||
public AutoMapToAttribute(params Type[] targetTypes) |
|
||||
: base(targetTypes) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public AutoMapToAttribute(MemberList memberList, params Type[] targetTypes) |
|
||||
: this(targetTypes) |
|
||||
{ |
|
||||
MemberList = memberList; |
|
||||
} |
|
||||
|
|
||||
public override void CreateMap(IMapperConfigurationExpression configuration, Type type) |
|
||||
{ |
|
||||
if (TargetTypes.IsNullOrEmpty()) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
foreach (var targetType in TargetTypes) |
|
||||
{ |
|
||||
configuration.CreateMap(type, targetType, MemberList); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
using System; |
|
||||
using System.Reflection; |
|
||||
using AutoMapper; |
|
||||
|
|
||||
namespace Volo.Abp.AutoMapper |
|
||||
{ |
|
||||
internal static class AutoMapperConfigurationExtensions |
|
||||
{ |
|
||||
public static void CreateAutoAttributeMaps(this IMapperConfigurationExpression configuration, Type type) |
|
||||
{ |
|
||||
foreach (var autoMapAttribute in type.GetTypeInfo().GetCustomAttributes<AutoMapAttributeBase>()) |
|
||||
{ |
|
||||
autoMapAttribute.CreateMap(configuration, type); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,75 +0,0 @@ |
|||||
using AutoMapper; |
|
||||
using Shouldly; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Volo.Abp.AutoMapper |
|
||||
{ |
|
||||
public class AutoMapper_Inheritance_Tests |
|
||||
{ |
|
||||
private readonly IMapper _mapper; |
|
||||
|
|
||||
public AutoMapper_Inheritance_Tests() |
|
||||
{ |
|
||||
var config = new MapperConfiguration(configuration => |
|
||||
{ |
|
||||
configuration.CreateAutoAttributeMaps(typeof(MyTargetClassToMap)); |
|
||||
configuration.CreateAutoAttributeMaps(typeof(EntityDto)); |
|
||||
configuration.CreateAutoAttributeMaps(typeof(DerivedEntityDto)); |
|
||||
}); |
|
||||
|
|
||||
_mapper = config.CreateMapper(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_Map_Derived_To_Target() |
|
||||
{ |
|
||||
var derived = new MyDerivedClass { Value = "fortytwo" }; |
|
||||
var target = _mapper.Map<MyTargetClassToMap>(derived); |
|
||||
target.Value.ShouldBe("fortytwo"); |
|
||||
} |
|
||||
|
|
||||
public class MyBaseClass |
|
||||
{ |
|
||||
public string Value { get; set; } |
|
||||
} |
|
||||
|
|
||||
public class MyDerivedClass : MyBaseClass |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
[AutoMapFrom(typeof(MyBaseClass))] |
|
||||
public class MyTargetClassToMap |
|
||||
{ |
|
||||
public string Value { get; set; } |
|
||||
} |
|
||||
|
|
||||
//[Fact] //TODO: That's a problem but related to AutoMapper rather than ABP.
|
|
||||
public void Should_Map_EntityProxy_To_EntityDto_And_To_DrivedEntityDto() |
|
||||
{ |
|
||||
var proxy = new EntityProxy() { Value = "42"}; |
|
||||
var target = _mapper.Map<EntityDto>(proxy); |
|
||||
var target2 = _mapper.Map<DerivedEntityDto>(proxy); |
|
||||
target.Value.ShouldBe("42"); |
|
||||
target2.Value.ShouldBe("42"); |
|
||||
} |
|
||||
|
|
||||
private class Entity |
|
||||
{ |
|
||||
public string Value { get; set; } |
|
||||
} |
|
||||
|
|
||||
private class DerivedEntity : Entity { } |
|
||||
|
|
||||
private class EntityProxy : DerivedEntity { } |
|
||||
|
|
||||
[AutoMapFrom(typeof(Entity))] |
|
||||
private class EntityDto |
|
||||
{ |
|
||||
public string Value { get; set; } |
|
||||
} |
|
||||
|
|
||||
[AutoMapFrom(typeof(DerivedEntity))] |
|
||||
private class DerivedEntityDto : EntityDto { } |
|
||||
} |
|
||||
} |
|
||||
@ -1,157 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using AutoMapper; |
|
||||
using Shouldly; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Volo.Abp.AutoMapper |
|
||||
{ |
|
||||
public class AutoMapping_Tests |
|
||||
{ |
|
||||
private readonly IMapper _mapper; |
|
||||
|
|
||||
public AutoMapping_Tests() |
|
||||
{ |
|
||||
var config = new MapperConfiguration(configuration => |
|
||||
{ |
|
||||
configuration.CreateAutoAttributeMaps(typeof(MyClass1)); |
|
||||
configuration.CreateAutoAttributeMaps(typeof(MyClass2)); |
|
||||
}); |
|
||||
|
|
||||
_mapper = config.CreateMapper(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Map_Null_Tests() |
|
||||
{ |
|
||||
MyClass1 obj1 = null; |
|
||||
var obj2 = _mapper.Map<MyClass2>(obj1); |
|
||||
obj2.ShouldBe(null); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Map_Null_Existing_Object_Tests() |
|
||||
{ |
|
||||
MyClass1 obj1 = null; |
|
||||
|
|
||||
var obj2 = new MyClass2 { TestProp = "before map" }; |
|
||||
_mapper.Map(obj1, obj2); |
|
||||
obj2.TestProp.ShouldBe("before map"); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void MapTo_Tests() |
|
||||
{ |
|
||||
var obj1 = new MyClass1 { TestProp = "Test value" }; |
|
||||
|
|
||||
var obj2 = _mapper.Map<MyClass2>(obj1); |
|
||||
obj2.TestProp.ShouldBe("Test value"); |
|
||||
|
|
||||
var obj3 = _mapper.Map<MyClass3>(obj1); |
|
||||
obj3.TestProp.ShouldBe("Test value"); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void MapTo_Existing_Object_Tests() |
|
||||
{ |
|
||||
var obj1 = new MyClass1 { TestProp = "Test value" }; |
|
||||
|
|
||||
var obj2 = new MyClass2(); |
|
||||
_mapper.Map(obj1, obj2); |
|
||||
obj2.TestProp.ShouldBe("Test value"); |
|
||||
|
|
||||
var obj3 = new MyClass3(); |
|
||||
_mapper.Map(obj2, obj3); |
|
||||
obj3.TestProp.ShouldBe("Test value"); |
|
||||
|
|
||||
Assert.ThrowsAny<Exception>(() => //Did not define reverse mapping!
|
|
||||
{ |
|
||||
_mapper.Map(obj3, obj2); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void MapFrom_Tests() |
|
||||
{ |
|
||||
var obj2 = new MyClass2 { TestProp = "Test value" }; |
|
||||
|
|
||||
var obj1 = _mapper.Map<MyClass1>(obj2); |
|
||||
obj1.TestProp.ShouldBe("Test value"); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void IgnoreMap_Tests() |
|
||||
{ |
|
||||
var obj2 = new MyClass2 {TestProp = "Test value", AnotherValue = 42}; |
|
||||
var obj3 = _mapper.Map<MyClass3>(obj2); |
|
||||
obj3.TestProp.ShouldBe("Test value"); |
|
||||
obj3.AnotherValue.ShouldBe(0); //Ignored because of IgnoreMap attribute!
|
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void MapTo_Collection_Tests() |
|
||||
{ |
|
||||
var list1 = new List<MyClass1> |
|
||||
{ |
|
||||
new MyClass1 {TestProp = "Test value 1"}, |
|
||||
new MyClass1 {TestProp = "Test value 2"} |
|
||||
}; |
|
||||
|
|
||||
var list2 = _mapper.Map<List<MyClass2>>(list1); |
|
||||
list2.Count.ShouldBe(2); |
|
||||
list2[0].TestProp.ShouldBe("Test value 1"); |
|
||||
list2[1].TestProp.ShouldBe("Test value 2"); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Map_Should_Set_Null_Existing_Object_Tests() |
|
||||
{ |
|
||||
MyClass1 obj1 = new MyClass1 { TestProp = null }; |
|
||||
var obj2 = new MyClass2 { TestProp = "before map" }; |
|
||||
_mapper.Map(obj1, obj2); |
|
||||
obj2.TestProp.ShouldBe(null); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_Map_Nullable_Value_To_Null_If_It_Is_Null_On_Source() |
|
||||
{ |
|
||||
var obj1 = new MyClass1(); |
|
||||
var obj2 = _mapper.Map<MyClass2>(obj1); |
|
||||
obj2.NullableValue.ShouldBeNull(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_Map_Nullable_Value_To__Not_Null_If_It_Is__Not_Null_On_Source() |
|
||||
{ |
|
||||
var obj1 = new MyClass1 { NullableValue = 42 }; |
|
||||
var obj2 = _mapper.Map<MyClass2>(obj1); |
|
||||
obj2.NullableValue.ShouldBe(42); |
|
||||
} |
|
||||
|
|
||||
[AutoMap(typeof(MyClass2), typeof(MyClass3))] |
|
||||
private class MyClass1 |
|
||||
{ |
|
||||
public string TestProp { get; set; } |
|
||||
|
|
||||
public long? NullableValue { get; set; } |
|
||||
} |
|
||||
|
|
||||
[AutoMapTo(typeof(MyClass3))] |
|
||||
private class MyClass2 |
|
||||
{ |
|
||||
public string TestProp { get; set; } |
|
||||
|
|
||||
public long? NullableValue { get; set; } |
|
||||
|
|
||||
public int AnotherValue { get; set; } |
|
||||
} |
|
||||
|
|
||||
private class MyClass3 |
|
||||
{ |
|
||||
public string TestProp { get; set; } |
|
||||
|
|
||||
[IgnoreMap] |
|
||||
public int AnotherValue { get; set; } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,12 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace Volo.Abp.AutoMapper.SampleClasses |
||||
|
{ |
||||
|
public class MyMapProfile : Profile |
||||
|
{ |
||||
|
public MyMapProfile() |
||||
|
{ |
||||
|
CreateMap<MyEntity, MyEntityDto>().ReverseMap(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace Volo.Abp.TestApp.Domain |
||||
|
{ |
||||
|
public class TestAutoMapProfile : Profile |
||||
|
{ |
||||
|
public TestAutoMapProfile() |
||||
|
{ |
||||
|
CreateMap<PersonEto, Person>().ReverseMap(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue