├── .appveyor.yml
├── .gitattributes
├── .gitignore
├── .travis.yml
├── Dapper.FluentMap.sln
├── LICENSE
├── NuGet.Config
├── README.md
├── src
├── Dapper.FluentMap.Dommel
│ ├── Dapper.FluentMap.Dommel.csproj
│ ├── FluentMapConfigurationExtensions.cs
│ ├── Mapping
│ │ ├── DommelEntityMap.cs
│ │ └── DommelPropertyMap.cs
│ └── Resolvers
│ │ ├── DommelColumnNameResolver.cs
│ │ ├── DommelKeyPropertyResolver.cs
│ │ ├── DommelPropertyResolver.cs
│ │ └── DommelTableNameResolver.cs
└── Dapper.FluentMap
│ ├── Configuration
│ ├── FluentConventionConfiguration.cs
│ └── FluentMapConfiguration.cs
│ ├── Conventions
│ ├── Convention.cs
│ ├── ConventionPropertyConfiguration.cs
│ └── PropertyConventionConfiguration.cs
│ ├── Dapper.FluentMap.csproj
│ ├── FluentMapper.cs
│ ├── Mapping
│ ├── EntityMap.cs
│ └── PropertyMap.cs
│ ├── TypeMaps
│ ├── FluentConventionTypeMap.cs
│ ├── FluentTypeMap.cs
│ ├── IgnoredPropertyInfo.cs
│ └── MultiTypeMap.cs
│ └── Utils
│ ├── DictionaryExtensions.cs
│ ├── FluentMapConfigurationExtensions.cs
│ └── ReflectionHelper.cs
└── test
├── Dapper.FluentMap.Dommel.Tests
├── Dapper.FluentMap.Dommel.Tests.csproj
├── ManualMappingTests.cs
└── TestEntity.cs
└── Dapper.FluentMap.Tests
├── ConventionTests.cs
├── Dapper.FluentMap.Tests.csproj
├── ManualMappingTests.cs
├── ReflectionHelperTests.cs
└── TestEntity.cs
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | pull_requests:
3 | do_not_increment_build_number: true
4 | image: Visual Studio 2019
5 | build_script:
6 | - cmd: dotnet test
7 | test: off
8 | environment:
9 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
10 | DOTNET_CLI_TELEMETRY_OPTOUT: true
11 | CI: true
12 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj/
2 | [Bb]in/
3 | TestResults/
4 | .nuget/
5 | *.sln.ide/
6 | _ReSharper.*/
7 | packages/
8 | artifacts/
9 | PublishProfiles/
10 | .vs/
11 | bower_components/
12 | node_modules/
13 | **/wwwroot/lib/
14 | debugSettings.json
15 | project.lock.json
16 | *.user
17 | *.suo
18 | *.cache
19 | *.docstates
20 | _ReSharper.*
21 | nuget.exe
22 | *net45.csproj
23 | *net451.csproj
24 | *k10.csproj
25 | *.psess
26 | *.vsp
27 | *.pidb
28 | *.userprefs
29 | *DS_Store
30 | *.ncrunchsolution
31 | *.*sdf
32 | *.ipch
33 | .settings
34 | *.sln.ide
35 | node_modules
36 | **/[Cc]ompiler/[Rr]esources/**/*.js
37 | deploy/
38 | /.build
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | mono: none
3 | dotnet: 3.1
4 | dist: xenial
5 | env:
6 | global:
7 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true
8 | - DOTNET_CLI_TELEMETRY_OPTOUT=true
9 | script:
10 | - dotnet test
11 |
--------------------------------------------------------------------------------
/Dapper.FluentMap.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 16
3 | VisualStudioVersion = 16.0.29613.14
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{580E3446-6579-4414-9875-970849E635E5}"
6 | EndProject
7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{04C7C13D-7D23-42C4-9D6F-B76D94BCAA8C}"
8 | ProjectSection(SolutionItems) = preProject
9 | NuGet.Config = NuGet.Config
10 | EndProjectSection
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{742442F2-CAE7-4DC8-BD73-8C54C0005A53}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.FluentMap", "src\Dapper.FluentMap\Dapper.FluentMap.csproj", "{457E0B9B-F6A4-42C6-BFAE-6F8C71D1F435}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.FluentMap.Tests", "test\Dapper.FluentMap.Tests\Dapper.FluentMap.Tests.csproj", "{8901F2FD-F98B-484B-A20A-7844A39C7458}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.FluentMap.Dommel", "src\Dapper.FluentMap.Dommel\Dapper.FluentMap.Dommel.csproj", "{E60B79F6-FE71-44E0-BE88-BFA269378EDB}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.FluentMap.Dommel.Tests", "test\Dapper.FluentMap.Dommel.Tests\Dapper.FluentMap.Dommel.Tests.csproj", "{DFB62D87-9A74-40DF-A930-8F61A53E0F1B}"
21 | EndProject
22 | Global
23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
24 | Debug|Any CPU = Debug|Any CPU
25 | Release|Any CPU = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 | {457E0B9B-F6A4-42C6-BFAE-6F8C71D1F435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {457E0B9B-F6A4-42C6-BFAE-6F8C71D1F435}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {457E0B9B-F6A4-42C6-BFAE-6F8C71D1F435}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {457E0B9B-F6A4-42C6-BFAE-6F8C71D1F435}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {8901F2FD-F98B-484B-A20A-7844A39C7458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {8901F2FD-F98B-484B-A20A-7844A39C7458}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {8901F2FD-F98B-484B-A20A-7844A39C7458}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {8901F2FD-F98B-484B-A20A-7844A39C7458}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {E60B79F6-FE71-44E0-BE88-BFA269378EDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {E60B79F6-FE71-44E0-BE88-BFA269378EDB}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {E60B79F6-FE71-44E0-BE88-BFA269378EDB}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {E60B79F6-FE71-44E0-BE88-BFA269378EDB}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {DFB62D87-9A74-40DF-A930-8F61A53E0F1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {DFB62D87-9A74-40DF-A930-8F61A53E0F1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {DFB62D87-9A74-40DF-A930-8F61A53E0F1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {DFB62D87-9A74-40DF-A930-8F61A53E0F1B}.Release|Any CPU.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(NestedProjects) = preSolution
49 | {457E0B9B-F6A4-42C6-BFAE-6F8C71D1F435} = {580E3446-6579-4414-9875-970849E635E5}
50 | {8901F2FD-F98B-484B-A20A-7844A39C7458} = {742442F2-CAE7-4DC8-BD73-8C54C0005A53}
51 | {E60B79F6-FE71-44E0-BE88-BFA269378EDB} = {580E3446-6579-4414-9875-970849E635E5}
52 | {DFB62D87-9A74-40DF-A930-8F61A53E0F1B} = {742442F2-CAE7-4DC8-BD73-8C54C0005A53}
53 | EndGlobalSection
54 | GlobalSection(ExtensibilityGlobals) = postSolution
55 | SolutionGuid = {10834736-59FD-47FF-9344-096247DC48CD}
56 | EndGlobalSection
57 | EndGlobal
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Henk Mollema
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 📦 Archived
2 | This repository is archived as I'm not using this library myself anymore and have no time maintaining it. Thanks for using it.
3 |
4 |
5 |
6 |
7 | # Dapper.FluentMap
8 | Provides a simple API to fluently map POCO properties to database columns when using Dapper.
9 |
10 |
11 |
12 | | Windows | Linux/OSX | NuGet |
13 | | --- | --- | --- |
14 | | [](https://ci.appveyor.com/project/henkmollema/dapper-fluentmap) | [](https://travis-ci.org/henkmollema/Dapper-FluentMap) | [](https://www.nuget.org/packages/Dapper.FluentMap/ "NuGet version") |
15 |
16 | ### Introduction
17 |
18 | This [Dapper](https://github.com/StackExchange/dapper-dot-net) extension allows you to fluently configure the mapping between POCO properties and database columns. This keeps your POCO's clean of mapping attributes. The functionality is similar to [Entity Framework Fluent API](http://msdn.microsoft.com/nl-nl/data/jj591617.aspx). If you have any questions, suggestions or bugs, please don't hesitate to [contact me](mailto:henkmollema@gmail.com) or create an issue.
19 |
20 |
21 |
22 | ### Download
23 | [](https://www.nuget.org/packages/Dapper.FluentMap)
24 |
25 |
26 |
27 | ### Usage
28 | #### Manual mapping
29 | You can map property names manually using the [`EntityMap`](https://github.com/henkmollema/Dapper-FluentMap/blob/master/src/Dapper.FluentMap/Mapping/EntityMap.cs) class. When creating a derived class, the constructor gives you access to the `Map` method, allowing you to specify to which database column name a certain property of `TEntity` should map to.
30 | ```csharp
31 | public class ProductMap : EntityMap
32 | {
33 | public ProductMap()
34 | {
35 | // Map property 'Name' to column 'strName'.
36 | Map(p => p.Name)
37 | .ToColumn("strName");
38 |
39 | // Ignore the 'LastModified' property when mapping.
40 | Map(p => p.LastModified)
41 | .Ignore();
42 | }
43 | }
44 | ```
45 |
46 | Column names are mapped case sensitive by default. You can change this by specifying the `caseSensitive` parameter in the `ToColumn()` method: `Map(p => p.Name).ToColumn("strName", caseSensitive: false)`.
47 |
48 | **Initialization:**
49 | ```csharp
50 | FluentMapper.Initialize(config =>
51 | {
52 | config.AddMap(new ProductMap());
53 | });
54 | ```
55 |
56 | #### Convention based mapping
57 | When you have a lot of entity types, creating manual mapping classes can become plumbing. If your column names adhere to some kind of naming convention, you might be better off by configuring a mapping convention.
58 |
59 | You can create a convention by creating a class which derives from the [`Convention`](https://github.com/henkmollema/Dapper-FluentMap/blob/master/src/Dapper.FluentMap/Conventions/Convention.cs) class. In the contructor you can configure the property conventions:
60 | ```csharp
61 | public class TypePrefixConvention : Convention
62 | {
63 | public TypePrefixConvention()
64 | {
65 | // Map all properties of type int and with the name 'id' to column 'autID'.
66 | Properties()
67 | .Where(c => c.Name.ToLower() == "id")
68 | .Configure(c => c.HasColumnName("autID"));
69 |
70 | // Prefix all properties of type string with 'str' when mapping to column names.
71 | Properties()
72 | .Configure(c => c.HasPrefix("str"));
73 |
74 | // Prefix all properties of type int with 'int' when mapping to column names.
75 | Properties()
76 | .Configure(c => c.HasPrefix("int"));
77 | }
78 | }
79 | ```
80 |
81 | When initializing Dapper.FluentMap with conventions, the entities on which a convention applies must be configured. You can choose to either configure the entities explicitly or use assembly scanning.
82 |
83 | ```csharp
84 | FluentMapper.Initialize(config =>
85 | {
86 | // Configure entities explicitly.
87 | config.AddConvention()
88 | .ForEntity()
89 | .ForEntity;
90 |
91 | // Configure all entities in a certain assembly with an optional namespaces filter.
92 | config.AddConvention()
93 | .ForEntitiesInAssembly(typeof(Product).Assembly, "App.Domain.Model");
94 |
95 | // Configure all entities in the current assembly with an optional namespaces filter.
96 | config.AddConvention()
97 | .ForEntitiesInCurrentAssembly("App.Domain.Model.Catalog", "App.Domain.Model.Order");
98 | });
99 | ```
100 |
101 | ##### Transformations
102 | The convention API allows you to configure transformation of property names to database column names. An implementation would look like this:
103 | ```csharp
104 | public class PropertyTransformConvention : Convention
105 | {
106 | public PropertyTransformConvention()
107 | {
108 | Properties()
109 | .Configure(c => c.Transform(s => Regex.Replace(input: s, pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4")));
110 | }
111 | }
112 | ```
113 |
114 | This configuration will map camel case property names to underscore seperated database column names (`UrlOptimizedName` -> `Url_Optimized_Name`).
115 |
116 |
117 |
118 | ### [Dommel](https://github.com/henkmollema/Dommel)
119 | Dommel contains a set of extensions methods providing easy CRUD operations using Dapper. One of the goals was to provide extension points for resolving table and column names. [Dapper.FluentMap.Dommel](https://github.com/henkmollema/Dapper-FluentMap/tree/master/src/Dapper.FluentMap.Dommel) implements certain interfaces of Dommel and uses the configured mapping. It also provides more mapping functionality.
120 |
121 | #### [`PM> Install-Package Dapper.FluentMap.Dommel`](https://www.nuget.org/packages/Dapper.FluentMap.Dommel)
122 |
123 | #### Usage
124 | ##### `DommelEntityMap`
125 | This class derives from `EntityMap` and allows you to map an entity to a database table using the `ToTable()` method:
126 |
127 | ```csharp
128 | public class ProductMap : DommelEntityMap
129 | {
130 | public ProductMap()
131 | {
132 | ToTable("tblProduct");
133 |
134 | // ...
135 | }
136 | }
137 | ```
138 |
139 | ##### `DommelPropertyMap`
140 | This class derives `PropertyMap` and allows you to specify the key property of an entity using the `IsKey` method:
141 |
142 | ```csharp
143 | public class ProductMap : DommelEntityMap
144 | {
145 | public ProductMap()
146 | {
147 | Map(p => p.Id).IsKey();
148 | }
149 | }
150 | ```
151 |
152 | You can configure Dapper.FluentMap.Dommel in the `FluentMapper.Initialize()` method:
153 |
154 | ```csharp
155 | FluentMapper.Initialize(config =>
156 | {
157 | config.AddMap(new ProductMap());
158 | config.ForDommel();
159 | });
160 | ```
161 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/Dapper.FluentMap.Dommel.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dapper.FluentMap extension for Dommel support.
4 | Copyright © Henk Mollema 2014
5 | 2.0.0
6 | Henk Mollema
7 | netstandard2.0
8 | true
9 | dapper;fluentmap;dommel
10 | https://github.com/henkmollema/Dapper-FluentMap
11 | https://github.com/henkmollema/Dapper-FluentMap/blob/master/LICENSE
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/FluentMapConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | using Dapper.FluentMap.Configuration;
2 | using Dapper.FluentMap.Dommel.Resolvers;
3 | using Dommel;
4 |
5 | namespace Dapper.FluentMap.Dommel
6 | {
7 | ///
8 | /// Defines methods for configured Dapper.FluentMap.Dommel.
9 | ///
10 | public static class FluentMapConfigurationExtensions
11 | {
12 | ///
13 | /// Configures the specified configuration for Dapper.FluentMap.Dommel.
14 | ///
15 | /// The Dapper.FluentMap configuration.
16 | /// The Dapper.FluentMap configuration.
17 | public static FluentMapConfiguration ForDommel(this FluentMapConfiguration config)
18 | {
19 | DommelMapper.SetColumnNameResolver(new DommelColumnNameResolver());
20 | DommelMapper.SetKeyPropertyResolver(new DommelKeyPropertyResolver());
21 | DommelMapper.SetTableNameResolver(new DommelTableNameResolver());
22 | DommelMapper.SetPropertyResolver(new DommelPropertyResolver());
23 | return config;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/Mapping/DommelEntityMap.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Dapper.FluentMap.Mapping;
3 |
4 | namespace Dapper.FluentMap.Dommel.Mapping
5 | {
6 | ///
7 | /// Represents a non-typed mapping of an entity for Dommel.
8 | ///
9 | public interface IDommelEntityMap : IEntityMap
10 | {
11 | ///
12 | /// Gets the table name for the current entity.
13 | ///
14 | string TableName { get; }
15 | }
16 |
17 | ///
18 | /// Represents the typed mapping of an entity for Dommel.
19 | ///
20 | /// The type of an entity.
21 | public abstract class DommelEntityMap : EntityMapBase, IDommelEntityMap
22 | where TEntity : class
23 | {
24 | ///
25 | /// Gets the implementation for the current entity map.
26 | ///
27 | /// The information about the property.
28 | /// An implementation of .
29 | protected override DommelPropertyMap GetPropertyMap(PropertyInfo info)
30 | {
31 | return new DommelPropertyMap(info);
32 | }
33 |
34 | ///
35 | /// Gets the table name for this entity map.
36 | ///
37 | public string TableName { get; private set; }
38 |
39 | ///
40 | /// Sets the table name for the current entity.
41 | ///
42 | /// The name of the table in the database.
43 | protected void ToTable(string tableName)
44 | {
45 | TableName = tableName;
46 | }
47 |
48 | ///
49 | /// Sets the table name for the current entity.
50 | ///
51 | /// The name of the table in the database.
52 | /// The name of the table schema in the database.
53 | protected void ToTable(string tableName, string schemaName)
54 | {
55 | TableName = $"{schemaName}.{tableName}";
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/Mapping/DommelPropertyMap.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations.Schema;
2 | using System.Reflection;
3 | using Dapper.FluentMap.Mapping;
4 |
5 | namespace Dapper.FluentMap.Dommel.Mapping
6 | {
7 | ///
8 | /// Represents mapping of a property for Dommel.
9 | ///
10 | public class DommelPropertyMap : PropertyMapBase, IPropertyMap
11 | {
12 | ///
13 | /// Initializes a new instance of the class
14 | /// with the specified object.
15 | ///
16 | /// The information about the property.
17 | public DommelPropertyMap(PropertyInfo info) : base(info)
18 | {
19 | }
20 |
21 | ///
22 | /// Gets a value indicating whether this property is a primary key.
23 | ///
24 | public bool Key { get; private set; }
25 |
26 | ///
27 | /// Gets a value indicating whether this primary key is an identity.
28 | ///
29 | public bool Identity { get; set; }
30 |
31 | ///
32 | /// Gets a value indicating how the column is generated.
33 | ///
34 | public DatabaseGeneratedOption? GeneratedOption { get; set; }
35 |
36 | ///
37 | /// Specifies the current property as key for the entity.
38 | ///
39 | /// The current instance of .
40 | public DommelPropertyMap IsKey()
41 | {
42 | Key = true;
43 | return this;
44 | }
45 |
46 | ///
47 | /// Specifies the current property as an identity.
48 | ///
49 | /// The current instance of .
50 | public DommelPropertyMap IsIdentity()
51 | {
52 | Identity = true;
53 | return this;
54 | }
55 |
56 | ///
57 | /// Specifies how the property is generated.
58 | ///
59 | public DommelPropertyMap SetGeneratedOption(DatabaseGeneratedOption option)
60 | {
61 | GeneratedOption = option;
62 | return this;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/Resolvers/DommelColumnNameResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 | using Dapper.FluentMap.Dommel.Mapping;
4 | using Dapper.FluentMap.Mapping;
5 | using Dommel;
6 |
7 | namespace Dapper.FluentMap.Dommel.Resolvers
8 | {
9 | ///
10 | /// Implements the interface by using the configured mapping.
11 | ///
12 | public class DommelColumnNameResolver : IColumnNameResolver
13 | {
14 | private static readonly IColumnNameResolver DefaultResolver = new DefaultColumnNameResolver();
15 |
16 | ///
17 | public string ResolveColumnName(PropertyInfo propertyInfo)
18 | {
19 | if (propertyInfo.DeclaringType != null)
20 | {
21 | #if NETSTANDARD1_3
22 | if (FluentMapper.EntityMaps.TryGetValue(propertyInfo.DeclaringType, out var entityMap))
23 |
24 | #else
25 | if (FluentMapper.EntityMaps.TryGetValue(propertyInfo.ReflectedType, out var entityMap))
26 | #endif
27 | {
28 | var mapping = entityMap as IDommelEntityMap;
29 | if (mapping != null)
30 | {
31 | var propertyMaps = entityMap.PropertyMaps.Where(m => m.PropertyInfo.Name == propertyInfo.Name).ToList();
32 | if (propertyMaps.Count == 1)
33 | {
34 | return propertyMaps[0].ColumnName;
35 | }
36 | }
37 | }
38 | #if NETSTANDARD1_3
39 | else if (FluentMapper.TypeConventions.TryGetValue(propertyInfo.DeclaringType, out var conventions))
40 |
41 | #else
42 | else if (FluentMapper.TypeConventions.TryGetValue(propertyInfo.ReflectedType, out var conventions))
43 | #endif
44 | {
45 | foreach (var convention in conventions)
46 | {
47 | var propertyMaps = convention.PropertyMaps.Where(m => m.PropertyInfo.Name == propertyInfo.Name).ToList();
48 | if (propertyMaps.Count == 1)
49 | {
50 | return propertyMaps[0].ColumnName;
51 | }
52 | }
53 | }
54 | }
55 |
56 | return DefaultResolver.ResolveColumnName(propertyInfo);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/Resolvers/DommelKeyPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 | using System.Linq;
4 | using System.Reflection;
5 | using Dapper.FluentMap.Dommel.Mapping;
6 | using Dapper.FluentMap.Mapping;
7 | using Dommel;
8 |
9 | namespace Dapper.FluentMap.Dommel.Resolvers
10 | {
11 | ///
12 | /// Implements the interface by using the configured mapping.
13 | ///
14 | public class DommelKeyPropertyResolver : IKeyPropertyResolver
15 | {
16 | private static readonly IKeyPropertyResolver DefaultResolver = new DefaultKeyPropertyResolver();
17 |
18 | ///
19 | public ColumnPropertyInfo[] ResolveKeyProperties(Type type)
20 | {
21 | IEntityMap entityMap;
22 | if (!FluentMapper.EntityMaps.TryGetValue(type, out entityMap))
23 | {
24 | return DefaultResolver.ResolveKeyProperties(type);
25 | }
26 |
27 | var mapping = entityMap as IDommelEntityMap;
28 | if (mapping != null)
29 | {
30 | var allPropertyMaps = entityMap.PropertyMaps.OfType();
31 | var keyPropertyInfos = allPropertyMaps
32 | .Where(e => e.Key)
33 | .Select(x => new ColumnPropertyInfo(x.PropertyInfo, x.GeneratedOption ?? (x.Key ? DatabaseGeneratedOption.Identity : DatabaseGeneratedOption.None)))
34 | .ToArray();
35 |
36 | // Now make sure there aren't any missing key properties that weren't explicitly defined in the mapping.
37 | try
38 | {
39 | // Make sure to exclude any keys that were defined in the dommel entity map and not marked as keys.
40 | var defaultKeyPropertyInfos = DefaultResolver.ResolveKeyProperties(type).Where(x => allPropertyMaps.Count(y => y.PropertyInfo.Equals(x.Property)) == 0);
41 | keyPropertyInfos = keyPropertyInfos.Union(defaultKeyPropertyInfos).ToArray();
42 | }
43 | catch
44 | {
45 | // There could be no default Ids found. This is okay as long as we found a custom one.
46 | if (keyPropertyInfos.Length == 0)
47 | {
48 | throw new InvalidOperationException($"Could not find the key properties for type '{type.FullName}'.");
49 | }
50 | }
51 |
52 | return keyPropertyInfos;
53 | }
54 |
55 | // Fall back to the default mapping strategy.
56 | return DefaultResolver.ResolveKeyProperties(type);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/Resolvers/DommelPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations.Schema;
4 | using System.Linq;
5 | using System.Reflection;
6 | using Dapper.FluentMap.Dommel.Mapping;
7 | using Dapper.FluentMap.Mapping;
8 | using Dommel;
9 |
10 | namespace Dapper.FluentMap.Dommel.Resolvers
11 | {
12 | ///
13 | /// Implements the interface by using the configured mapping.
14 | ///
15 | public class DommelPropertyResolver : DefaultPropertyResolver
16 | {
17 | private static readonly IPropertyResolver DefaultResolver = new DefaultPropertyResolver();
18 |
19 | ///
20 | protected override IEnumerable FilterComplexTypes(IEnumerable properties)
21 | {
22 | foreach (var propertyInfo in properties)
23 | {
24 | var type = propertyInfo.PropertyType;
25 | type = Nullable.GetUnderlyingType(type) ?? type;
26 |
27 | if (type.GetTypeInfo().IsPrimitive || type.GetTypeInfo().IsEnum || PrimitiveTypes.Contains(type))
28 | {
29 | yield return propertyInfo;
30 | }
31 | }
32 | }
33 |
34 | ///
35 | public override IEnumerable ResolveProperties(Type type)
36 | {
37 | IEntityMap entityMap;
38 | if (FluentMapper.EntityMaps.TryGetValue(type, out entityMap))
39 | {
40 | foreach (var property in FilterComplexTypes(type.GetProperties()))
41 | {
42 | // Determine whether the property should be ignored.
43 | var propertyMap = entityMap.PropertyMaps.FirstOrDefault(p => p.PropertyInfo.Name == property.Name);
44 | if (propertyMap == null || !propertyMap.Ignored)
45 | {
46 | var dommelPropertyMap = propertyMap as DommelPropertyMap;
47 | if (dommelPropertyMap != null)
48 | {
49 | yield return new ColumnPropertyInfo(property, dommelPropertyMap.GeneratedOption ?? (dommelPropertyMap.Key ? DatabaseGeneratedOption.Identity : DatabaseGeneratedOption.None));
50 | }
51 | else
52 | {
53 | yield return new ColumnPropertyInfo(property);
54 | }
55 | }
56 | }
57 | }
58 | else
59 | {
60 | foreach (var property in DefaultResolver.ResolveProperties(type))
61 | {
62 | yield return property;
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap.Dommel/Resolvers/DommelTableNameResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Dapper.FluentMap.Dommel.Mapping;
3 | using Dapper.FluentMap.Mapping;
4 | using Dommel;
5 |
6 | namespace Dapper.FluentMap.Dommel.Resolvers
7 | {
8 | ///
9 | /// Implements the interface by using the configured mapping.
10 | ///
11 | public class DommelTableNameResolver : ITableNameResolver
12 | {
13 | private static readonly ITableNameResolver DefaultResolver = new DefaultTableNameResolver();
14 |
15 | ///
16 | public string ResolveTableName(Type type)
17 | {
18 | IEntityMap entityMap;
19 | if (FluentMapper.EntityMaps.TryGetValue(type, out entityMap))
20 | {
21 | var mapping = entityMap as IDommelEntityMap;
22 |
23 | if (mapping != null)
24 | {
25 | return mapping.TableName;
26 | }
27 | }
28 |
29 | return DefaultResolver.ResolveTableName(type);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Configuration/FluentConventionConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using System.Reflection;
5 | using Dapper.FluentMap.Conventions;
6 | using Dapper.FluentMap.Mapping;
7 | using Dapper.FluentMap.Utils;
8 |
9 | namespace Dapper.FluentMap.Configuration
10 | {
11 | ///
12 | /// Defines methods for configuring conventions.
13 | ///
14 | public class FluentConventionConfiguration
15 | {
16 | private readonly Convention _convention;
17 |
18 | ///
19 | /// Initializes a new instance of the class,
20 | /// allowing configuration of conventions.
21 | ///
22 | /// The convention.
23 | public FluentConventionConfiguration(Convention convention)
24 | {
25 | _convention = convention;
26 | }
27 |
28 | ///
29 | /// Configures the current covention for the specified entity type.
30 | ///
31 | /// The type of the entity.
32 | /// The current instance of .
33 | public FluentConventionConfiguration ForEntity()
34 | {
35 | var type = typeof(T);
36 | MapProperties(type);
37 |
38 | FluentMapper.TypeConventions.AddOrUpdate(type, _convention);
39 | FluentMapper.AddConventionTypeMap();
40 | return this;
41 | }
42 |
43 | #if !NETSTANDARD1_3
44 | ///
45 | /// Configures the current convention for all the entities in current assembly filtered by the specified namespaces.
46 | ///
47 | ///
48 | /// An array of namespaces which filter the types in the current assembly.
49 | /// This parameter is optional.
50 | ///
51 | /// The current instance of .
52 | public FluentConventionConfiguration ForEntitiesInCurrentAssembly(params string[] namespaces)
53 | {
54 | foreach (var type in Assembly.GetCallingAssembly().GetExportedTypes())
55 | {
56 | if (namespaces != null &&
57 | namespaces.Length > 0 &&
58 | namespaces.All(n => n != type.Namespace))
59 | {
60 | // Filter by namespace.
61 | continue;
62 | }
63 |
64 | MapProperties(type);
65 | FluentMapper.TypeConventions.AddOrUpdate(type, _convention);
66 | FluentMapper.AddConventionTypeMap(type);
67 | }
68 |
69 | return this;
70 | }
71 | #endif
72 |
73 | ///
74 | /// Configures the current convention for all entities in the specified assembly filtered by the specified namespaces.
75 | ///
76 | /// The assembly to scan for entities.
77 | ///
78 | /// An array of namespaces which filter the types in .
79 | /// This parameter is optional.
80 | ///
81 | /// The current instance of .
82 | public FluentConventionConfiguration ForEntitiesInAssembly(Assembly assembly, params string[] namespaces)
83 | {
84 | foreach (var type in assembly.GetExportedTypes())
85 | {
86 | if (namespaces != null &&
87 | namespaces.Length > 0 &&
88 | namespaces.All(n => n != type.Namespace))
89 | {
90 | // Filter by namespace.
91 | continue;
92 | }
93 |
94 | MapProperties(type);
95 | FluentMapper.TypeConventions.AddOrUpdate(type, _convention);
96 | FluentMapper.AddConventionTypeMap(type);
97 | }
98 |
99 | return this;
100 | }
101 |
102 | private void MapProperties(Type type)
103 | {
104 | var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
105 |
106 | foreach (var property in properties)
107 | {
108 | // Find the convention configurations for the convetion with either none or matching property predicates.
109 | foreach (var config in _convention.ConventionConfigurations
110 | .Where(c => c.PropertyPredicates.Count <= 0 ||
111 | c.PropertyPredicates.All(e => e(property))))
112 | {
113 | if (!string.IsNullOrEmpty(config.PropertyConfiguration.ColumnName))
114 | {
115 | AddConventionPropertyMap(
116 | property,
117 | config.PropertyConfiguration.ColumnName,
118 | config.PropertyConfiguration.CaseSensitive);
119 | break;
120 | }
121 |
122 | if (!string.IsNullOrEmpty(config.PropertyConfiguration.Prefix))
123 | {
124 | AddConventionPropertyMap(
125 | property,
126 | config.PropertyConfiguration.Prefix + property.Name,
127 | config.PropertyConfiguration.CaseSensitive);
128 | break;
129 | }
130 |
131 | if (config.PropertyConfiguration.PropertyTransformer != null)
132 | {
133 | AddConventionPropertyMap(
134 | property,
135 | config.PropertyConfiguration.PropertyTransformer(property.Name),
136 | config.PropertyConfiguration.CaseSensitive);
137 | }
138 | }
139 | }
140 | }
141 |
142 | private void AddConventionPropertyMap(PropertyInfo property, string columnName, bool caseSensitive)
143 | {
144 | var map = new PropertyMap(property, columnName, caseSensitive);
145 | _convention.PropertyMaps.Add(map);
146 | }
147 |
148 | #region EditorBrowsableStates
149 | ///
150 | [EditorBrowsable(EditorBrowsableState.Never)]
151 | public override string ToString()
152 | {
153 | return base.ToString();
154 | }
155 |
156 | ///
157 | [EditorBrowsable(EditorBrowsableState.Never)]
158 | public override bool Equals(object obj)
159 | {
160 | return base.Equals(obj);
161 | }
162 |
163 | ///
164 | [EditorBrowsable(EditorBrowsableState.Never)]
165 | public override int GetHashCode()
166 | {
167 | return base.GetHashCode();
168 | }
169 |
170 | ///
171 | [EditorBrowsable(EditorBrowsableState.Never)]
172 | public new Type GetType()
173 | {
174 | return base.GetType();
175 | }
176 | #endregion
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Configuration/FluentMapConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.ComponentModel;
4 | using Dapper.FluentMap.Conventions;
5 | using Dapper.FluentMap.Mapping;
6 |
7 | namespace Dapper.FluentMap.Configuration
8 | {
9 | ///
10 | /// Defines methods for configuring Dapper.FluentMap.
11 | ///
12 | public class FluentMapConfiguration
13 | {
14 | ///
15 | /// Adds the specified to the configuration of Dapper.FluentMap.
16 | ///
17 | /// The type argument of the entity.
18 | ///
19 | /// An instance of the interface containing the
20 | /// entity mapping configuration.
21 | ///
22 | public void AddMap(IEntityMap mapper) where TEntity : class
23 | {
24 | if (FluentMapper.EntityMaps.TryAdd(typeof(TEntity), mapper))
25 | {
26 | FluentMapper.AddTypeMap();
27 | }
28 | else
29 | {
30 | throw new InvalidOperationException($"Adding entity map for type '{typeof(TEntity)}' failed. The type already exists. Current entity maps: " + string.Join(", ", FluentMapper.EntityMaps.Select(e => e.Key.ToString())));
31 | }
32 | }
33 |
34 | ///
35 | /// Adds the specified to the configuration of Dapper.FluentMap.
36 | ///
37 | /// The type of the convention.
38 | ///
39 | /// An instance of
40 | /// which allows configuration of the convention.
41 | ///
42 | public FluentConventionConfiguration AddConvention() where TConvention : Convention, new()
43 | {
44 | return new FluentConventionConfiguration(new TConvention());
45 | }
46 |
47 | #region EditorBrowsableStates
48 | ///
49 | [EditorBrowsable(EditorBrowsableState.Never)]
50 | public override string ToString()
51 | {
52 | return base.ToString();
53 | }
54 |
55 | ///
56 | [EditorBrowsable(EditorBrowsableState.Never)]
57 | public override bool Equals(object obj)
58 | {
59 | return base.Equals(obj);
60 | }
61 |
62 | ///
63 | [EditorBrowsable(EditorBrowsableState.Never)]
64 | public override int GetHashCode()
65 | {
66 | return base.GetHashCode();
67 | }
68 |
69 | ///
70 | [EditorBrowsable(EditorBrowsableState.Never)]
71 | public new Type GetType()
72 | {
73 | return base.GetType();
74 | }
75 | #endregion
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Conventions/Convention.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using Dapper.FluentMap.Mapping;
5 |
6 | namespace Dapper.FluentMap.Conventions
7 | {
8 | ///
9 | /// Represents a convention for mapping entity properties to column names.
10 | ///
11 | public abstract class Convention
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | protected Convention()
17 | {
18 | ConventionConfigurations = new List();
19 | PropertyMaps = new List();
20 | }
21 |
22 | ///
23 | /// Gets the convention configurations for the properties.
24 | ///
25 | public IList ConventionConfigurations { get; }
26 |
27 | ///
28 | /// Gets the property mappings.
29 | ///
30 | public IList PropertyMaps { get; }
31 |
32 | ///
33 | /// Configures a convention that applies on all properties of the entity.
34 | ///
35 | /// A configuration object for the convention.
36 | protected PropertyConventionConfiguration Properties()
37 | {
38 | var config = new PropertyConventionConfiguration();
39 | ConventionConfigurations.Add(config);
40 |
41 | return config;
42 | }
43 |
44 | ///
45 | /// Configures a convention that applies on all the properties of a specified type of the entity.
46 | ///
47 | /// The type of the properties that the convention will apply to.
48 | /// A configuration object for the convention.
49 | protected PropertyConventionConfiguration Properties()
50 | {
51 | var type = typeof(T);
52 | PropertyConventionConfiguration config;
53 | if (Nullable.GetUnderlyingType(type) != null)
54 | {
55 | // Convention defined for nullable type, match nullable properties
56 | config = new PropertyConventionConfiguration().Where(p => p.PropertyType == type);
57 | }
58 | else
59 | {
60 | // Convention defined for non-nullable types, match both nullabel and non-nullable properties
61 | config = new PropertyConventionConfiguration().Where(p => (Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType) == type);
62 | }
63 | ConventionConfigurations.Add(config);
64 |
65 | return config;
66 | }
67 |
68 | #region EditorBrowsableStates
69 | ///
70 | [EditorBrowsable(EditorBrowsableState.Never)]
71 | public override string ToString()
72 | {
73 | return base.ToString();
74 | }
75 |
76 | ///
77 | [EditorBrowsable(EditorBrowsableState.Never)]
78 | public override bool Equals(object obj)
79 | {
80 | return base.Equals(obj);
81 | }
82 |
83 | ///
84 | [EditorBrowsable(EditorBrowsableState.Never)]
85 | public override int GetHashCode()
86 | {
87 | return base.GetHashCode();
88 | }
89 |
90 | ///
91 | [EditorBrowsable(EditorBrowsableState.Never)]
92 | public new Type GetType()
93 | {
94 | return base.GetType();
95 | }
96 | #endregion
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Conventions/ConventionPropertyConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 |
4 | namespace Dapper.FluentMap.Conventions
5 | {
6 | ///
7 | /// Represents configuration of a property via conventions.
8 | ///
9 | public class ConventionPropertyConfiguration
10 | {
11 | ///
12 | /// Initializes a new instane of the .
13 | ///
14 | public ConventionPropertyConfiguration()
15 | {
16 | CaseSensitive = true;
17 | }
18 |
19 | ///
20 | /// Configures the name of the database column used to store the property.
21 | ///
22 | /// The name of the database column.
23 | /// The same instance of .
24 | public ConventionPropertyConfiguration HasColumnName(string columnName)
25 | {
26 | ColumnName = columnName;
27 | return this;
28 | }
29 |
30 | ///
31 | /// Configures the prefix of the database column used to store the property.
32 | ///
33 | /// The prefix of the database column.
34 | /// The same instance of .
35 | public ConventionPropertyConfiguration HasPrefix(string prefix)
36 | {
37 | Prefix = prefix;
38 | return this;
39 | }
40 |
41 | ///
42 | /// Configures the current convention to be case insensitive.
43 | ///
44 | /// The same instance of .
45 | public ConventionPropertyConfiguration IsCaseInsensitive()
46 | {
47 | CaseSensitive = false;
48 | return this;
49 | }
50 |
51 | ///
52 | /// Configures the function for transforming property names to database column names.
53 | ///
54 | /// A function which takes the property name and returns the database colum name.
55 | /// The same instance of .
56 | public ConventionPropertyConfiguration Transform(Func transformer)
57 | {
58 | PropertyTransformer = transformer;
59 | return this;
60 | }
61 |
62 | internal string ColumnName { get; private set; }
63 |
64 | internal string Prefix { get; private set; }
65 |
66 | internal bool CaseSensitive { get; private set; }
67 |
68 | internal Func PropertyTransformer { get; private set; }
69 |
70 | #region EditorBrowsableStates
71 | ///
72 | [EditorBrowsable(EditorBrowsableState.Never)]
73 | public override string ToString()
74 | {
75 | return base.ToString();
76 | }
77 |
78 | ///
79 | [EditorBrowsable(EditorBrowsableState.Never)]
80 | public override bool Equals(object obj)
81 | {
82 | return base.Equals(obj);
83 | }
84 |
85 | ///
86 | [EditorBrowsable(EditorBrowsableState.Never)]
87 | public override int GetHashCode()
88 | {
89 | return base.GetHashCode();
90 | }
91 |
92 | ///
93 | [EditorBrowsable(EditorBrowsableState.Never)]
94 | public new Type GetType()
95 | {
96 | return base.GetType();
97 | }
98 | #endregion
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Conventions/PropertyConventionConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Reflection;
5 |
6 | namespace Dapper.FluentMap.Conventions
7 | {
8 | ///
9 | /// Represents the configuration for a convention.
10 | ///
11 | public class PropertyConventionConfiguration
12 | {
13 | ///
14 | /// Initializes a new instance of the class,
15 | /// allowing configuration for a convention.
16 | ///
17 | public PropertyConventionConfiguration()
18 | {
19 | PropertyPredicates = new List>();
20 | }
21 |
22 | internal IList> PropertyPredicates { get; }
23 |
24 | internal ConventionPropertyConfiguration PropertyConfiguration { get; private set; }
25 |
26 | ///
27 | /// Filters the properties that this convention applies to based on a predicate.
28 | ///
29 | /// A function to test each property for a condition.
30 | /// The same instance of .
31 | public PropertyConventionConfiguration Where(Func predicate)
32 | {
33 | PropertyPredicates.Add(predicate);
34 | return this;
35 | }
36 |
37 | ///
38 | /// Configures the properties that this convention applies to.
39 | ///
40 | ///
41 | /// An action that performs configuration against
42 | /// .
43 | ///
44 | public void Configure(Action configure)
45 | {
46 | var config = new ConventionPropertyConfiguration();
47 | PropertyConfiguration = config;
48 | configure(config);
49 | }
50 |
51 | #region EditorBrowsableStates
52 | ///
53 | [EditorBrowsable(EditorBrowsableState.Never)]
54 | public override string ToString()
55 | {
56 | return base.ToString();
57 | }
58 |
59 | ///
60 | [EditorBrowsable(EditorBrowsableState.Never)]
61 | public override bool Equals(object obj)
62 | {
63 | return base.Equals(obj);
64 | }
65 |
66 | ///
67 | [EditorBrowsable(EditorBrowsableState.Never)]
68 | public override int GetHashCode()
69 | {
70 | return base.GetHashCode();
71 | }
72 |
73 | ///
74 | [EditorBrowsable(EditorBrowsableState.Never)]
75 | public new Type GetType()
76 | {
77 | return base.GetType();
78 | }
79 | #endregion
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Dapper.FluentMap.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Simple API to fluently map POCO properties to database columns when using Dapper.
4 | Copyright © Henk Mollema 2014
5 | 2.0.0
6 | Henk Mollema
7 | netstandard2.0
8 | true
9 | c#;dapper;mapping;fluentmap
10 | https://github.com/henkmollema/Dapper-FluentMap
11 | https://github.com/henkmollema/Dapper-FluentMap/blob/master/LICENSE
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/FluentMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using Dapper.FluentMap.Configuration;
5 | using Dapper.FluentMap.Conventions;
6 | using Dapper.FluentMap.Mapping;
7 | using Dapper.FluentMap.TypeMaps;
8 |
9 | namespace Dapper.FluentMap
10 | {
11 | ///
12 | /// Main entry point for Dapper.FluentMap configuration.
13 | ///
14 | public static class FluentMapper
15 | {
16 | private static readonly FluentMapConfiguration _configuration = new FluentMapConfiguration();
17 |
18 | ///
19 | /// Gets the dictionary containing the entity mapping per entity type.
20 | ///
21 | public static readonly ConcurrentDictionary EntityMaps = new ConcurrentDictionary();
22 |
23 | ///
24 | /// Gets the dictionary containing the conventions per entity type.
25 | ///
26 | public static readonly ConcurrentDictionary> TypeConventions = new ConcurrentDictionary>();
27 |
28 | ///
29 | /// Initializes Dapper.FluentMap with the specified configuration.
30 | /// This is method should be called when the application starts or when the first mapping is needed.
31 | ///
32 | /// A callback containing the configuration of Dapper.FluentMap.
33 | public static void Initialize(Action configure)
34 | {
35 | configure(_configuration);
36 | }
37 |
38 | ///
39 | /// Registers a Dapper type map using fluent mapping for the specified .
40 | ///
41 | /// The type of the entity.
42 | internal static void AddTypeMap()
43 | {
44 | SqlMapper.SetTypeMap(typeof(TEntity), new FluentMapTypeMap());
45 | }
46 |
47 | ///
48 | /// Registers a Dapper type map using fluent mapping for the specified .
49 | ///
50 | /// The type of the entity.
51 | internal static void AddTypeMap(Type entityType)
52 | {
53 | var instance = (SqlMapper.ITypeMap)Activator.CreateInstance(typeof(FluentMapTypeMap<>).MakeGenericType(entityType));
54 | SqlMapper.SetTypeMap(entityType, instance);
55 | }
56 |
57 | ///
58 | /// Registers a Dapper type map using conventions for the specified .
59 | ///
60 | /// The type of the entity.
61 | internal static void AddConventionTypeMap()
62 | {
63 | SqlMapper.SetTypeMap(typeof(TEntity), new FluentConventionTypeMap());
64 | }
65 |
66 | ///
67 | /// Registers a Dapper type map using conventions for the specified .
68 | ///
69 | /// The type of the entity.
70 | internal static void AddConventionTypeMap(Type entityType)
71 | {
72 | var instance = (SqlMapper.ITypeMap)Activator.CreateInstance(typeof(FluentConventionTypeMap<>).MakeGenericType(entityType));
73 | SqlMapper.SetTypeMap(entityType, instance);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Mapping/EntityMap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 | using System.Reflection;
6 | using Dapper.FluentMap.Utils;
7 |
8 | namespace Dapper.FluentMap.Mapping
9 | {
10 | ///
11 | /// Represents a non-typed mapping of an entity.
12 | ///
13 | public interface IEntityMap
14 | {
15 | ///
16 | /// Gets the collection of mapped properties.
17 | ///
18 | IList PropertyMaps { get; }
19 | }
20 |
21 | ///
22 | /// Represents a typed mapping of an entity.
23 | /// This serves as a marker interface for generic type inference.
24 | ///
25 | /// The type of the entity to configure the mapping for.
26 | public interface IEntityMap : IEntityMap
27 | {
28 | }
29 |
30 | ///
31 | /// Serves as the base class for all entity mapping implementations.
32 | ///
33 | /// The type of the entity.
34 | /// The type of the property mapping.
35 | public abstract class EntityMapBase : IEntityMap
36 | where TPropertyMap : IPropertyMap
37 | {
38 | ///
39 | /// Initializes a new instance of the class.
40 | ///
41 | protected EntityMapBase()
42 | {
43 | PropertyMaps = new List();
44 | }
45 |
46 | ///
47 | /// Gets the collection of mapped properties.
48 | ///
49 | public IList PropertyMaps { get; }
50 |
51 | ///
52 | /// Returns an instance of which can perform custom mapping
53 | /// for the specified property on .
54 | ///
55 | /// Expression to the property on .
56 | /// The created instance. This enables a fluent API.
57 | /// when a duplicate mapping is provided.
58 | protected TPropertyMap Map(Expression> expression)
59 | {
60 | var info = (PropertyInfo)ReflectionHelper.GetMemberInfo(expression);
61 | var propertyMap = GetPropertyMap(info);
62 | ThrowIfDuplicateMapping(propertyMap);
63 | PropertyMaps.Add(propertyMap);
64 | return propertyMap;
65 | }
66 |
67 | ///
68 | /// When overridden in a derived class, gets the property mapping for the specified property.
69 | ///
70 | /// The for the property.
71 | /// An instance of .
72 | protected abstract TPropertyMap GetPropertyMap(PropertyInfo info);
73 |
74 | private void ThrowIfDuplicateMapping(IPropertyMap map)
75 | {
76 | if (PropertyMaps.Any(p => p.PropertyInfo.Name == map.PropertyInfo.Name))
77 | {
78 | throw new Exception($"Duplicate mapping detected. Property '{map.PropertyInfo.Name}' is already mapped to column '{map.ColumnName}'.");
79 | }
80 | }
81 | }
82 |
83 | ///
84 | /// Represents a typed mapping of an entity.
85 | ///
86 | /// The type of the entity to configure the mapping for.
87 | public abstract class EntityMap : EntityMapBase
88 | where TEntity : class
89 | {
90 | ///
91 | protected override PropertyMap GetPropertyMap(PropertyInfo info)
92 | {
93 | return new PropertyMap(info);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Mapping/PropertyMap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Reflection;
4 |
5 | namespace Dapper.FluentMap.Mapping
6 | {
7 | ///
8 | /// Represents the mapping of a property.
9 | ///
10 | public interface IPropertyMap
11 | {
12 | ///
13 | /// Gets the name of the column in the data store.
14 | ///
15 | string ColumnName { get; }
16 |
17 | ///
18 | /// Gets the object for the current property.
19 | ///
20 | PropertyInfo PropertyInfo { get; }
21 |
22 | ///
23 | /// Gets or sets a value indicating whether column name mapping should be case sensitive.
24 | ///
25 | bool CaseSensitive { get; }
26 |
27 | ///
28 | /// Gets a value indicating wether the property should be ignored when mapping.
29 | ///
30 | bool Ignored { get; }
31 | }
32 |
33 | ///
34 | /// Serves as the base class for all property mapping implementations.
35 | ///
36 | /// The type of the property mapping.
37 | public abstract class PropertyMapBase
38 | where TPropertyMap : class, IPropertyMap
39 | {
40 | ///
41 | /// Initializes a new instance of the using
42 | /// the specified object representing the property to map.
43 | ///
44 | /// The object representing to the property to map.
45 | protected PropertyMapBase(PropertyInfo info)
46 | {
47 | PropertyInfo = info;
48 | ColumnName = info.Name;
49 | }
50 |
51 | ///
52 | /// Initializes a new instance of the using
53 | /// the specified object representing the property to map
54 | /// and column name to map the property to.
55 | ///
56 | /// The object representing to the property to map.
57 | /// The column name in the database to map the property to.
58 | internal PropertyMapBase(PropertyInfo info, string columnName)
59 | {
60 | PropertyInfo = info;
61 | ColumnName = columnName;
62 | }
63 |
64 | ///
65 | /// Initializes a new instance of the using
66 | /// the specified object representing the property to map,
67 | /// column name to map the property to and a value indicating whether the mapping should be case sensitive.
68 | ///
69 | /// The object representing to the property to map.
70 | /// The column name in the database to map the property to.
71 | /// A value indicating whether the mappig should be case sensitive.
72 | internal PropertyMapBase(PropertyInfo info, string columnName, bool caseSensitive)
73 | {
74 | PropertyInfo = info;
75 | ColumnName = columnName;
76 | CaseSensitive = caseSensitive;
77 | }
78 |
79 | ///
80 | /// Gets the column name for the mapping.
81 | ///
82 | public string ColumnName { get; private set; }
83 |
84 | ///
85 | /// Gets a value indicating whether this mapping is case sensitive.
86 | ///
87 | public bool CaseSensitive { get; private set; }
88 |
89 | ///
90 | /// Gets a value indicating the property should be ignored when mapping.
91 | ///
92 | public bool Ignored { get; private set; }
93 |
94 | ///
95 | /// Gets a reference to the of this mapping.
96 | ///
97 | public PropertyInfo PropertyInfo { get; }
98 |
99 | ///
100 | /// Maps the current property to the specified column name.
101 | ///
102 | /// The name of the column in the data store.
103 | /// A value indicating whether column name mapping should be case sensitive.
104 | /// The current instance of .
105 | public TPropertyMap ToColumn(string columnName, bool caseSensitive = true)
106 | {
107 | ColumnName = columnName;
108 | CaseSensitive = caseSensitive;
109 | return this as TPropertyMap;
110 | }
111 |
112 | ///
113 | /// Marks the current property as ignored, resulting in the property not being mapped by Dapper.
114 | ///
115 | /// The current instance. This enables a fluent API.
116 | public TPropertyMap Ignore()
117 | {
118 | Ignored = true;
119 | return this as TPropertyMap;
120 | }
121 |
122 | #region EditorBrowsableStates
123 | ///
124 | [EditorBrowsable(EditorBrowsableState.Never)]
125 | public override string ToString()
126 | {
127 | return base.ToString();
128 | }
129 |
130 | ///
131 | [EditorBrowsable(EditorBrowsableState.Never)]
132 | public override bool Equals(object obj)
133 | {
134 | return base.Equals(obj);
135 | }
136 |
137 | ///
138 | [EditorBrowsable(EditorBrowsableState.Never)]
139 | public override int GetHashCode()
140 | {
141 | return base.GetHashCode();
142 | }
143 |
144 | ///
145 | [EditorBrowsable(EditorBrowsableState.Never)]
146 | public new Type GetType()
147 | {
148 | return base.GetType();
149 | }
150 | #endregion
151 | }
152 |
153 | ///
154 | /// Represents the mapping of a property.
155 | ///
156 | public class PropertyMap : PropertyMapBase, IPropertyMap
157 | {
158 | ///
159 | /// Initializes a new instance of the class
160 | /// with the specified object.
161 | ///
162 | /// The information about the property.
163 | public PropertyMap(PropertyInfo info)
164 | : base(info)
165 | {
166 | }
167 |
168 | ///
169 | /// Initializes a new instance of the class
170 | /// with the specified object and column name.
171 | ///
172 | /// The information about the property.
173 | /// The column name.
174 | public PropertyMap(PropertyInfo info, string columnName)
175 | : base(info, columnName)
176 | {
177 | }
178 |
179 | ///
180 | /// Initializes a new instance of the class
181 | /// with the specified object, column name
182 | /// and a value indicating whether the mapping should be case sensitive.
183 | ///
184 | /// The information about the property.
185 | /// The column name.
186 | /// A value indicating whether the mappig should be case sensitive.
187 | public PropertyMap(PropertyInfo info, string columnName, bool caseSensitive)
188 | : base(info, columnName, caseSensitive)
189 | {
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/TypeMaps/FluentConventionTypeMap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 | using Dapper.FluentMap.Mapping;
5 |
6 | namespace Dapper.FluentMap.TypeMaps
7 | {
8 | ///
9 | /// Represents a Dapper type mapping strategy which first tries to map the type using a
10 | ///
11 | /// with the configured conventions. is used as fallback mapping strategy.
12 | ///
13 | /// The type of the entity.
14 | public class FluentConventionTypeMap : MultiTypeMap
15 | {
16 | ///
17 | /// Initializes a new instance of the class
18 | /// which uses the and
19 | /// as mapping strategies.
20 | ///
21 | public FluentConventionTypeMap()
22 | : base(new CustomPropertyTypeMap(typeof(TEntity), GetPropertyInfo), new DefaultTypeMap(typeof(TEntity)))
23 | {
24 | }
25 |
26 | private static PropertyInfo GetPropertyInfo(Type type, string columnName)
27 | {
28 | var cacheKey = $"{type.FullName};{columnName}";
29 | if (TypePropertyMapCache.TryGetValue(cacheKey, out var info))
30 | {
31 | return info;
32 | }
33 |
34 | if (FluentMapper.TypeConventions.TryGetValue(type, out var conventions))
35 | {
36 | foreach (var convention in conventions)
37 | {
38 | // Find property map for current type and column name.
39 | var maps = convention.PropertyMaps
40 | #if NETSTANDARD1_3
41 | // HACK: ReflectedType isn't available on.NET Standard 1.3,
42 | // this will cause issues when mapping derived entities.
43 | .Where(map => map.PropertyInfo.DeclaringType == type &&
44 | MatchColumnNames(map, columnName))
45 | #else
46 | .Where(map => map.PropertyInfo.ReflectedType == type &&
47 | MatchColumnNames(map, columnName))
48 | #endif
49 | .ToList();
50 |
51 | if (maps.Count > 1)
52 | {
53 | const string msg = "Finding mappings for column '{0}' yielded more than 1 PropertyMap. The conventions should be more specific. Type: '{1}'. Convention: '{2}'.";
54 | throw new Exception(string.Format(msg, columnName, type, convention));
55 | }
56 |
57 | if (maps.Count == 0)
58 | {
59 | // This convention has no property maps, continue to next convention.
60 | continue;
61 | }
62 |
63 | info = maps[0].PropertyInfo;
64 | TypePropertyMapCache.TryAdd(cacheKey, info);
65 | return info;
66 | }
67 | }
68 |
69 | // If we get here, the property was not mapped.
70 | TypePropertyMapCache.TryAdd(cacheKey, null);
71 | return null;
72 | }
73 |
74 | private static bool MatchColumnNames(IPropertyMap map, string columnName)
75 | {
76 | var comparison = StringComparison.Ordinal;
77 | if (!map.CaseSensitive)
78 | {
79 | comparison = StringComparison.OrdinalIgnoreCase;
80 | }
81 |
82 | return string.Equals(map.ColumnName, columnName, comparison);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/TypeMaps/FluentTypeMap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 | using Dapper.FluentMap.Mapping;
5 |
6 | namespace Dapper.FluentMap.TypeMaps
7 | {
8 | ///
9 | /// Represents a Dapper type mapping strategy which first tries to map the type using a
10 | /// ,
11 | /// if that fails, the is used as mapping strategy.
12 | ///
13 | /// The type of the entity.
14 | public class FluentMapTypeMap : MultiTypeMap
15 | {
16 | ///
17 | /// Initializes a new instance of the class
18 | /// which uses the and
19 | /// as mapping strategies.
20 | ///
21 | public FluentMapTypeMap()
22 | : base(new CustomPropertyTypeMap(typeof(TEntity), GetPropertyInfo), new DefaultTypeMap(typeof(TEntity)))
23 | {
24 | }
25 |
26 | private static PropertyInfo GetPropertyInfo(Type type, string columnName)
27 | {
28 | var cacheKey = $"{type.FullName};{columnName}";
29 |
30 | PropertyInfo info;
31 | if (TypePropertyMapCache.TryGetValue(cacheKey, out info))
32 | {
33 | return info;
34 | }
35 |
36 | IEntityMap entityMap;
37 | if (FluentMapper.EntityMaps.TryGetValue(type, out entityMap))
38 | {
39 | var propertyMaps = entityMap.PropertyMaps;
40 |
41 | // Find the mapping for the column name.
42 | var propertyMap = propertyMaps.FirstOrDefault(m => MatchColumnNames(m, columnName));
43 |
44 | if (propertyMap != null)
45 | {
46 | if (!propertyMap.Ignored)
47 | {
48 | TypePropertyMapCache.TryAdd(cacheKey, propertyMap.PropertyInfo);
49 | return propertyMap.PropertyInfo;
50 | }
51 | #if !NETSTANDARD1_3
52 | else
53 | {
54 | var ignoredPropertyInfo = new IgnoredPropertyInfo();
55 | TypePropertyMapCache.TryAdd(cacheKey, ignoredPropertyInfo);
56 | return ignoredPropertyInfo;
57 | }
58 | #endif
59 | }
60 | }
61 |
62 | // If we get here, the property was not mapped.
63 | TypePropertyMapCache.TryAdd(cacheKey, null);
64 | return null;
65 | }
66 |
67 | private static bool MatchColumnNames(IPropertyMap map, string columnName)
68 | {
69 | var comparison = StringComparison.Ordinal;
70 | if (!map.CaseSensitive)
71 | {
72 | comparison = StringComparison.OrdinalIgnoreCase;
73 | }
74 |
75 | return string.Equals(map.ColumnName, columnName, comparison);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/TypeMaps/IgnoredPropertyInfo.cs:
--------------------------------------------------------------------------------
1 | #if !NETSTANDARD1_3
2 | using System;
3 | using System.Globalization;
4 | using System.Reflection;
5 |
6 | namespace Dapper.FluentMap.TypeMaps
7 | {
8 | internal class IgnoredPropertyInfo : PropertyInfo
9 | {
10 | public override Type PropertyType => throw new NotImplementedException();
11 | public override PropertyAttributes Attributes => throw new NotImplementedException();
12 | public override bool CanRead => throw new NotImplementedException();
13 | public override bool CanWrite => throw new NotImplementedException();
14 | public override string Name => throw new NotImplementedException();
15 | public override Type DeclaringType => throw new NotImplementedException();
16 | public override ParameterInfo[] GetIndexParameters() => throw new NotImplementedException();
17 | public override Type ReflectedType => throw new NotImplementedException();
18 | public override MethodInfo[] GetAccessors(bool nonPublic) => throw new NotImplementedException();
19 | public override object[] GetCustomAttributes(bool inherit) => throw new NotImplementedException();
20 | public override object[] GetCustomAttributes(Type attributeType, bool inherit) => throw new NotImplementedException();
21 | public override MethodInfo GetGetMethod(bool nonPublic) => throw new NotImplementedException();
22 | public override MethodInfo GetSetMethod(bool nonPublic) => throw new NotImplementedException();
23 | public override bool IsDefined(Type attributeType, bool inherit) => throw new NotImplementedException();
24 | public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) => throw new NotImplementedException();
25 | public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) => throw new NotImplementedException();
26 | }
27 | }
28 | #endif
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/TypeMaps/MultiTypeMap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Reflection;
5 |
6 | namespace Dapper.FluentMap.TypeMaps
7 | {
8 | ///
9 | /// Represents a Dapper type mapping strategy which consists of multiple strategies.
10 | ///
11 | public abstract class MultiTypeMap : SqlMapper.ITypeMap
12 | {
13 | private readonly IEnumerable _mappers;
14 |
15 | ///
16 | /// Initializes an instance of the
17 | /// class with the specified Dapper type mappers.
18 | ///
19 | /// The type mapping strategies to be used when mapping.
20 | protected MultiTypeMap(params SqlMapper.ITypeMap[] mappers)
21 | {
22 | _mappers = mappers;
23 | }
24 |
25 | ///
26 | public ConstructorInfo FindConstructor(string[] names, Type[] types)
27 | {
28 | foreach (var mapper in _mappers)
29 | {
30 | try
31 | {
32 | var result = mapper.FindConstructor(names, types);
33 |
34 | if (result != null)
35 | {
36 | return result;
37 | }
38 | }
39 | catch (NotImplementedException)
40 | {
41 | // Ignore NotImplementedException's thrown by the CustomPropertyTypeMap
42 | // and continue to the next mapping strategy.
43 | }
44 | }
45 |
46 | return null;
47 | }
48 |
49 | ///
50 | public ConstructorInfo FindExplicitConstructor()
51 | {
52 | foreach (var mapper in _mappers)
53 | {
54 | try
55 | {
56 | var result = mapper.FindExplicitConstructor();
57 | if (result != null)
58 | {
59 | return result;
60 | }
61 | }
62 | catch (NotImplementedException)
63 | {
64 | // Ignore NotImplementedException's thrown by the CustomPropertyTypeMap
65 | // and continue to the next mapping strategy.
66 | }
67 | }
68 |
69 | return null;
70 | }
71 |
72 | ///
73 | public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
74 | {
75 | foreach (var mapper in _mappers)
76 | {
77 | try
78 | {
79 | var result = mapper.GetConstructorParameter(constructor, columnName);
80 |
81 | if (result != null)
82 | {
83 | return result;
84 | }
85 | }
86 | catch (NotImplementedException)
87 | {
88 | // Ignore NotImplementedException's thrown by the CustomPropertyTypeMap
89 | // and continue to the next mapping strategy.
90 | }
91 | }
92 |
93 | return null;
94 | }
95 |
96 | ///
97 | public SqlMapper.IMemberMap GetMember(string columnName)
98 | {
99 | foreach (var mapper in _mappers)
100 | {
101 | try
102 | {
103 | var result = mapper.GetMember(columnName);
104 | if (result != null)
105 | {
106 | #if !NETSTANDARD1_3
107 | if (result is IgnoredPropertyInfo || result.Property is IgnoredPropertyInfo)
108 | {
109 | // The property is explicitly ignored,
110 | // return null to prevent falling back to default type map of Dapper.
111 | return null;
112 | }
113 | #endif
114 | return result;
115 | }
116 | }
117 | catch (NotImplementedException)
118 | {
119 | // Ignore NotImplementedException's thrown by the CustomPropertyTypeMap
120 | // and continue to the next mapping strategy.
121 | }
122 | }
123 |
124 | return null;
125 | }
126 |
127 | ///
128 | /// Gets a cache for columns and properties.
129 | ///
130 | protected static ConcurrentDictionary TypePropertyMapCache { get; } = new ConcurrentDictionary();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Utils/DictionaryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Dapper.FluentMap.Utils
4 | {
5 | internal static class DictionaryExtensions
6 | {
7 | internal static void AddOrUpdate(this IDictionary> dict, TKey key, TValue value)
8 | {
9 | if (dict.ContainsKey(key))
10 | {
11 | dict[key].Add(value);
12 | }
13 | else
14 | {
15 | dict.Add(key, new List { value });
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Utils/FluentMapConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using Dapper.FluentMap.Configuration;
6 | using Dapper.FluentMap.Mapping;
7 |
8 | namespace Dapper.FluentMap.Utils
9 | {
10 | ///
11 | /// Extension methods for .
12 | ///
13 | public static class FluentMapConfigurationExtensions
14 | {
15 | ///
16 | /// Finds all types, from provided assemblies, implementing
17 | /// and applies them to ,
18 | /// by calling and passing an instance of found type.
19 | ///
20 | /// The instance.
21 | /// The assemblies to scan for entity maps.
22 | public static void ApplyMapsFromAssemblies(this FluentMapConfiguration configuration, params Assembly[] assemblies)
23 | {
24 | if (assemblies == null)
25 | {
26 | throw new ArgumentNullException(nameof(assemblies));
27 | }
28 |
29 | var entityMapTypes = FindTypesImplementingIEntityMap(assemblies);
30 |
31 | if (!entityMapTypes.Any())
32 | {
33 | return;
34 | }
35 |
36 | EnsureNoDuplicateMapping(entityMapTypes);
37 |
38 | AddMaps(configuration, entityMapTypes);
39 | }
40 |
41 |
42 | private static List<(Type Type, Type EntityMapInterface)> FindTypesImplementingIEntityMap(Assembly[] assemblies)
43 | {
44 | return assemblies
45 | .SelectMany(a => a.GetTypes()
46 | .Where(t => !t.IsAbstract && !t.IsInterface)
47 | .Select(t =>
48 | (
49 | t,
50 | t.GetInterfaces()
51 | .Where(i => i.IsGenericType)
52 | .FirstOrDefault(i => i.GetGenericTypeDefinition() == typeof(IEntityMap<>))
53 | )))
54 | .Where(t => t.EntityMapInterface != null)
55 | .ToList();
56 | }
57 |
58 | private static void EnsureNoDuplicateMapping(List<(Type Type, Type EntityMapInterface)> entityMapTypes)
59 | {
60 | var typesWithMultipleMappings = entityMapTypes.GroupBy(t => t.EntityMapInterface)
61 | .Where(g => g.Count() > 1)
62 | .Select(g => g.Key.GetGenericArguments().First())
63 | .ToList();
64 |
65 | if (typesWithMultipleMappings.Any())
66 | throw new InvalidOperationException(
67 | $"Multiple mappings defined for types: '{PrintTypeNames(typesWithMultipleMappings)}'");
68 | }
69 |
70 | private static string PrintTypeNames(List multipleMappings)
71 | => multipleMappings.Aggregate(string.Empty, (previous, next) => $"{previous}, {next.Name}");
72 |
73 | private static void AddMaps(FluentMapConfiguration configuration,
74 | List<(Type Type, Type EntityMapInterface)> entityMapTypes)
75 | {
76 | var addMapMethod = configuration.GetType().GetMethod(nameof(configuration.AddMap))
77 | ?? throw new InvalidOperationException(
78 | $"Cannot find {nameof(configuration.AddMap)} method on {configuration.GetType().Name}");
79 |
80 | foreach (var entityMapType in entityMapTypes)
81 | {
82 | addMapMethod
83 | .MakeGenericMethod(entityMapType.EntityMapInterface.GetGenericArguments())
84 | .Invoke(configuration, new[] {Activator.CreateInstance(entityMapType.Type)});
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Dapper.FluentMap/Utils/ReflectionHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Linq.Expressions;
4 | using System.Reflection;
5 |
6 | namespace Dapper.FluentMap.Utils
7 | {
8 | ///
9 | /// Provides helper methods for reflection operations.
10 | ///
11 | public static class ReflectionHelper
12 | {
13 | ///
14 | /// Returns the for the specified lamba expression.
15 | ///
16 | /// A lamba expression containing a MemberExpression.
17 | /// A object for the member in the specified lambda expression.
18 | public static MemberInfo GetMemberInfo(LambdaExpression lambda)
19 | {
20 | Expression expr = lambda;
21 | while (true)
22 | {
23 | switch (expr.NodeType)
24 | {
25 | case ExpressionType.Lambda:
26 | expr = ((LambdaExpression)expr).Body;
27 | break;
28 |
29 | case ExpressionType.Convert:
30 | expr = ((UnaryExpression)expr).Operand;
31 | break;
32 |
33 | case ExpressionType.MemberAccess:
34 | var memberExpression = (MemberExpression)expr;
35 | var member = memberExpression.Member;
36 | Type paramType;
37 |
38 | while (memberExpression != null)
39 | {
40 | paramType = memberExpression.Type;
41 |
42 | // Find the member on the base type of the member type
43 | // E.g. EmailAddress.Value
44 | var baseMember = paramType.GetMembers().FirstOrDefault(m => m.Name == member.Name);
45 | if (baseMember != null)
46 | {
47 | // Don't use the base type if it's just the nullable type of the derived type
48 | // or when the same member exists on a different type
49 | // E.g. Nullable -> decimal
50 | // or: SomeType { string Length; } -> string.Length
51 | if (baseMember is PropertyInfo baseProperty && member is PropertyInfo property)
52 | {
53 | if (baseProperty.DeclaringType == property.DeclaringType &&
54 | baseProperty.PropertyType != Nullable.GetUnderlyingType(property.PropertyType))
55 | {
56 | return baseMember;
57 | }
58 | }
59 | else
60 | {
61 | return baseMember;
62 | }
63 | }
64 |
65 | memberExpression = memberExpression.Expression as MemberExpression;
66 | }
67 |
68 | // Make sure we get the property from the derived type.
69 | paramType = lambda.Parameters[0].Type;
70 | return paramType.GetMember(member.Name)[0];
71 |
72 | default:
73 | return null;
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Dommel.Tests/Dapper.FluentMap.Dommel.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 | false
5 |
6 |
7 |
8 |
9 |
10 |
11 | runtime; build; native; contentfiles; analyzers; buildtransitive
12 | all
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Dommel.Tests/ManualMappingTests.cs:
--------------------------------------------------------------------------------
1 | using Dapper.FluentMap.Dommel.Mapping;
2 | using System;
3 | using System.ComponentModel.DataAnnotations.Schema;
4 | using System.Linq;
5 | using Xunit;
6 |
7 | [assembly: CollectionBehavior(DisableTestParallelization = true)]
8 | namespace Dapper.FluentMap.Dommel.Tests
9 | {
10 | public class ManualMappingTests
11 | {
12 | [Fact]
13 | public void EntityMapsCustomIdAsKey()
14 | {
15 | PreTest();
16 |
17 | FluentMapper.Initialize(c => c.AddMap(new MapWithCustomIdPropertyMap()));
18 |
19 | var type = typeof(CustomIdEntity);
20 | var keyResolver = new Dommel.Resolvers.DommelKeyPropertyResolver();
21 | var columnResolver = new Dommel.Resolvers.DommelColumnNameResolver();
22 |
23 | var keys = keyResolver.ResolveKeyProperties(type);
24 | var columnName = columnResolver.ResolveColumnName(keys.Single().Property);
25 |
26 | Assert.Single(keys);
27 | Assert.Equal("customid", columnName);
28 | }
29 |
30 | [Fact]
31 | public void EntityMapsToSingleCustomId()
32 | {
33 | PreTest();
34 |
35 | FluentMapper.Initialize(c => c.AddMap(new MapSingleCustomIdPropertyMap()));
36 |
37 | var type = typeof(DoubleIdEntity);
38 | var keyResolver = new Dommel.Resolvers.DommelKeyPropertyResolver();
39 | var columnResolver = new Dommel.Resolvers.DommelColumnNameResolver();
40 | FluentMapper.EntityMaps.TryGetValue(type, out var entityMap);
41 |
42 | var keys = keyResolver.ResolveKeyProperties(type);
43 | var columnName = columnResolver.ResolveColumnName(keys.Single().Property);
44 |
45 | var idName = columnResolver.ResolveColumnName(entityMap.PropertyMaps.Single(x => x.PropertyInfo.Name == nameof(DoubleIdEntity.Id)).PropertyInfo);
46 |
47 | Assert.Single(keys);
48 | Assert.Equal("id", columnName);
49 | Assert.Equal("Id", idName);
50 | }
51 |
52 | [Fact]
53 | public void EntityMapsToDefaultSingleKey()
54 | {
55 | PreTest();
56 |
57 | FluentMapper.Initialize(c => c.AddMap(new MapSingleCustomIdDefaultKey()));
58 |
59 | var type = typeof(DoubleIdEntity);
60 | var keyResolver = new Dommel.Resolvers.DommelKeyPropertyResolver();
61 | var keys = keyResolver.ResolveKeyProperties(type);
62 | Assert.Single(keys);
63 | }
64 |
65 | [Fact]
66 | public void KeyPropertyIsGenerated()
67 | {
68 | PreTest();
69 |
70 | FluentMapper.Initialize(c => c.AddMap(new MapSingleCustomIdPropertyMap()));
71 |
72 | var type = typeof(DoubleIdEntity);
73 | var keyResolver = new Dommel.Resolvers.DommelKeyPropertyResolver();
74 | var keys = keyResolver.ResolveKeyProperties(type);
75 |
76 | var key = keys.FirstOrDefault();
77 | Assert.True(key.IsGenerated);
78 | }
79 |
80 | [Fact]
81 |
82 | public void PropertyIsGenerated()
83 | {
84 | PreTest();
85 |
86 | FluentMapper.Initialize(c => c.AddMap(new MapSingleCustomIdPropertyMap()));
87 |
88 | var type = typeof(DoubleIdEntity);
89 | var propertyResolver = new Dommel.Resolvers.DommelPropertyResolver();
90 | var properties = propertyResolver.ResolveProperties(type);
91 |
92 | var property = properties.Where(x => x.IsGenerated);
93 | Assert.NotEmpty(property);
94 |
95 | }
96 |
97 | [Fact]
98 | public void EntityMapsToMultipleKeys()
99 | {
100 | PreTest();
101 |
102 | FluentMapper.Initialize(c => c.AddMap(new MapCompositeKeyPropertyMap()));
103 |
104 | var type = typeof(CompositeKeyEntity);
105 | var keyResolver = new Dommel.Resolvers.DommelKeyPropertyResolver();
106 | var keys = keyResolver.ResolveKeyProperties(type);
107 |
108 | Assert.True(keys.Count() == 2);
109 | Assert.All(keys, k => Assert.False(k.IsGenerated));
110 | }
111 |
112 | [Fact]
113 |
114 | public void PropertiesAreNotGenerated()
115 | {
116 | PreTest();
117 |
118 | FluentMapper.Initialize(c => c.AddMap(new MapCompositeKeyPropertyMap()));
119 |
120 | var type = typeof(CompositeKeyEntity);
121 | var propertyResolver = new Dommel.Resolvers.DommelPropertyResolver();
122 | var properties = propertyResolver.ResolveProperties(type);
123 |
124 | Assert.All(properties, p => Assert.False(p.IsGenerated));
125 | }
126 |
127 | private static void PreTest()
128 | {
129 | FluentMapper.EntityMaps.Clear();
130 | FluentMapper.TypeConventions.Clear();
131 | }
132 |
133 | private class MapWithCustomIdPropertyMap : DommelEntityMap
134 | {
135 | public MapWithCustomIdPropertyMap()
136 | {
137 | Map(p => p.CustomId).ToColumn("customid").IsIdentity().IsKey();
138 | }
139 | }
140 |
141 | private class MapSingleCustomIdPropertyMap : DommelEntityMap
142 | {
143 | public MapSingleCustomIdPropertyMap()
144 | {
145 | Map(p => p.Id);
146 | Map(p => p.CustomId).IsKey().ToColumn("id", false);
147 | }
148 | }
149 |
150 | private class MapSingleCustomIdDefaultKey : DommelEntityMap
151 | {
152 | public MapSingleCustomIdDefaultKey()
153 | {
154 | Map(p => p.CustomId).ToColumn("customid", false);
155 | }
156 | }
157 |
158 | private class MapCompositeKeyPropertyMap : DommelEntityMap
159 | {
160 | public MapCompositeKeyPropertyMap()
161 | {
162 | Map(p => p.KeyPartOne).IsKey().SetGeneratedOption(DatabaseGeneratedOption.None);
163 | Map(p => p.KeyPartTwo).IsKey().SetGeneratedOption(DatabaseGeneratedOption.None);
164 | }
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Dommel.Tests/TestEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dapper.FluentMap.Dommel.Tests
8 | {
9 | public class TestEntity
10 | {
11 | public int Id { get; set; }
12 |
13 | public int? OtherId { get; set; }
14 | }
15 |
16 | public class CustomIdEntity
17 | {
18 | public int CustomId { get; set; }
19 | public string Name { get; set; }
20 | }
21 |
22 | public class DoubleIdEntity
23 | {
24 | public int Id { get; set; }
25 | public int CustomId { get; set; }
26 | public string Name { get; set; }
27 | }
28 |
29 | public class Other
30 | {
31 | public int Id { get; set; }
32 | }
33 |
34 | public class CompositeKeyEntity
35 | {
36 | public int KeyPartOne { get; set; }
37 | public int KeyPartTwo { get; set; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Tests/ConventionTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 | using Dapper.FluentMap.Conventions;
4 | using Xunit;
5 |
6 | namespace Dapper.FluentMap.Tests
7 | {
8 | public class ConventionTests
9 | {
10 | public ConventionTests()
11 | {
12 | // Clear configurations
13 | FluentMapper.EntityMaps.Clear();
14 | FluentMapper.TypeConventions.Clear();
15 | }
16 |
17 | public class Bar
18 | {
19 | public string Name { get; set; }
20 | }
21 |
22 | [Fact]
23 | public void DerivedProperties()
24 | {
25 | // Arrange
26 | FluentMapper.Initialize(c => c.AddConvention().ForEntity());
27 | var typeMap = SqlMapper.GetTypeMap(typeof(DerivedTestEntity));
28 |
29 | // Act
30 | var colName = typeMap.GetMember("colName");
31 | var colId = typeMap.GetMember("colId");
32 |
33 | //Assert
34 | Assert.NotNull(colName);
35 | Assert.NotNull(colId);
36 | Assert.Equal(typeof(DerivedTestEntity).GetProperty(nameof(DerivedTestEntity.Name)), colName.Property);
37 | Assert.Equal(typeof(DerivedTestEntity).GetProperty(nameof(TestEntity.Id)), colId.Property);
38 | }
39 |
40 | [Fact]
41 | public void NullableProperties()
42 | {
43 | // Arrange
44 | FluentMapper.Initialize(c => c.AddConvention().ForEntity());
45 | var typeMap = SqlMapper.GetTypeMap(typeof(TestEntity));
46 |
47 | // Act
48 | var colValue = typeMap.GetMember("intId");
49 | var colValueNotNull = typeMap.GetMember("intOtherId");
50 |
51 | //Assert
52 | Assert.NotNull(colValue);
53 | Assert.NotNull(colValueNotNull);
54 | Assert.Equal(typeof(TestEntity).GetProperty(nameof(TestEntity.Id)), colValue.Property);
55 | Assert.Equal(typeof(TestEntity).GetProperty(nameof(TestEntity.OtherId)), colValueNotNull.Property);
56 | }
57 |
58 | [Fact]
59 | public void ExplicitNullableProperties()
60 | {
61 | // Arrange
62 | FluentMapper.Initialize(c => c.AddConvention().ForEntity());
63 | var typeMap = SqlMapper.GetTypeMap(typeof(TestEntityWithNullable));
64 |
65 | // Act
66 | var colValue = typeMap.GetMember("decValue");
67 | var colValueNotNull = typeMap.GetMember("decValueNotNull");
68 |
69 | //Assert
70 | Assert.NotNull(colValue);
71 | Assert.Null(colValueNotNull);
72 | Assert.Equal(typeof(TestEntityWithNullable).GetProperty(nameof(TestEntityWithNullable.Value)), colValue.Property);
73 | }
74 |
75 | [Fact]
76 | public void TwoEntitiesWithSamePropertyName()
77 | {
78 | // Arrange
79 | FluentMapper.Initialize(c =>
80 | c.AddConvention()
81 | .ForEntity()
82 | .ForEntity());
83 |
84 | var typeMapFoo = SqlMapper.GetTypeMap(typeof(DerivedTestEntity));
85 | var typeMapBar = SqlMapper.GetTypeMap(typeof(Bar));
86 |
87 | // Act
88 | var colNameFoo = typeMapFoo.GetMember("colName");
89 | var colNameBar = typeMapBar.GetMember("colName");
90 |
91 | // Assert
92 | Assert.NotNull(colNameFoo);
93 | Assert.NotNull(colNameBar);
94 | Assert.Equal(typeof(DerivedTestEntity).GetProperty(nameof(Bar.Name)), colNameFoo.Property);
95 | Assert.Equal(typeof(Bar).GetProperty(nameof(Bar.Name)), colNameBar.Property);
96 | }
97 |
98 | [Fact]
99 | public void ShouldMapEntitiesInAssembly()
100 | {
101 | // Arrange & Act
102 | FluentMapper.Initialize(c => c.AddConvention().ForEntitiesInAssembly(typeof(ConventionTests).GetTypeInfo().Assembly));
103 |
104 | // Assert
105 | var conventions = FluentMapper.TypeConventions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
106 | Assert.NotEmpty(conventions);
107 | Assert.True(conventions.ContainsKey(typeof(TestEntity)));
108 | var map = conventions[typeof(TestEntity)];
109 | Assert.True(map[0] is TestConvention);
110 | }
111 |
112 | private class TestConvention : Convention
113 | {
114 | public TestConvention()
115 | {
116 | Properties().
117 | Where(p => p.Name.ToLower() == "id")
118 | .Configure(c => c.HasColumnName("autID"));
119 | }
120 | }
121 |
122 | private class NullableConvention : Convention
123 | {
124 | public NullableConvention()
125 | {
126 | Properties()
127 | .Configure(c => c.HasPrefix("int"));
128 | }
129 | }
130 |
131 | private class ExplicitNullableConvention : Convention
132 | {
133 | public ExplicitNullableConvention()
134 | {
135 | Properties()
136 | .Configure(c => c.HasPrefix("dec"));
137 | }
138 | }
139 |
140 | private class DerivedConvention : Convention
141 | {
142 | public DerivedConvention()
143 | {
144 | Properties()
145 | .Configure(c => c.HasPrefix("col"));
146 | }
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Tests/Dapper.FluentMap.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 | false
5 |
6 |
7 |
8 |
9 |
10 | runtime; build; native; contentfiles; analyzers; buildtransitive
11 | all
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Tests/ManualMappingTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Dapper.FluentMap.Mapping;
4 | using Dapper.FluentMap.TypeMaps;
5 | using Xunit;
6 |
7 | [assembly: CollectionBehavior(DisableTestParallelization = true)]
8 | namespace Dapper.FluentMap.Tests
9 | {
10 | public class ManualMappingTests
11 | {
12 | [Fact]
13 | public void DuplicateMappingShouldThrow_Exception()
14 | {
15 | // Arrange
16 | PreTest();
17 |
18 | // Act & Assert
19 | Assert.Throws(() => new MapWithDuplicateMapping());
20 | }
21 |
22 | [Fact]
23 | public void EntityMapShouldHaveEmptyPropertyMapCollection()
24 | {
25 | // Arrange
26 | PreTest();
27 |
28 | // Act
29 | var map = new EmptyMap();
30 |
31 | // Assert
32 | Assert.NotNull(map.PropertyMaps);
33 | Assert.Empty(map.PropertyMaps);
34 | }
35 |
36 | [Fact]
37 | public void EntityMapShouldHavePropertyMap()
38 | {
39 | // Arrange
40 | PreTest();
41 |
42 | // Act
43 | var map = new MapWithOnePropertyMap();
44 |
45 | // Assert
46 | Assert.Single(map.PropertyMaps);
47 | }
48 |
49 | [Fact]
50 | public void PropertyMapShouldBeCaseInsensitive()
51 | {
52 | // Arrange
53 | PreTest();
54 |
55 | // Act
56 | var map = new CaseInsensitveMap();
57 | var propertyMap = map.PropertyMaps.Single();
58 |
59 | // Assert
60 | Assert.Equal("Test", propertyMap.ColumnName, ignoreCase: true);
61 | Assert.False(propertyMap.CaseSensitive);
62 | }
63 |
64 | [Fact]
65 | public void PropertyShouldBeIgnored()
66 | {
67 | // Arrange
68 | PreTest();
69 |
70 | // Act
71 | var map = new IgnoreMap();
72 | var propertyMap = map.PropertyMaps.Single();
73 |
74 | // Assert
75 | Assert.True(propertyMap.Ignored);
76 | }
77 |
78 | [Fact]
79 | public void PropertyInfoNameShouldBeId()
80 | {
81 | // Arrange
82 | PreTest();
83 |
84 | // Act
85 | var map = new MapWithOnePropertyMap();
86 | var propertyMap = map.PropertyMaps.Single();
87 |
88 | // Assert
89 | Assert.Equal("Id", propertyMap.PropertyInfo.Name);
90 | }
91 |
92 | [Fact]
93 | public void FluentMapperInitializeShouldAddEntityMap()
94 | {
95 | // Arrange
96 | PreTest();
97 |
98 | // Act
99 | FluentMapper.Initialize(c => c.AddMap(new MapWithOnePropertyMap()));
100 | var entityMap = FluentMapper.EntityMaps.Single();
101 |
102 | // Assert
103 | Assert.Single(FluentMapper.EntityMaps);
104 | Assert.Equal(typeof(TestEntity), entityMap.Key);
105 | Assert.IsType(entityMap.Value);
106 | }
107 |
108 | [Fact]
109 | public void FluentMapperInitializeShouldAddDapperTypeMap()
110 | {
111 | // Arrange
112 | PreTest();
113 |
114 | // Act
115 | FluentMapper.Initialize(c => c.AddMap(new MapWithOnePropertyMap()));
116 | var typeMap = SqlMapper.GetTypeMap(typeof(TestEntity));
117 |
118 | // Assert
119 | Assert.NotNull(typeMap);
120 | Assert.IsType>(typeMap);
121 | }
122 |
123 | [Fact]
124 | public void PropertyMapShouldMapInheritedProperies()
125 | {
126 | // Arrange
127 | PreTest();
128 |
129 | // Act
130 | var map = new DerivedMap();
131 | var idMap = map.PropertyMaps.First();
132 | var nameMap = map.PropertyMaps.Skip(1).First();
133 |
134 | // Assert
135 | // todo: should be ReflectedType so the type is DerivedTestEntity
136 | Assert.Equal(typeof(TestEntity), idMap.PropertyInfo.DeclaringType);
137 | Assert.Equal(typeof(DerivedTestEntity), nameMap.PropertyInfo.DeclaringType);
138 | }
139 |
140 | [Fact]
141 | public void PropertyMapShouldMapValueObjectProperties()
142 | {
143 | PreTest();
144 |
145 | var map = new ValueObjectMap();
146 | var email = map.PropertyMaps.First();
147 | Assert.Equal(typeof(EmailTestValueObject), email.PropertyInfo.DeclaringType);
148 | }
149 |
150 | private static void PreTest()
151 | {
152 | FluentMapper.EntityMaps.Clear();
153 | FluentMapper.TypeConventions.Clear();
154 | }
155 |
156 | private class IgnoreMap : EntityMap
157 | {
158 | public IgnoreMap()
159 | {
160 | Map(p => p.Id).Ignore();
161 | }
162 | }
163 |
164 | private class CaseInsensitveMap : EntityMap
165 | {
166 | public CaseInsensitveMap()
167 | {
168 | Map(p => p.Id).ToColumn("test", caseSensitive: false);
169 | }
170 | }
171 |
172 | private class MapWithOnePropertyMap : EntityMap
173 | {
174 | public MapWithOnePropertyMap()
175 | {
176 | Map(p => p.Id).ToColumn("test");
177 | }
178 | }
179 |
180 | private class DerivedMap : EntityMap
181 | {
182 | public DerivedMap()
183 | {
184 | Map(p => p.Id).ToColumn("intId");
185 | Map(p => p.Name).ToColumn("strName");
186 | }
187 | }
188 |
189 | private class MapWithDuplicateMapping : EntityMap
190 | {
191 | public MapWithDuplicateMapping()
192 | {
193 | Map(p => p.Id).ToColumn("id");
194 | Map(p => p.Id).ToColumn("id2");
195 | }
196 | }
197 |
198 | private class EmptyMap : EntityMap
199 | {
200 | }
201 |
202 | private class ValueObjectMap : EntityMap
203 | {
204 | public ValueObjectMap()
205 | {
206 | Map(x => x.Email.Address).ToColumn("email");
207 | }
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Tests/ReflectionHelperTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 | using System.Reflection;
4 | using Dapper.FluentMap.Utils;
5 | using Xunit;
6 |
7 | namespace Dapper.FluentMap.Tests
8 | {
9 | public class ReflectionHelperTests
10 | {
11 | [Fact]
12 | public void GetMemberInfo_ReturnsProperty()
13 | {
14 | // Arrange
15 | Expression> expression = e => e.Id;
16 |
17 | // Act
18 | var memberInfo = ReflectionHelper.GetMemberInfo(expression);
19 |
20 | // Assert
21 | Assert.Equal(typeof(TestEntity).GetProperty("Id"), memberInfo);
22 | Assert.Equal(typeof(int), ((PropertyInfo)memberInfo).PropertyType);
23 | }
24 |
25 | [Fact]
26 | public void GetMemberInfo_ReturnsProperty_OfDerivedType()
27 | {
28 | // Arrange
29 | Expression> expression = e => e.Id;
30 |
31 | // Act
32 | var memberInfo = ReflectionHelper.GetMemberInfo(expression);
33 |
34 | // Assert
35 | Assert.Equal(typeof(DerivedTestEntity).GetProperty("Id"), memberInfo);
36 | Assert.Equal(typeof(int), ((PropertyInfo)memberInfo).PropertyType);
37 | }
38 |
39 | [Fact]
40 | public void GetMemberInfo_ReturnsProperty_OfNullableSystemType()
41 | {
42 | // Arrange
43 | Expression> expression = e => e.Value;
44 |
45 | // Act
46 | var memberInfo = ReflectionHelper.GetMemberInfo(expression);
47 |
48 | // Assert
49 | Assert.Equal(typeof(TestEntityWithNullable).GetProperty("Value"), memberInfo);
50 | Assert.Equal(typeof(TestEntityWithNullable), memberInfo.DeclaringType);
51 | Assert.Equal(typeof(decimal?), ((PropertyInfo)memberInfo).PropertyType);
52 | }
53 |
54 | [Fact]
55 | public void GetMemberInfo_ReturnsValueTypeProperty()
56 | {
57 | // Arrange
58 | Expression> expression = e => e.Email.Address;
59 |
60 | // Act
61 | var memberInfo = ReflectionHelper.GetMemberInfo(expression);
62 |
63 | // Assert
64 | Assert.Equal(typeof(EmailTestValueObject).GetProperty(nameof(EmailTestValueObject.Address)), memberInfo);
65 | Assert.Equal(typeof(EmailTestValueObject), memberInfo.DeclaringType);
66 | Assert.Equal(typeof(string), ((PropertyInfo)memberInfo).PropertyType);
67 | }
68 |
69 | [Fact]
70 | public void GetMemberInfo_ReturnsValueTypeProperty_WithSystemTypeNames()
71 | {
72 | // Arrange
73 | Expression> expression = e => e.String.Length;
74 |
75 | // Act
76 | var memberInfo = ReflectionHelper.GetMemberInfo(expression);
77 |
78 | // Assert
79 | Assert.Equal(typeof(SameMemberAsStringObject).GetProperty(nameof(SameMemberAsStringObject.Length)), memberInfo);
80 | Assert.Equal(typeof(SameMemberAsStringObject), memberInfo.DeclaringType);
81 | Assert.Equal(typeof(string), ((PropertyInfo)memberInfo).PropertyType);
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/test/Dapper.FluentMap.Tests/TestEntity.cs:
--------------------------------------------------------------------------------
1 | namespace Dapper.FluentMap.Tests
2 | {
3 | public class TestEntity
4 | {
5 | public int Id { get; set; }
6 |
7 | public int? OtherId { get; set; }
8 | }
9 |
10 | public class TestEntityWithNullable
11 | {
12 | public int Id { get; set; }
13 |
14 | public decimal? Value { get; set; }
15 | }
16 |
17 | public class DerivedTestEntity : TestEntity
18 | {
19 | public string Name { get; set; }
20 | }
21 |
22 | public class ValueObjectTestEntity
23 | {
24 | public EmailTestValueObject Email { get; set; }
25 |
26 | public SameMemberAsStringObject String { get; set; }
27 | }
28 |
29 | public class EmailTestValueObject
30 | {
31 | public string Address { get; set; }
32 | }
33 |
34 | public class SameMemberAsStringObject
35 | {
36 | public string Length { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------