diff --git a/README.md b/README.md index e250795..2edf655 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ This [Dapper](https://github.com/SamSaffron/dapper-dot-net/) extension allows yo
### Usage -Mapping properties: -``` +#### Mapping properties using `EntityMap` +```csharp public class ProductMap : EntityMap { public ProductMap() @@ -27,13 +27,54 @@ public class ProductMap : EntityMap } ``` -Initializing Dapper.FluentMap: - -``` +Initialization: +```csharp FluentMapper.Intialize(config => { - config.AddEntityMap(new ProductMap()); + config.AddMap(new ProductMap()); }); ``` -That's it. When querying the database using Dapper, the product name from column `strName` will be mapped to the `Name` property of the `Product` entity. `LastModifed` won't be mapped since we marked it as 'ignored'. +#### Mapping properties using conventions + +You can create a convention by creating a class which derives from the `Convention` class. In the contructor you can configure the property conventions: +```csharp +public class TypePrefixConvention : Convention +{ + public TypePrefixConvention() + { + // Map all properties of type int and with the name 'id' to column 'autID'. + Properties() + .Where(c => c.Name.ToLower() == "id") + .Configure(c => c.HasColumnName("autID")); + + // Prefix all properties of type string with 'str' when mapping to column names. + Properties() + .Configure(c => c.HasPrefix("str")); + + // Prefix all properties of type int with 'int' when mapping to column names. + Properties() + .Configure(c => c.HasPrefix("int")); + } +} +``` + +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 scan a specified, or the current assembly. + +```csharp +FluentMapper.Intialize(config => + { + // Configure entities explicitly. + config.AddConvention(new TypePrefixConvention()) + .ForEntity() + .ForEntity; + + // Configure all entities in a certain assembly with an optional namespaces filter. + config.AddConvention(new TypePrefixConvention()) + .ForEntitiesInAssembly(typeof (Product).Assembly, "App.Domain.Model"); + + // Configure all entities in the current assembly with an optional namespaces filter. + config.AddConvention(new TypePrefixConvention()) + .ForEntitiesInCurrentAssembly("App.Domain.Model.Catalog", "App.Domain.Model.Order"); + }); +``` diff --git a/Dapper.FluentMap.1.0.0.nupkg b/nuget/Dapper.FluentMap.1.0.0.nupkg similarity index 100% rename from Dapper.FluentMap.1.0.0.nupkg rename to nuget/Dapper.FluentMap.1.0.0.nupkg diff --git a/Dapper.FluentMap.1.0.1.nupkg b/nuget/Dapper.FluentMap.1.0.1.nupkg similarity index 100% rename from Dapper.FluentMap.1.0.1.nupkg rename to nuget/Dapper.FluentMap.1.0.1.nupkg diff --git a/Dapper.FluentMap.1.0.2.nupkg b/nuget/Dapper.FluentMap.1.0.2.nupkg similarity index 100% rename from Dapper.FluentMap.1.0.2.nupkg rename to nuget/Dapper.FluentMap.1.0.2.nupkg diff --git a/nuget/Dapper.FluentMap.1.1.0.nupkg b/nuget/Dapper.FluentMap.1.1.0.nupkg new file mode 100644 index 0000000..e5620f2 Binary files /dev/null and b/nuget/Dapper.FluentMap.1.1.0.nupkg differ diff --git a/src/Dapper.FluentMap/Configuration/FluentConventionConfiguration.cs b/src/Dapper.FluentMap/Configuration/FluentConventionConfiguration.cs new file mode 100644 index 0000000..434b2e7 --- /dev/null +++ b/src/Dapper.FluentMap/Configuration/FluentConventionConfiguration.cs @@ -0,0 +1,142 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using Dapper.FluentMap.Conventions; +using Dapper.FluentMap.Mapping; +using Dapper.FluentMap.Utils; + +namespace Dapper.FluentMap.Configuration +{ + /// + /// Defines methods for configuring conventions. + /// + public class FluentConventionConfiguration + { + private readonly Convention _convention; + + /// + /// Initializes a new instance of the class, + /// allowing configuration of conventions. + /// + /// The convention. + public FluentConventionConfiguration(Convention convention) + { + _convention = convention; + } + + /// + /// Configures the current covention for the specified entity type. + /// + /// The type of the entity. + /// The current instance of . + public FluentConventionConfiguration ForEntity() + { + Type type = typeof (T); + MapProperties(type); + + FluentMapper.TypeConventions.AddOrUpdate(type, _convention); + FluentMapper.AddConventionTypeMap(); + return this; + } + + /// + /// Configures the current convention for all the entities in current assembly filtered by the specified namespaces. + /// + /// An array of namespaces which filter the types in the current assembly. This parameter is optional. + /// The current instance of . + public FluentConventionConfiguration ForEntitiesInCurrentAssembly(params string[] namespaces) + { + foreach (var type in Assembly.GetCallingAssembly() + .GetExportedTypes() + .Where(type => namespaces.Length == 0 || namespaces.Any(n => type.Namespace == n))) + { + MapProperties(type); + FluentMapper.TypeConventions.AddOrUpdate(type, _convention); + FluentMapper.AddConventionTypeMap(type); + } + + return this; + } + + /// + /// Configures the current convention for all entities in the specified assembly filtered by the specified namespaces. + /// + /// The assembly to scan for entities. + /// An array of namespaces which filter the types in . This parameter is optional. + /// The current instance of . + public FluentConventionConfiguration ForEntitiesInAssembly(Assembly assembly, params string[] namespaces) + { + foreach (var type in assembly.GetExportedTypes().Where(t => namespaces.Any(n => n.Contains(t.Namespace)))) + { + MapProperties(type); + FluentMapper.TypeConventions.AddOrUpdate(type, _convention); + FluentMapper.AddConventionTypeMap(type); + } + + return this; + } + + private void MapProperties(Type type) + { + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var property in properties) + { + // Find the convention configurations for the convetion with either none or matching property predicates. + foreach (var config in _convention.ConventionConfigurations + .Where(c => c.PropertyPredicates.Count <= 0 || + c.PropertyPredicates.All(e => e(property)))) + { + if (!string.IsNullOrEmpty(config.Config.ColumnName)) + { + AddConventionPropertyMap(property, config.Config.ColumnName); + break; + } + + if (!string.IsNullOrEmpty(config.Config.Prefix)) + { + AddConventionPropertyMap(property, config.Config.Prefix + property.Name); + break; + } + } + } + } + + private void AddConventionPropertyMap(PropertyInfo property, string columnName) + { + var map = new PropertyMap(property, columnName); + _convention.PropertyMaps.Add(map); + } + + #region EditorBrowsableStates + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() + { + return base.ToString(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + #endregion + } +} diff --git a/src/Dapper.FluentMap/Configuration/FluentMapConfiguration.cs b/src/Dapper.FluentMap/Configuration/FluentMapConfiguration.cs new file mode 100644 index 0000000..5f48013 --- /dev/null +++ b/src/Dapper.FluentMap/Configuration/FluentMapConfiguration.cs @@ -0,0 +1,67 @@ +using System; +using System.ComponentModel; +using Dapper.FluentMap.Conventions; +using Dapper.FluentMap.Mapping; + +namespace Dapper.FluentMap.Configuration +{ + /// + /// Defines methods for configuring Dapper.FluentMap. + /// + public class FluentMapConfiguration + { + /// + /// Adds the specified to the configuration of Dapper.FluentMap. + /// + /// The type argument of the entity. + /// An instance of the EntityMap classs containing the entity mapping configuration. + public void AddMap(EntityMap mapper) where TEntity : class + { + FluentMapper.EntityMappers.Add(typeof (TEntity), mapper); + FluentMapper.AddTypeMap(); + } + + /// + /// Adds the specified to the configuration of Dapper.FluentMap. + /// + /// An instance of the class. + /// + /// An instance of + /// which allows configuration of the convention. + /// + public FluentConventionConfiguration AddConvention(Convention convention) + { + return new FluentConventionConfiguration(convention); + } + + #region EditorBrowsableStates + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() + { + return base.ToString(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + #endregion + } +} diff --git a/src/Dapper.FluentMap/Conventions/Convention.cs b/src/Dapper.FluentMap/Conventions/Convention.cs new file mode 100644 index 0000000..2e2aa6a --- /dev/null +++ b/src/Dapper.FluentMap/Conventions/Convention.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Dapper.FluentMap.Mapping; + +namespace Dapper.FluentMap.Conventions +{ + /// + /// Represents a convention for mapping entity properties to column names. + /// + public abstract class Convention + { + /// + /// Initializes a new instance of the class. + /// + protected Convention() + { + ConventionConfigurations = new List(); + PropertyMaps = new List(); + } + + internal IList ConventionConfigurations { get; private set; } + + internal IList PropertyMaps { get; private set; } + + /// + /// Configures a convention that applies on all properties of the entity. + /// + /// A configuration object for the convention. + protected PropertyConventionConfiguration Properties() + { + var config = new PropertyConventionConfiguration(); + ConventionConfigurations.Add(config); + + return config; + } + + /// + /// Configures a convention that applies on all the properties of a specified type of the entity. + /// + /// The type of the properties that the convention will apply to. + /// A configuration object for the convention. + protected PropertyConventionConfiguration Properties() + { + // Get the underlying type for a nullale type. (int? -> int) + Type underlyingType = Nullable.GetUnderlyingType(typeof (T)) ?? typeof (T); + var config = new PropertyConventionConfiguration().Where(p => p.PropertyType == underlyingType); + ConventionConfigurations.Add(config); + + return config; + } + + #region EditorBrowsableStates + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() + { + return base.ToString(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + #endregion + } +} diff --git a/src/Dapper.FluentMap/Conventions/ConventionPropertyConfiguration.cs b/src/Dapper.FluentMap/Conventions/ConventionPropertyConfiguration.cs new file mode 100644 index 0000000..032b6dc --- /dev/null +++ b/src/Dapper.FluentMap/Conventions/ConventionPropertyConfiguration.cs @@ -0,0 +1,67 @@ +using System; +using System.ComponentModel; + +namespace Dapper.FluentMap.Conventions +{ + /// + /// Represents configuration of a property via conventions. + /// + public class ConventionPropertyConfiguration + { + internal string ColumnName { get; private set; } + + internal string Prefix { get; private set; } + + /// + /// Configures the name of the database column used to store the property. + /// + /// The name of the database column. + /// The same instance of . + public ConventionPropertyConfiguration HasColumnName(string columnName) + { + ColumnName = columnName; + return this; + } + + /// + /// Configures the prefix of the database column used to store the property. + /// + /// The prefix of the database column. + /// The same instance of . + public ConventionPropertyConfiguration HasPrefix(string prefix) + { + Prefix = prefix; + return this; + } + + #region EditorBrowsableStates + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() + { + return base.ToString(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + #endregion + } +} diff --git a/src/Dapper.FluentMap/Conventions/PropertyConventionConfiguration.cs b/src/Dapper.FluentMap/Conventions/PropertyConventionConfiguration.cs new file mode 100644 index 0000000..08da724 --- /dev/null +++ b/src/Dapper.FluentMap/Conventions/PropertyConventionConfiguration.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; + +namespace Dapper.FluentMap.Conventions +{ + /// + /// Represents the configuration for a convention. + /// + public class PropertyConventionConfiguration + { + /// + /// Initializes a new instance of the class, + /// allowing configuration for a convention. + /// + public PropertyConventionConfiguration() + { + PropertyPredicates = new List>(); + } + + internal IList> PropertyPredicates { get; private set; } + + internal ConventionPropertyConfiguration Config { get; private set; } + + /// + /// Filters the properties that this convention applies to based on a predicate. + /// + /// A function to test each property for a condition. + /// The same instance of . + public PropertyConventionConfiguration Where(Func predicate) + { + PropertyPredicates.Add(predicate); + return this; + } + + /// + /// Configures the properties that this convention applies to. + /// + /// An action that performs configuration against . + public void Configure(Action configure) + { + var config = new ConventionPropertyConfiguration(); + Config = config; + configure(config); + } + + #region EditorBrowsableStates + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() + { + return base.ToString(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + #endregion + } +} diff --git a/src/Dapper.FluentMap/Dapper.FluentMap.csproj b/src/Dapper.FluentMap/Dapper.FluentMap.csproj index 63060ac..af74e48 100644 --- a/src/Dapper.FluentMap/Dapper.FluentMap.csproj +++ b/src/Dapper.FluentMap/Dapper.FluentMap.csproj @@ -45,14 +45,20 @@ - - + + + + + + - - + + + - - + + + diff --git a/src/Dapper.FluentMap/FluentMapConfiguration.cs b/src/Dapper.FluentMap/FluentMapConfiguration.cs deleted file mode 100644 index 704a102..0000000 --- a/src/Dapper.FluentMap/FluentMapConfiguration.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Dapper.FluentMap -{ - /// - /// Defines methods for configuring Dapper.FluentMap. - /// - public interface IFluentMapConfiguration - { - /// - /// Adds the specified EntityMap to the configuration of Dapper.FluentMap. - /// - /// The type argument of the entity. - /// An instance of the EntityMap classs containing the entity mapping configuration. - void AddEntityMap(EntityMap mapper) where TEntity : class; - } - - public class FluentMapConfiguration : IFluentMapConfiguration - { - public void AddEntityMap(EntityMap mapper) where TEntity : class - { - FluentMapper.EntityMappers.Add(typeof (TEntity), mapper); - FluentMapper.AddTypeMap(); - } - } -} diff --git a/src/Dapper.FluentMap/FluentMapper.cs b/src/Dapper.FluentMap/FluentMapper.cs index 358f648..16af4b9 100644 --- a/src/Dapper.FluentMap/FluentMapper.cs +++ b/src/Dapper.FluentMap/FluentMapper.cs @@ -1,5 +1,9 @@ using System; using System.Collections.Generic; +using Dapper.FluentMap.Configuration; +using Dapper.FluentMap.Conventions; +using Dapper.FluentMap.Mapping; +using Dapper.FluentMap.TypeMaps; namespace Dapper.FluentMap { @@ -8,25 +12,30 @@ namespace Dapper.FluentMap /// public static class FluentMapper { - private static readonly IFluentMapConfiguration _configuration = new FluentMapConfiguration(); + private static readonly FluentMapConfiguration _configuration = new FluentMapConfiguration(); /// /// Gets the dictionary containing the entity mapping per entity type. /// internal static readonly IDictionary EntityMappers = new Dictionary(); + /// + /// Gets the dictionairy containing the conventions per entity type. + /// + internal static readonly IDictionary> TypeConventions = new Dictionary>(); + /// /// Initializes Dapper.FluentMap with the specified configuration. /// This is method should be called when the application starts or when the first mapping is needed. /// /// A callback containing the configuration of Dapper.FluentMap. - public static void Intialize(Action configure) + public static void Intialize(Action configure) { configure(_configuration); } /// - /// Registers a Dapper type map for the specified . + /// Registers a Dapper type map using fluent mapping for the specified . /// /// The type of the entity. internal static void AddTypeMap() @@ -35,7 +44,7 @@ internal static void AddTypeMap() } /// - /// Registers a Dapper type map for the specified . + /// Registers a Dapper type map using fluent mapping for the specified . /// /// The type of the entity. internal static void AddTypeMap(Type entityType) @@ -43,5 +52,24 @@ internal static void AddTypeMap(Type entityType) var instance = (SqlMapper.ITypeMap)Activator.CreateInstance(typeof (FluentMapTypeMap<>).MakeGenericType(entityType)); SqlMapper.SetTypeMap(entityType, instance); } + + /// + /// Registers a Dapper type map using conventions for the specified . + /// + /// The type of the entity. + internal static void AddConventionTypeMap() + { + SqlMapper.SetTypeMap(typeof (TEntity), new FluentConventionTypeMap()); + } + + /// + /// Registers a Dapper type map using conventions for the specified . + /// + /// The type of the entity. + internal static void AddConventionTypeMap(Type entityType) + { + var instance = (SqlMapper.ITypeMap)Activator.CreateInstance(typeof (FluentConventionTypeMap<>).MakeGenericType(entityType)); + SqlMapper.SetTypeMap(entityType, instance); + } } } diff --git a/src/Dapper.FluentMap/EntityMap.cs b/src/Dapper.FluentMap/Mapping/EntityMap.cs similarity index 96% rename from src/Dapper.FluentMap/EntityMap.cs rename to src/Dapper.FluentMap/Mapping/EntityMap.cs index 22940ff..ac4e5af 100644 --- a/src/Dapper.FluentMap/EntityMap.cs +++ b/src/Dapper.FluentMap/Mapping/EntityMap.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; +using Dapper.FluentMap.Utils; -namespace Dapper.FluentMap +namespace Dapper.FluentMap.Mapping { /// /// Represents a non-typed mapping of an entity. diff --git a/src/Dapper.FluentMap/Mapping/PropertyMap.cs b/src/Dapper.FluentMap/Mapping/PropertyMap.cs new file mode 100644 index 0000000..43bc3c5 --- /dev/null +++ b/src/Dapper.FluentMap/Mapping/PropertyMap.cs @@ -0,0 +1,102 @@ +using System; +using System.ComponentModel; +using System.Reflection; + +namespace Dapper.FluentMap.Mapping +{ + /// + /// Represents the mapping of a property. + /// + public class PropertyMap + { + /// + /// Initializes a new instance of the using + /// the specified object representing the property to map. + /// + /// The object representing to the property to map. + internal PropertyMap(PropertyInfo info) + { + PropertyInfo = info; + ColumnName = info.Name; + } + + /// + /// Initializes a new instance of the using + /// the specified object representing the property to map + /// and column name to map the property to. + /// + /// The object representing to the property to map. + /// The column name in the database to map the property to. + internal PropertyMap(PropertyInfo info, string columnName) + { + PropertyInfo = info; + ColumnName = columnName; + } + + /// + /// Gets the name of the column in the data store. + /// + internal string ColumnName { get; private set; } + + /// + /// Gets a value indicating wether the property should be ignored when mapping. + /// + internal bool Ignored { get; private set; } + + /// + /// Gets the object for the current property. + /// + internal PropertyInfo PropertyInfo { get; private set; } + + /// + /// Maps the current property to the specified column name. + /// + /// The name of the column in the data store. + /// The current instance. This enables a fluent API. + public PropertyMap ToColumn(string columnName) + { + ColumnName = columnName; + return this; + } + + /// + /// Marks the current property as ignored, resulting in the property not being mapped by Dapper. + /// + /// The current instance. This enables a fluent API. + public PropertyMap Ignore() + { + Ignored = true; + return this; + } + + #region EditorBrowsableStates + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() + { + return base.ToString(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new Type GetType() + { + return base.GetType(); + } + #endregion + } +} diff --git a/src/Dapper.FluentMap/Properties/AssemblyInfo.cs b/src/Dapper.FluentMap/Properties/AssemblyInfo.cs index a511002..0422143 100644 --- a/src/Dapper.FluentMap/Properties/AssemblyInfo.cs +++ b/src/Dapper.FluentMap/Properties/AssemblyInfo.cs @@ -32,5 +32,4 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.2.0")] -[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyVersion("1.1.0")] diff --git a/src/Dapper.FluentMap/PropertyMap.cs b/src/Dapper.FluentMap/PropertyMap.cs deleted file mode 100644 index 883bffd..0000000 --- a/src/Dapper.FluentMap/PropertyMap.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Reflection; - -namespace Dapper.FluentMap -{ - public class PropertyMap - { - /// - /// Initializes a new instance of the using - /// the specified object representing the property to map. - /// - /// - public PropertyMap(PropertyInfo info) - { - PropertyInfo = info; - ColumnName = info.Name; - } - - /// - /// Gets the name of the column in the data store. - /// - internal string ColumnName { get; private set; } - - /// - /// Gets a value indicating wether the property should be ignored when mapping. - /// - internal bool Ignored { get; private set; } - - /// - /// Gets the object for the current property. - /// - internal PropertyInfo PropertyInfo { get; private set; } - - /// - /// Maps the current property to the specified column name. - /// - /// The name of the column in the data store. - /// The current instance. This enables a fluent API. - public PropertyMap ToColumn(string columnName) - { - ColumnName = columnName; - return this; - } - - /// - /// Marks the current property as ignored, resulting in the property not being mapped by Dapper. - /// - /// The current instance. This enables a fluent API. - public PropertyMap Ignore() - { - Ignored = true; - return this; - } - } -} diff --git a/src/Dapper.FluentMap/TypeMaps/FluentConventionTypeMap.cs b/src/Dapper.FluentMap/TypeMaps/FluentConventionTypeMap.cs new file mode 100644 index 0000000..073e27a --- /dev/null +++ b/src/Dapper.FluentMap/TypeMaps/FluentConventionTypeMap.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Dapper.FluentMap.Conventions; + +namespace Dapper.FluentMap.TypeMaps +{ + /// + /// Represents a Dapper type mapping strategy which first tries to map the type using a + /// with the configured conventions. is used as fallback mapping strategy. + /// + /// The type of the entity. + internal class FluentConventionTypeMap : MultiTypeMap + { + /// + /// Intializes a new instance of the class + /// which uses the and + /// as mapping strategies. + /// + public FluentConventionTypeMap() + : base(new CustomPropertyTypeMap(typeof (TEntity), GetPropertyInfo), new DefaultTypeMap(typeof (TEntity))) + { + } + + private static PropertyInfo GetPropertyInfo(Type type, string columnName) + { + string cacheKey = string.Format("{0};{1}", type.FullName, columnName); + + PropertyInfo info; + if (TypePropertyMapCache.TryGetValue(cacheKey, out info)) + { + return info; + } + + IList conventions; + if (FluentMapper.TypeConventions.TryGetValue(type, out conventions)) + { + foreach (var convention in conventions) + { + var maps = convention.PropertyMaps.Where(c => c.ColumnName == columnName).ToList(); + + if (maps.Count > 1) + { + const string msg = "Finding mappings for column '{0}' yielded more than 1 PropertyMap. The conventions should be more specific. Type: '{1}'. Convention: '{2}'."; + throw new Exception(string.Format(msg, columnName, type, convention)); + } + + if (maps.Count == 0) + { + return null; + } + + info = maps[0].PropertyInfo; + TypePropertyMapCache.Add(cacheKey, info); + return info; + } + } + + // If we get here, the property was not mapped. + TypePropertyMapCache.Add(cacheKey, null); + return null; + } + } +} diff --git a/src/Dapper.FluentMap/FluentMapTypeMap.cs b/src/Dapper.FluentMap/TypeMaps/FluentTypeMap.cs similarity index 84% rename from src/Dapper.FluentMap/FluentMapTypeMap.cs rename to src/Dapper.FluentMap/TypeMaps/FluentTypeMap.cs index 223b8d0..5623710 100644 --- a/src/Dapper.FluentMap/FluentMapTypeMap.cs +++ b/src/Dapper.FluentMap/TypeMaps/FluentTypeMap.cs @@ -1,15 +1,16 @@ using System; using System.Linq; using System.Reflection; +using Dapper.FluentMap.Mapping; -namespace Dapper.FluentMap +namespace Dapper.FluentMap.TypeMaps { /// /// Represents a Dapper type mapping strategy which first tries to map the type using a , /// if that fails, the is used as mapping strategy. /// /// The type of the entity. - public class FluentMapTypeMap : MultiTypeMap + internal class FluentMapTypeMap : MultiTypeMap { /// /// Intializes a new instance of the class @@ -26,7 +27,7 @@ private static PropertyInfo GetPropertyInfo(Type type, string columnName) string cacheKey = string.Format("{0};{1}", type.FullName, columnName); PropertyInfo info; - if (_typePropertyMapCache.TryGetValue(cacheKey, out info)) + if (TypePropertyMapCache.TryGetValue(cacheKey, out info)) { return info; } @@ -43,14 +44,14 @@ private static PropertyInfo GetPropertyInfo(Type type, string columnName) { if (!propertyMap.Ignored) { - _typePropertyMapCache.Add(cacheKey, propertyMap.PropertyInfo); + TypePropertyMapCache.Add(cacheKey, propertyMap.PropertyInfo); return propertyMap.PropertyInfo; } } } // If we get here, the property was not mapped. - _typePropertyMapCache.Add(cacheKey, null); + TypePropertyMapCache.Add(cacheKey, null); return null; } } diff --git a/src/Dapper.FluentMap/MultiTypeMap.cs b/src/Dapper.FluentMap/TypeMaps/MultiTypeMap.cs similarity index 82% rename from src/Dapper.FluentMap/MultiTypeMap.cs rename to src/Dapper.FluentMap/TypeMaps/MultiTypeMap.cs index bc589ef..424e1df 100644 --- a/src/Dapper.FluentMap/MultiTypeMap.cs +++ b/src/Dapper.FluentMap/TypeMaps/MultiTypeMap.cs @@ -2,18 +2,19 @@ using System.Collections.Generic; using System.Reflection; -namespace Dapper.FluentMap +namespace Dapper.FluentMap.TypeMaps { /// /// Represents a Dapper type mapping strategy which consists of multiple strategies. /// - public abstract class MultiTypeMap : SqlMapper.ITypeMap + internal abstract class MultiTypeMap : SqlMapper.ITypeMap { - protected static readonly Dictionary _typePropertyMapCache = new Dictionary(); private readonly IEnumerable _mappers; + private static readonly Dictionary _typePropertyMapCache = new Dictionary(); /// - /// Initializes an instance of the class with the specified Dapper type mappers. + /// Initializes an instance of the + /// class with the specified Dapper type mappers. /// /// The type mapping strategies to be used when mapping. protected MultiTypeMap(params SqlMapper.ITypeMap[] mappers) @@ -88,5 +89,13 @@ public SqlMapper.IMemberMap GetMember(string columnName) } return null; } + + protected static Dictionary TypePropertyMapCache + { + get + { + return _typePropertyMapCache; + } + } } } diff --git a/src/Dapper.FluentMap/Utils/DictionaryExtensions.cs b/src/Dapper.FluentMap/Utils/DictionaryExtensions.cs new file mode 100644 index 0000000..37086f4 --- /dev/null +++ b/src/Dapper.FluentMap/Utils/DictionaryExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Dapper.FluentMap.Utils +{ + internal static class DictionaryExtensions + { + internal static void AddOrUpdate(this IDictionary> dict, TKey key, TValue value) + { + if (dict.ContainsKey(key)) + { + dict[key].Add(value); + } + else + { + dict.Add(key, new[] { value }); + } + } + } +} diff --git a/src/Dapper.FluentMap/ReflectionHelper.cs b/src/Dapper.FluentMap/Utils/ReflectionHelper.cs similarity index 82% rename from src/Dapper.FluentMap/ReflectionHelper.cs rename to src/Dapper.FluentMap/Utils/ReflectionHelper.cs index e24d796..e627519 100644 --- a/src/Dapper.FluentMap/ReflectionHelper.cs +++ b/src/Dapper.FluentMap/Utils/ReflectionHelper.cs @@ -1,19 +1,19 @@ using System.Linq.Expressions; using System.Reflection; -namespace Dapper.FluentMap +namespace Dapper.FluentMap.Utils { /// /// Provides helper methods for reflection operations. /// - public static class ReflectionHelper + internal static class ReflectionHelper { /// - /// Returns the MemberInfo for the specified lamba expression. + /// Returns the for the specified lamba expression. /// /// A lamba expression containing a MemberExpression. /// A MemberInfo object for the member in the specified lambda expression. - public static MemberInfo GetMemberInfo(LambdaExpression lambda) + internal static MemberInfo GetMemberInfo(LambdaExpression lambda) { Expression expr = lambda; while (true)