From 7a21ce85f51ad8e5f2b90d1bd1dbe571af849293 Mon Sep 17 00:00:00 2001 From: ToddThomson Date: Wed, 22 Aug 2018 17:58:13 -0700 Subject: [PATCH] progress towards lazy loading. --- Achilles.Entities.Sqlite/DataContext.cs | 8 +- ...tyCollection.cs => EntityCollectionOfT.cs} | 13 +- ...tityReference.cs => EntityReferenceOfT.cs} | 5 + Achilles.Entities.Sqlite/EntitySet.cs | 7 +- Achilles.Entities.Sqlite/IEntityCollection.cs | 5 +- .../IEntityCollectionOfT.cs | 23 + Achilles.Entities.Sqlite/IEntityReference.cs | 3 + Achilles.Entities.Sqlite/IEntitySet.cs | 5 +- .../Linq/EntityQueryExecutor.cs | 2 +- .../Modelling/EntityModel.cs | 24 +- .../Modelling/EntityModelBuilder.cs | 4 +- .../Modelling/Mapping/AutoMapper/Cache.cs | 199 +- .../Mapping/AutoMapper/Configuration.cs | 567 ++--- .../Modelling/Mapping/AutoMapper/Helpers.cs | 1850 +++++++++-------- .../Modelling/Mapping/AutoMapper/Mapper.cs | 395 ++-- .../Mapping/Builders/EntityMappingBuilder.cs | 31 +- .../Mapping/Builders/HasManyMappingBuilder.cs | 57 +- .../Mapping/Builders/HasOneMappingBuilder.cs | 48 +- .../Mapping/Builders/IHasOneMappingBuilder.cs | 16 +- .../Modelling/Mapping/EntityMapping.cs | 2 + .../Modelling/Mapping/IEntityMapping.cs | 2 + .../Modelling/Mapping/IForeignKeyMapping.cs | 15 - .../Modelling/Mapping/IRelationshipMapping.cs | 36 + .../Modelling/Mapping/RelationshipMapping.cs | 34 + .../Querying/EntityMaterializer.cs | 108 +- .../Querying/EntityTypeMap.cs | 6 +- .../Storage/IRelationalConnection.cs | 2 +- 27 files changed, 1819 insertions(+), 1648 deletions(-) rename Achilles.Entities.Sqlite/{EntityCollection.cs => EntityCollectionOfT.cs} (86%) rename Achilles.Entities.Sqlite/{EntityReference.cs => EntityReferenceOfT.cs} (91%) create mode 100644 Achilles.Entities.Sqlite/IEntityCollectionOfT.cs create mode 100644 Achilles.Entities.Sqlite/Modelling/Mapping/IRelationshipMapping.cs create mode 100644 Achilles.Entities.Sqlite/Modelling/Mapping/RelationshipMapping.cs diff --git a/Achilles.Entities.Sqlite/DataContext.cs b/Achilles.Entities.Sqlite/DataContext.cs index d6dab94..0c0465f 100644 --- a/Achilles.Entities.Sqlite/DataContext.cs +++ b/Achilles.Entities.Sqlite/DataContext.cs @@ -42,7 +42,7 @@ public class DataContext : IDisposable private IDataContextService _contextService = null; private IRelationalCommandBuilder _commandBuilder = null; - private List _entitySets = new List(); + private Dictionary _entitySets = new Dictionary(); private bool _isConfiguring = false; private bool _isModelBuilding = false; @@ -325,12 +325,12 @@ private void InitializeServices( DataContextOptions options ) #region Internal Methods - internal void AddEntitySet( IEntitySet entitySet ) + internal void AddEntitySet( EntitySet entitySet ) where TEntity : class { - _entitySets.Add( entitySet ); + _entitySets.Add( typeof(TEntity), entitySet ); } - internal List EntitySets => _entitySets; + internal Dictionary EntitySets => _entitySets; #endregion diff --git a/Achilles.Entities.Sqlite/EntityCollection.cs b/Achilles.Entities.Sqlite/EntityCollectionOfT.cs similarity index 86% rename from Achilles.Entities.Sqlite/EntityCollection.cs rename to Achilles.Entities.Sqlite/EntityCollectionOfT.cs index bebd7cc..dfd7ea8 100644 --- a/Achilles.Entities.Sqlite/EntityCollection.cs +++ b/Achilles.Entities.Sqlite/EntityCollectionOfT.cs @@ -19,7 +19,7 @@ namespace Achilles.Entities { - public sealed class EntityCollection : ICollection, IListSource, IEntityCollection + public sealed class EntityCollection : IEntityCollection, IEntityCollection, ICollection, IListSource where TEntity : class { #region Private Fields @@ -40,6 +40,15 @@ public EntityCollection() #endregion + #region IEntityCollection Implementation + + void IEntityCollection.AttachSource( IEnumerable source ) + { + _source = source; + } + + #endregion + #region ICollection Implementation public int Count => ((ICollection)_entities).Count; @@ -92,6 +101,8 @@ IList IListSource.GetList() throw new NotImplementedException(); } + + #endregion } } diff --git a/Achilles.Entities.Sqlite/EntityReference.cs b/Achilles.Entities.Sqlite/EntityReferenceOfT.cs similarity index 91% rename from Achilles.Entities.Sqlite/EntityReference.cs rename to Achilles.Entities.Sqlite/EntityReferenceOfT.cs index 6a29aac..3c168c4 100644 --- a/Achilles.Entities.Sqlite/EntityReference.cs +++ b/Achilles.Entities.Sqlite/EntityReferenceOfT.cs @@ -53,6 +53,11 @@ public TEntity Entity } } + public void AttachSource( IEnumerable source ) + { + _source = source; + } + #endregion #region Private Methods diff --git a/Achilles.Entities.Sqlite/EntitySet.cs b/Achilles.Entities.Sqlite/EntitySet.cs index 106282a..a7a1af1 100644 --- a/Achilles.Entities.Sqlite/EntitySet.cs +++ b/Achilles.Entities.Sqlite/EntitySet.cs @@ -66,7 +66,7 @@ public EntitySet( DataContext context ) #region Public CRUD Methods - // TODO: Add documented user interface + // TODO: Add documented user interface on IRepository public int Add( TEntity entity ) => _context.Add( entity ); @@ -113,6 +113,11 @@ private EntityQueryable EntityQueryable IEnumerator IEnumerable.GetEnumerator() => EntityQueryable.GetEnumerator(); + public IEnumerable GetSource( TSource t ) where TSource : class + { + return (IEnumerable)EntityQueryable.Cast(); + } + #endregion } } diff --git a/Achilles.Entities.Sqlite/IEntityCollection.cs b/Achilles.Entities.Sqlite/IEntityCollection.cs index 5ff57b2..76de6f0 100644 --- a/Achilles.Entities.Sqlite/IEntityCollection.cs +++ b/Achilles.Entities.Sqlite/IEntityCollection.cs @@ -10,7 +10,10 @@ namespace Achilles.Entities { - public interface IEntityCollection + /// + /// Marker interface + /// + internal interface IEntityCollection { } } diff --git a/Achilles.Entities.Sqlite/IEntityCollectionOfT.cs b/Achilles.Entities.Sqlite/IEntityCollectionOfT.cs new file mode 100644 index 0000000..7c84c47 --- /dev/null +++ b/Achilles.Entities.Sqlite/IEntityCollectionOfT.cs @@ -0,0 +1,23 @@ +#region Copyright Notice + +// Copyright (c) by Achilles Software, All rights reserved. +// +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// Send questions regarding this copyright notice to: mailto:todd.thomson@achilles-software.com + +#endregion + +#region Namespaces + +using System.Collections.Generic; + +#endregion + +namespace Achilles.Entities +{ + internal interface IEntityCollection + { + void AttachSource( IEnumerable source ); + } +} diff --git a/Achilles.Entities.Sqlite/IEntityReference.cs b/Achilles.Entities.Sqlite/IEntityReference.cs index e890424..0633d49 100644 --- a/Achilles.Entities.Sqlite/IEntityReference.cs +++ b/Achilles.Entities.Sqlite/IEntityReference.cs @@ -8,6 +8,8 @@ #endregion +using System.Collections.Generic; + namespace Achilles.Entities { /// @@ -15,5 +17,6 @@ namespace Achilles.Entities /// internal interface IEntityReference { + //void AttachSource( IEnumerable source ) where TEntityRef: class; } } diff --git a/Achilles.Entities.Sqlite/IEntitySet.cs b/Achilles.Entities.Sqlite/IEntitySet.cs index af5a1f7..b7e6f45 100644 --- a/Achilles.Entities.Sqlite/IEntitySet.cs +++ b/Achilles.Entities.Sqlite/IEntitySet.cs @@ -11,6 +11,7 @@ #region Namespaces using System; +using System.Collections.Generic; #endregion @@ -20,12 +21,14 @@ namespace Achilles.Entities /// Internal API for /// /// An internal interface for now. - internal interface IEntitySet + public interface IEntitySet { /// /// Returns the entity set generic type. /// /// Type EntityType { get; } + + IEnumerable GetSource( TSource source ) where TSource : class; } } diff --git a/Achilles.Entities.Sqlite/Linq/EntityQueryExecutor.cs b/Achilles.Entities.Sqlite/Linq/EntityQueryExecutor.cs index 8976ab9..ce700b5 100644 --- a/Achilles.Entities.Sqlite/Linq/EntityQueryExecutor.cs +++ b/Achilles.Entities.Sqlite/Linq/EntityQueryExecutor.cs @@ -32,7 +32,7 @@ public EntityQueryExecutor( DataContext context ) _context = context; _connection = context.Database.Connection; //_transaction = transaction; - _materializer = new EntityMaterializer( context.Model ); + _materializer = new EntityMaterializer( context ); } #endregion diff --git a/Achilles.Entities.Sqlite/Modelling/EntityModel.cs b/Achilles.Entities.Sqlite/Modelling/EntityModel.cs index df8f91d..dccf1d5 100644 --- a/Achilles.Entities.Sqlite/Modelling/EntityModel.cs +++ b/Achilles.Entities.Sqlite/Modelling/EntityModel.cs @@ -39,22 +39,22 @@ public EntityModel( EntityMappingCollection entityMappings ) public IReadOnlyCollection EntityMappings => _entityMappings.Values as IReadOnlyCollection; - public IEntityMapping GetOrAddEntityMapping( Type entityType ) - { - IEntityMapping mapping; + //public IEntityMapping GetOrAddEntityMapping( Type entityType ) + //{ + // IEntityMapping mapping; - if ( !_entityMappings.TryGetValue( entityType, out mapping ) ) - { - var EntityMappingType = typeof( EntityMapping<> ); - var mapType = EntityMappingType.MakeGenericType( entityType ); + // if ( !_entityMappings.TryGetValue( entityType, out mapping ) ) + // { + // var EntityMappingType = typeof( EntityMapping<> ); + // var mapType = EntityMappingType.MakeGenericType( entityType ); - mapping = Activator.CreateInstance( mapType ) as IEntityMapping; + // mapping = Activator.CreateInstance( mapType ) as IEntityMapping; - _entityMappings[ entityType ] = mapping; - } + // _entityMappings[ entityType ] = mapping; + // } - return mapping; - } + // return mapping; + //} public IEntityMapping GetEntityMapping() where TEntity : class { diff --git a/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs b/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs index 7d77d7e..365abb0 100644 --- a/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs +++ b/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs @@ -61,9 +61,9 @@ public IEntityModel Build( DataContext context ) // What should happen if an entity is referenced during model building that is // not in the context? - foreach ( var entitySet in context.EntitySets ) + foreach ( var entitySetType in context.EntitySets.Keys ) { - EntityMappings.GetOrAddEntityMapping( entitySet.EntityType ); + EntityMappings.GetOrAddEntityMapping( entitySetType ); } context.OnModelBuilding( this ); diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Cache.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Cache.cs index ca3af22..ffbf6db 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Cache.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Cache.cs @@ -33,102 +33,105 @@ dynamic data into static types and populate complex nested child objects. namespace Achilles.Entities.Mapping { - public static partial class AutoMapper - { - #region Cache - - /// - /// Contains the methods and members responsible for this libraries caching concerns. - /// - public static class Cache - { - /// - /// The name of the instance cache stored in the logical call context. - /// - //internal const string InstanceCacheContextStorageKey = "Slapper.AutoMapper.InstanceCache"; - - /// - /// Cache of TypeMaps containing the types identifiers and PropertyInfo/FieldInfo objects. - /// - internal static readonly ConcurrentDictionary TypeMapCache = new ConcurrentDictionary(); - - private static Dictionary _instanceCache; - - /// - /// A TypeMap holds data relevant for a particular Type. - /// - internal class TypeMap - { - /// - /// Creates a new . - /// - /// Type to map. - /// The s identifiers. - /// The s properties and fields. - public TypeMap( Type type, IEnumerable identifiers, Dictionary propertiesAndFields ) - { - Type = type; - Identifiers = identifiers; - PropertiesAndFieldsInfo = propertiesAndFields; - } - - /// - /// Type for this TypeMap - /// - public readonly Type Type; - - /// - /// List of identifiers - /// - public IEnumerable Identifiers; - - /// - /// Property/field names and their corresponding PropertyInfo/FieldInfo objects - /// - public Dictionary PropertiesAndFieldsInfo; - } - - /// - /// Clears all internal caches. - /// - public static void ClearAllCaches() - { - TypeMapCache.Clear(); - ClearInstanceCache(); - } - - /// - /// Clears the instance cache. This cache contains all objects created by Slapper.AutoMapper. - /// - public static void ClearInstanceCache() - { - // TJT: FIXME - no instance thread cache. - - //InternalHelpers.ContextStorage.Remove( InstanceCacheContextStorageKey ); - } - - /// - /// Gets the instance cache containing all objects created by Slapper.AutoMapper. - /// This cache exists for the lifetime of the current thread until manually cleared/purged. - /// - /// - /// Due to the nature of how the cache is persisted, each new thread will recieve it's own - /// unique cache. - /// - /// Instance Cache - internal static Dictionary GetInstanceCache() - { - if ( _instanceCache == null ) - { - _instanceCache = new Dictionary(); - - //InternalHelpers.ContextStorage.Store( InstanceCacheContextStorageKey, instanceCache ); - } - - return _instanceCache; - } - } - - #endregion Cache - } + // TJT: Customized Automapper is now in /Querying/EntityMaterializer.cs + // Remove after beta. + + //public static partial class AutoMapper + //{ + // #region Cache + + // /// + // /// Contains the methods and members responsible for this libraries caching concerns. + // /// + // public static class Cache + // { + // /// + // /// The name of the instance cache stored in the logical call context. + // /// + // //internal const string InstanceCacheContextStorageKey = "Slapper.AutoMapper.InstanceCache"; + + // /// + // /// Cache of TypeMaps containing the types identifiers and PropertyInfo/FieldInfo objects. + // /// + // internal static readonly ConcurrentDictionary TypeMapCache = new ConcurrentDictionary(); + + // private static Dictionary _instanceCache; + + // /// + // /// A TypeMap holds data relevant for a particular Type. + // /// + // internal class TypeMap + // { + // /// + // /// Creates a new . + // /// + // /// Type to map. + // /// The s identifiers. + // /// The s properties and fields. + // public TypeMap( Type type, IEnumerable identifiers, Dictionary propertiesAndFields ) + // { + // Type = type; + // Identifiers = identifiers; + // PropertiesAndFieldsInfo = propertiesAndFields; + // } + + // /// + // /// Type for this TypeMap + // /// + // public readonly Type Type; + + // /// + // /// List of identifiers + // /// + // public IEnumerable Identifiers; + + // /// + // /// Property/field names and their corresponding PropertyInfo/FieldInfo objects + // /// + // public Dictionary PropertiesAndFieldsInfo; + // } + + // /// + // /// Clears all internal caches. + // /// + // public static void ClearAllCaches() + // { + // TypeMapCache.Clear(); + // ClearInstanceCache(); + // } + + // /// + // /// Clears the instance cache. This cache contains all objects created by Slapper.AutoMapper. + // /// + // public static void ClearInstanceCache() + // { + // // TJT: FIXME - no instance thread cache. + + // //InternalHelpers.ContextStorage.Remove( InstanceCacheContextStorageKey ); + // } + + // /// + // /// Gets the instance cache containing all objects created by Slapper.AutoMapper. + // /// This cache exists for the lifetime of the current thread until manually cleared/purged. + // /// + // /// + // /// Due to the nature of how the cache is persisted, each new thread will recieve it's own + // /// unique cache. + // /// + // /// Instance Cache + // internal static Dictionary GetInstanceCache() + // { + // if ( _instanceCache == null ) + // { + // _instanceCache = new Dictionary(); + + // //InternalHelpers.ContextStorage.Store( InstanceCacheContextStorageKey, instanceCache ); + // } + + // return _instanceCache; + // } + // } + + // #endregion Cache + //} } \ No newline at end of file diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Configuration.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Configuration.cs index a5cc061..91c6ff9 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Configuration.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Configuration.cs @@ -30,286 +30,289 @@ dynamic data into static types and populate complex nested child objects. namespace Achilles.Entities.Mapping { - public static partial class AutoMapper - { - #region Configuration - - /// - /// Contains the methods and members responsible for this libraries configuration concerns. - /// - public static class Configuration - { - static Configuration() - { - IdentifierAttributeType = typeof( Id ); - - ApplyDefaultIdentifierConventions(); - ApplyDefaultTypeConverters(); - } - - /// - /// Current version of Slapper.AutoMapper. - /// - public static readonly Version Version = new Version( "1.0.0.6" ); - - /// - /// The attribute Type specifying that a field or property is an identifier. - /// - public static Type IdentifierAttributeType; - - /// - /// Convention for finding an identifier. - /// - /// - /// - public delegate string ApplyIdentifierConvention( Type type ); - - /// - /// Conventions for finding an identifier. - /// - public static readonly List IdentifierConventions = new List(); - - /// - /// Type converters used to convert values from one type to another. - /// - public static readonly List TypeConverters = new List(); - - /// - /// Activators to instantiate types. - /// - public static readonly List TypeActivators = new List(); - - /// - /// Applies default conventions for finding identifiers. - /// - public static void ApplyDefaultIdentifierConventions() - { - IdentifierConventions.Add( type => "Id" ); - IdentifierConventions.Add( type => type.Name + "Id" ); - IdentifierConventions.Add( type => type.Name + "Nbr" ); - } - - /// - /// Applies the default ITypeConverters for converting values to different types. - /// - public static void ApplyDefaultTypeConverters() - { - TypeConverters.Add( new GuidConverter() ); - TypeConverters.Add( new EnumConverter() ); - TypeConverters.Add( new ValueTypeConverter() ); - } - - /// - /// Adds an identifier for the specified type. - /// Replaces any identifiers previously specified. - /// - /// Type - /// Identifier - public static void AddIdentifier( Type type, string identifier ) - { - AddIdentifiers( type, new List { identifier } ); - } - - /// - /// Adds identifiers for the specified type. - /// Replaces any identifiers previously specified. - /// - /// Type - /// Identifiers - public static void AddIdentifiers( Type type, IEnumerable identifiers ) - { - var typeMap = Cache.TypeMapCache.GetOrAdd( type, InternalHelpers.CreateTypeMap( type ) ); - - typeMap.Identifiers = identifiers; - } - - /// - /// Defines methods that can convert values from one type to another. - /// - public interface ITypeConverter - { - /// - /// Converts the given value to the requested type. - /// - /// Value to convert. - /// Type the value is to be converted to. - /// Converted value. - object Convert( object value, Type type ); - - /// - /// Indicates whether it can convert the given value to the requested type. - /// - /// Value to convert. - /// Type the value needs to be converted to. - /// Boolean response. - bool CanConvert( object value, Type type ); - - /// - /// Order to execute an in. - /// - int Order { get; } - } - - /// - /// Converts values to Guids. - /// - public class GuidConverter : ITypeConverter - { - #region Implementation of ITypeConverter - - /// - /// Converts the given value to the requested type. - /// - /// Value to convert. - /// Type the value is to be converted to. - /// Converted value. - public object Convert( object value, Type type ) - { - object convertedValue = null; - - if ( value is string ) - { - convertedValue = new Guid( value as string ); - } - if ( value is byte[] ) - { - convertedValue = new Guid( value as byte[] ); - } - - return convertedValue; - } - - /// - /// Indicates whether it can convert the given value to the requested type. - /// - /// Value to convert. - /// Type the value needs to be converted to. - /// Boolean response. - public bool CanConvert( object value, Type type ) - { - var conversionType = Nullable.GetUnderlyingType( type ) ?? type; - return conversionType == typeof( Guid ); - } - - /// - /// Order to execute an in. - /// - public int Order { get { return 100; } } - - #endregion - } - - /// - /// Converts values to Enums. - /// - public class EnumConverter : ITypeConverter - { - #region Implementation of ITypeConverter - - /// - /// Converts the given value to the requested type. - /// - /// Value to convert. - /// Type the value is to be converted to. - /// Converted value. - public object Convert( object value, Type type ) - { - // Handle Nullable types - var conversionType = Nullable.GetUnderlyingType( type ) ?? type; - - object convertedValue = Enum.Parse( conversionType, value.ToString() ); - - return convertedValue; - } - - /// - /// Indicates whether it can convert the given value to the requested type. - /// - /// Value to convert. - /// Type the value needs to be converted to. - /// Boolean response. - public bool CanConvert( object value, Type type ) - { - var conversionType = Nullable.GetUnderlyingType( type ) ?? type; - return conversionType.IsEnum; - } - - /// - /// Order to execute an in. - /// - public int Order { get { return 100; } } - - #endregion - } - - /// - /// Converts values types. - /// - public class ValueTypeConverter : ITypeConverter - { - #region Implementation of ITypeConverter - - /// - /// Converts the given value to the requested type. - /// - /// Value to convert. - /// Type the value is to be converted to. - /// Converted value. - public object Convert( object value, Type type ) - { - // Handle Nullable types - var conversionType = Nullable.GetUnderlyingType( type ) ?? type; - - var convertedValue = System.Convert.ChangeType( value, conversionType ); - - return convertedValue; - } - - /// - /// Indicates whether it can convert the given value to the requested type. - /// - /// Value to convert. - /// Type the value needs to be converted to. - /// Boolean response. - public bool CanConvert( object value, Type type ) - { - return type.IsValueType && !type.IsEnum && type != typeof( Guid ); - } - - /// - /// Order to execute an in. - /// - public int Order { get { return 1000; } } - - #endregion - } - - /// - /// Defines an interface for an activator for a specific type. - /// - public interface ITypeActivator - { - /// - /// Creates the type. - /// - /// The type to create. - /// The created type. - object Create( Type type ); - - /// - /// Indicates whether it can create the type. - /// - /// The type to create. - /// Boolean response. - bool CanCreate( Type type ); - - /// - /// The order to try the activator in. - /// - int Order { get; } - } - } - - #endregion Configuration - } + // TJT: Customized Automapper is now in /Querying/EntityMaterializer.cs + // Remove after beta. + + //public static partial class AutoMapper + //{ + // #region Configuration + + // /// + // /// Contains the methods and members responsible for this libraries configuration concerns. + // /// + // public static class Configuration + // { + // static Configuration() + // { + // IdentifierAttributeType = typeof( Id ); + + // ApplyDefaultIdentifierConventions(); + // ApplyDefaultTypeConverters(); + // } + + // /// + // /// Current version of Slapper.AutoMapper. + // /// + // public static readonly Version Version = new Version( "1.0.0.6" ); + + // /// + // /// The attribute Type specifying that a field or property is an identifier. + // /// + // public static Type IdentifierAttributeType; + + // /// + // /// Convention for finding an identifier. + // /// + // /// + // /// + // public delegate string ApplyIdentifierConvention( Type type ); + + // /// + // /// Conventions for finding an identifier. + // /// + // public static readonly List IdentifierConventions = new List(); + + // /// + // /// Type converters used to convert values from one type to another. + // /// + // public static readonly List TypeConverters = new List(); + + // /// + // /// Activators to instantiate types. + // /// + // public static readonly List TypeActivators = new List(); + + // /// + // /// Applies default conventions for finding identifiers. + // /// + // public static void ApplyDefaultIdentifierConventions() + // { + // IdentifierConventions.Add( type => "Id" ); + // IdentifierConventions.Add( type => type.Name + "Id" ); + // IdentifierConventions.Add( type => type.Name + "Nbr" ); + // } + + // /// + // /// Applies the default ITypeConverters for converting values to different types. + // /// + // public static void ApplyDefaultTypeConverters() + // { + // TypeConverters.Add( new GuidConverter() ); + // TypeConverters.Add( new EnumConverter() ); + // TypeConverters.Add( new ValueTypeConverter() ); + // } + + // /// + // /// Adds an identifier for the specified type. + // /// Replaces any identifiers previously specified. + // /// + // /// Type + // /// Identifier + // public static void AddIdentifier( Type type, string identifier ) + // { + // AddIdentifiers( type, new List { identifier } ); + // } + + // /// + // /// Adds identifiers for the specified type. + // /// Replaces any identifiers previously specified. + // /// + // /// Type + // /// Identifiers + // public static void AddIdentifiers( Type type, IEnumerable identifiers ) + // { + // var typeMap = Cache.TypeMapCache.GetOrAdd( type, InternalHelpers.CreateTypeMap( type ) ); + + // typeMap.Identifiers = identifiers; + // } + + // /// + // /// Defines methods that can convert values from one type to another. + // /// + // public interface ITypeConverter + // { + // /// + // /// Converts the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value is to be converted to. + // /// Converted value. + // object Convert( object value, Type type ); + + // /// + // /// Indicates whether it can convert the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value needs to be converted to. + // /// Boolean response. + // bool CanConvert( object value, Type type ); + + // /// + // /// Order to execute an in. + // /// + // int Order { get; } + // } + + // /// + // /// Converts values to Guids. + // /// + // public class GuidConverter : ITypeConverter + // { + // #region Implementation of ITypeConverter + + // /// + // /// Converts the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value is to be converted to. + // /// Converted value. + // public object Convert( object value, Type type ) + // { + // object convertedValue = null; + + // if ( value is string ) + // { + // convertedValue = new Guid( value as string ); + // } + // if ( value is byte[] ) + // { + // convertedValue = new Guid( value as byte[] ); + // } + + // return convertedValue; + // } + + // /// + // /// Indicates whether it can convert the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value needs to be converted to. + // /// Boolean response. + // public bool CanConvert( object value, Type type ) + // { + // var conversionType = Nullable.GetUnderlyingType( type ) ?? type; + // return conversionType == typeof( Guid ); + // } + + // /// + // /// Order to execute an in. + // /// + // public int Order { get { return 100; } } + + // #endregion + // } + + // /// + // /// Converts values to Enums. + // /// + // public class EnumConverter : ITypeConverter + // { + // #region Implementation of ITypeConverter + + // /// + // /// Converts the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value is to be converted to. + // /// Converted value. + // public object Convert( object value, Type type ) + // { + // // Handle Nullable types + // var conversionType = Nullable.GetUnderlyingType( type ) ?? type; + + // object convertedValue = Enum.Parse( conversionType, value.ToString() ); + + // return convertedValue; + // } + + // /// + // /// Indicates whether it can convert the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value needs to be converted to. + // /// Boolean response. + // public bool CanConvert( object value, Type type ) + // { + // var conversionType = Nullable.GetUnderlyingType( type ) ?? type; + // return conversionType.IsEnum; + // } + + // /// + // /// Order to execute an in. + // /// + // public int Order { get { return 100; } } + + // #endregion + // } + + // /// + // /// Converts values types. + // /// + // public class ValueTypeConverter : ITypeConverter + // { + // #region Implementation of ITypeConverter + + // /// + // /// Converts the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value is to be converted to. + // /// Converted value. + // public object Convert( object value, Type type ) + // { + // // Handle Nullable types + // var conversionType = Nullable.GetUnderlyingType( type ) ?? type; + + // var convertedValue = System.Convert.ChangeType( value, conversionType ); + + // return convertedValue; + // } + + // /// + // /// Indicates whether it can convert the given value to the requested type. + // /// + // /// Value to convert. + // /// Type the value needs to be converted to. + // /// Boolean response. + // public bool CanConvert( object value, Type type ) + // { + // return type.IsValueType && !type.IsEnum && type != typeof( Guid ); + // } + + // /// + // /// Order to execute an in. + // /// + // public int Order { get { return 1000; } } + + // #endregion + // } + + // /// + // /// Defines an interface for an activator for a specific type. + // /// + // public interface ITypeActivator + // { + // /// + // /// Creates the type. + // /// + // /// The type to create. + // /// The created type. + // object Create( Type type ); + + // /// + // /// Indicates whether it can create the type. + // /// + // /// The type to create. + // /// Boolean response. + // bool CanCreate( Type type ); + + // /// + // /// The order to try the activator in. + // /// + // int Order { get; } + // } + // } + + // #endregion Configuration + //} } \ No newline at end of file diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Helpers.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Helpers.cs index 94d1501..dcee7e0 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Helpers.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Helpers.cs @@ -34,928 +34,930 @@ dynamic data into static types and populate complex nested child objects. namespace Achilles.Entities.Mapping { - public static partial class AutoMapper - { - #region Internal Helpers - - /// - /// Contains the methods and members responsible for this libraries internal concerns. - /// - internal static class InternalHelpers - { - /// - /// Combine several hashcodes into a single new one. This implementation was grabbed from http://stackoverflow.com/a/34229665 where it is introduced - /// as MS implementation of GetHashCode() for strings. - /// - /// Hascodes to be combined. - /// A new Hascode value combining those passed as parameters. - private static int CombineHashCodes( params int[] hashCodes ) - { - int hash1 = (5381 << 16) + 5381; - int hash2 = hash1; - - int i = 0; - foreach ( var hashCode in hashCodes ) - { - if ( i % 2 == 0 ) - hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ hashCode; - else - hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ hashCode; - - ++i; - } - - return hash1 + (hash2 * 1566083941); - } - - /// - /// Defines the key for caching instances. Overrides Equality as to get unicity for a given set of identifiers values - /// for a given type. - /// - public struct InstanceKey : IEquatable - { - public bool Equals( InstanceKey other ) - { - return Equals( Type, other.Type ) - && Equals( ParentInstance, other.ParentInstance ) - && StructuralComparisons.StructuralEqualityComparer.Equals( IdentifierValues, other.IdentifierValues ); - } - - public override bool Equals( object obj ) - { - if ( ReferenceEquals( null, obj ) ) return false; - return obj is InstanceKey && Equals( (InstanceKey)obj ); - } - - public override int GetHashCode() - { - unchecked - { - return CombineHashCodes( Type?.GetHashCode() ?? 0, StructuralComparisons.StructuralEqualityComparer.GetHashCode( IdentifierValues ), ParentInstance?.GetHashCode() ?? 0 ); - } - } - - public static bool operator ==( InstanceKey left, InstanceKey right ) { return left.Equals( right ); } - - public static bool operator !=( InstanceKey left, InstanceKey right ) { return !left.Equals( right ); } - - public InstanceKey( Type type, object[] identifierValues, object parentInstance ) - { - Type = type; - IdentifierValues = identifierValues; - ParentInstance = parentInstance; - } - - public Type Type { get; } - public object[] IdentifierValues { get; } - public object ParentInstance { get; } - } - - /// - /// Gets the identifiers for the given type. Returns NULL if not found. - /// Results are cached for subsequent use and performance. - /// - /// - /// If no identifiers have been manually added, this method will attempt - /// to first find an attribute on the - /// and if not found will then try to match based upon any specified identifier conventions. - /// - /// Type - /// Identifier - public static IEnumerable GetIdentifiers( Type type ) - { - var typeMap = Cache.TypeMapCache.GetOrAdd( type, CreateTypeMap( type ) ); - - return typeMap.Identifiers.Any() ? typeMap.Identifiers : null; - } - - /// - /// Get a Dictionary of a type's property names and field names and their corresponding PropertyInfo or FieldInfo. - /// Results are cached for subsequent use and performance. - /// - /// Type - /// Dictionary of a type's property names and their corresponding PropertyInfo - public static Dictionary GetFieldsAndProperties( Type type ) - { - var typeMap = Cache.TypeMapCache.GetOrAdd( type, CreateTypeMap( type ) ); - - return typeMap.PropertiesAndFieldsInfo; - } - - /// - /// Creates an instance of the specified type using that type's default constructor. - /// - /// The type of object to create. - /// - /// A reference to the newly created object. - /// - public static object CreateInstance( Type type ) - { - if ( type == typeof( string ) ) - { - return string.Empty; - } - - if ( Configuration.TypeActivators.Count > 0 ) - { - foreach ( var typeActivator in Configuration.TypeActivators.OrderBy( ta => ta.Order ) ) - { - if ( typeActivator.CanCreate( type ) ) - { - return typeActivator.Create( type ); - } - } - } - - return Activator.CreateInstance( type ); - } - - /// - /// Creates a TypeMap for a given Type. - /// - /// Type - /// TypeMap - public static Cache.TypeMap CreateTypeMap( Type type ) - { - var conventionIdentifiers = Configuration.IdentifierConventions.Select( applyIdentifierConvention => applyIdentifierConvention( type ) ).ToList(); - - var fieldsAndProperties = CreateFieldAndPropertyInfoDictionary( type ); - - var identifiers = new List(); - - foreach ( var fieldOrProperty in fieldsAndProperties ) - { - var memberName = fieldOrProperty.Key; - - var member = fieldOrProperty.Value; - - var fieldInfo = member as FieldInfo; - - if ( fieldInfo != null ) - { - if ( fieldInfo.GetCustomAttributes( Configuration.IdentifierAttributeType, false ).Length > 0 ) - { - identifiers.Add( memberName ); - } - else if ( conventionIdentifiers.Exists( x => x.ToLower() == memberName.ToLower() ) ) - { - identifiers.Add( memberName ); - } - } - else - { - var propertyInfo = member as PropertyInfo; - - if ( propertyInfo != null ) - { - if ( propertyInfo.GetCustomAttributes( Configuration.IdentifierAttributeType, false ).Length > 0 ) - { - identifiers.Add( memberName ); - } - else if ( conventionIdentifiers.Exists( x => x.ToLower() == memberName.ToLower() ) ) - { - identifiers.Add( memberName ); - } - } - } - } - - var typeMap = new Cache.TypeMap( type, identifiers, fieldsAndProperties ); - - return typeMap; - } - - /// - /// Creates a Dictionary of field or property names and their corresponding FieldInfo or PropertyInfo objects - /// - /// Type - /// Dictionary of member names and member info objects - public static Dictionary CreateFieldAndPropertyInfoDictionary( Type type ) - { - var dictionary = new Dictionary(); - - var properties = type.GetProperties(); - - foreach ( var propertyInfo in properties ) - { - dictionary.Add( propertyInfo.Name, propertyInfo ); - } - - var fields = type.GetFields(); - - foreach ( var fieldInfo in fields ) - { - dictionary.Add( fieldInfo.Name, fieldInfo ); - } - - return dictionary; - } - - /// - /// Gets the Type of the Field or Property - /// - /// FieldInfo or PropertyInfo object - /// Type - public static Type GetMemberType( object member ) - { - Type type = null; - - var fieldInfo = member as FieldInfo; - - if ( fieldInfo != null ) - { - type = fieldInfo.FieldType; - } - else - { - var propertyInfo = member as PropertyInfo; - - if ( propertyInfo != null ) - { - type = propertyInfo.PropertyType; - } - } - - return type; - } - - /// - /// Sets the value on a Field or Property - /// - /// FieldInfo or PropertyInfo object - /// Object to set the value on - /// Value - public static void SetMemberValue( object member, object obj, object value ) - { - var fieldInfo = member as FieldInfo; - - if ( fieldInfo != null ) - { - value = ConvertValuesTypeToMembersType( value, fieldInfo.Name, fieldInfo.FieldType, fieldInfo.DeclaringType ); - - try - { - fieldInfo.SetValue( obj, value ); - } - catch ( Exception e ) - { - string errorMessage = - string.Format( "{0}: An error occurred while mapping the value '{1}' of type {2} to the member name '{3}' of type {4} on the {5} class.", - e.Message, value, value.GetType(), fieldInfo.Name, fieldInfo.FieldType, fieldInfo.DeclaringType ); - - throw new Exception( errorMessage, e ); - } - } - else - { - var propertyInfo = member as PropertyInfo; - - if ( propertyInfo != null ) - { - value = ConvertValuesTypeToMembersType( value, propertyInfo.Name, propertyInfo.PropertyType, propertyInfo.DeclaringType ); - - try - { - propertyInfo.SetValue( obj, value, null ); - } - catch ( Exception e ) - { - string errorMessage = - string.Format( "{0}: An error occurred while mapping the value '{1}' of type {2} to the member name '{3}' of type {4} on the {5} class.", - e.Message, value, value.GetType(), propertyInfo.Name, propertyInfo.PropertyType, propertyInfo.DeclaringType ); - - throw new Exception( errorMessage, e ); - } - } - } - } - - /// - /// Converts the values type to the members type if needed. - /// - /// Object value. - /// Member name. - /// Member type. - /// Declarying class type. - /// Value converted to the same type as the member type. - private static object ConvertValuesTypeToMembersType( object value, string memberName, Type memberType, Type classType ) - { - if ( value == null || value == DBNull.Value ) - return null; - - var valueType = value.GetType(); - - try - { - if ( valueType != memberType ) - { - foreach ( var typeConverter in Configuration.TypeConverters.OrderBy( x => x.Order ) ) - { - if ( typeConverter.CanConvert( value, memberType ) ) - { - var convertedValue = typeConverter.Convert( value, memberType ); - - return convertedValue; - } - } - } - } - catch ( Exception e ) - { - string errorMessage = string.Format( "{0}: An error occurred while mapping the value '{1}' of type {2} to the member name '{3}' of type {4} on the {5} class.", - e.Message, value, valueType, memberName, memberType, classType ); - - throw new Exception( errorMessage, e ); - } - - return value; - } - - /// - /// Gets the value of the member - /// - /// FieldInfo or PropertyInfo object - /// Object to get the value from - /// Value of the member - private static object GetMemberValue( object member, object obj ) - { - object value = null; - - var fieldInfo = member as FieldInfo; - - if ( fieldInfo != null ) - { - value = fieldInfo.GetValue( obj ); - } - else - { - var propertyInfo = member as PropertyInfo; - - if ( propertyInfo != null ) - { - value = propertyInfo.GetValue( obj, null ); - } - } - - return value; - } - - /// - /// Computes a key for storing and identifying an instance in the cache. - /// - /// Type of instance to get - /// List of properties and values - /// Parent instance. Can be NULL if this is the root instance. - /// - /// InstanceKey that will be unique for given set of identifiers values for the type. If the type isn't associated with any - /// identifier, the return value is made unique by generating a Guid. - /// ASSUMES GetIdentifiers(type) ALWAYS RETURN IDENTIFIERS IN THE SAME ORDER FOR A GIVEN TYPE. - /// This is certainly the case as long as GetIdentifiers caches its result for a given type (which it does by 2016-11-25). - /// - private static InstanceKey GetCacheKey( Type type, IDictionary properties, object parentInstance ) - { - var identifierValues = GetIdentifiers( type )?.Select( id => properties[ id ] ).DefaultIfEmpty( Guid.NewGuid() ).ToArray() - ?? new object[] { Guid.NewGuid() }; - - var key = new InstanceKey( type, identifierValues, parentInstance ); - return key; - } - - - /// - /// Gets a new or existing instance depending on whether an instance with the same identifiers already existing - /// in the instance cache. - /// - /// Type of instance to get - /// List of properties and values - /// Parent instance. Can be NULL if this is the root instance. - /// - /// Tuple of bool, object, int where bool represents whether this is a newly created instance, - /// object being an instance of the requested type and int being the instance's identifier hash. - /// - internal static Tuple GetInstance( Type type, IDictionary properties, object parentInstance = null ) - { - var key = GetCacheKey( type, properties, parentInstance ); - - var instanceCache = Cache.GetInstanceCache(); - - object instance; - - var isNewlyCreatedInstance = !instanceCache.TryGetValue( key, out instance ); - - if ( isNewlyCreatedInstance ) - { - instance = CreateInstance( type ); - instanceCache[ key ] = instance; - } - - return Tuple.Create( isNewlyCreatedInstance, instance, key ); - } - - /// - /// Populates the given instance's properties where the IDictionary key property names - /// match the type's property names case insensitively. - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Dictionary of property names and values - /// Instance to populate - /// Optional parent instance of the instance being populated - /// Populated instance - internal static object Map( IDictionary dictionary, object instance, object parentInstance = null ) - { - if ( instance.GetType().IsPrimitive || instance is string ) - { - object value; - if ( !dictionary.TryGetValue( "$", out value ) ) - { - throw new InvalidCastException( "For lists of primitive types, include $ as the name of the property" ); - } - - instance = value; - return instance; - } - - var fieldsAndProperties = GetFieldsAndProperties( instance.GetType() ); - - foreach ( var fieldOrProperty in fieldsAndProperties ) - { - var memberName = fieldOrProperty.Key.ToLower(); - - var member = fieldOrProperty.Value; - - object value; - - // Handle populating simple members on the current type - if ( dictionary.TryGetValue( memberName, out value ) ) - { - SetMemberValue( member, instance, value ); - } - else - { - Type memberType = GetMemberType( member ); - - // Handle populating complex members on the current type - if ( memberType.IsClass || memberType.IsInterface ) - { - // Try to find any keys that start with the current member name - var nestedDictionary = dictionary.Where( x => x.Key.ToLower().StartsWith( memberName + "_" ) ).ToList(); - - // If there weren't any keys - if ( !nestedDictionary.Any() ) - { - // And the parent instance was not null - if ( parentInstance != null ) - { - // And the parent instance is of the same type as the current member - if ( parentInstance.GetType() == memberType ) - { - // Then this must be a 'parent' to the current type - SetMemberValue( member, instance, parentInstance ); - } - } - - continue; - } - var regex = new Regex( Regex.Escape( memberName + "_" ) ); - var newDictionary = nestedDictionary.ToDictionary( pair => regex.Replace( pair.Key.ToLower(), string.Empty, 1 ), - pair => pair.Value, StringComparer.OrdinalIgnoreCase ); - - // Try to get the value of the complex member. If the member - // hasn't been initialized, then this will return null. - object nestedInstance = GetMemberValue( member, instance ); - - var genericCollectionType = typeof( IEnumerable<> ); - var isEnumerableType = memberType.IsGenericType && genericCollectionType.IsAssignableFrom( memberType.GetGenericTypeDefinition() ) - || memberType.GetInterfaces().Any( x => x.IsGenericType && x.GetGenericTypeDefinition() == genericCollectionType ); - - // If the member is null and is a class or interface (not ienumerable), try to create an instance of the type - if ( nestedInstance == null && (memberType.IsClass || (memberType.IsInterface && !isEnumerableType)) ) - { - if ( memberType.IsArray ) - { - nestedInstance = new ArrayList().ToArray( memberType.GetElementType() ); - } - else - { - nestedInstance = typeof( IEnumerable ).IsAssignableFrom( memberType ) - ? CreateInstance( memberType ) - : GetInstance( memberType, newDictionary, parentInstance == null ? 0 : parentInstance.GetHashCode() ).Item2; - } - } - - if ( isEnumerableType ) - { - var innerType = memberType.GetGenericArguments().FirstOrDefault() ?? memberType.GetElementType(); - nestedInstance = MapCollection( innerType, newDictionary, nestedInstance, instance ); - } - else - { - if ( newDictionary.Values.All( v => v == null ) ) - { - nestedInstance = null; - } - else - { - nestedInstance = Map( newDictionary, nestedInstance, instance ); - } - } - - SetMemberValue( member, instance, nestedInstance ); - } - } - } - - return instance; - } - - /// - /// Populates the given instance's properties where the IDictionary key property names - /// match the type's property names case insensitively. - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Underlying instance type - /// Dictionary of property names and values - /// Instance to populate - /// Optional parent instance of the instance being populated - /// Populated instance - internal static object MapCollection( Type type, IDictionary dictionary, object instance, object parentInstance = null ) - { - Type baseListType = typeof( List<> ); - Type collectionType = instance == null ? baseListType.MakeGenericType( type ) : instance.GetType(); - - if ( instance == null ) - { - instance = CreateInstance( collectionType ); - } - - // If the dictionnary only contains null values, we return an empty instance - if ( dictionary.Values.FirstOrDefault( v => v != null ) == null ) - { - return instance; - } - - var getInstanceResult = GetInstance( type, dictionary, parentInstance ); - - // Is this a newly created instance? If false, then this item was retrieved from the instance cache. - bool isNewlyCreatedInstance = getInstanceResult.Item1; - - bool isArray = instance.GetType().IsArray; - - object instanceToAddToCollectionInstance = getInstanceResult.Item2; - - instanceToAddToCollectionInstance = Map( dictionary, instanceToAddToCollectionInstance, parentInstance ); - - if ( isNewlyCreatedInstance ) - { - if ( isArray ) - { - var arrayList = new ArrayList { instanceToAddToCollectionInstance }; - - instance = arrayList.ToArray( type ); - } - else - { - MethodInfo addMethod = collectionType.GetMethod( "Add" ); - - addMethod.Invoke( instance, new[] { instanceToAddToCollectionInstance } ); - } - } - else - { - MethodInfo containsMethod = collectionType.GetMethod( "Contains" ); - - var alreadyContainsInstance = (bool)containsMethod.Invoke( instance, new[] { instanceToAddToCollectionInstance } ); - - if ( alreadyContainsInstance == false ) - { - if ( isArray ) - { - var arrayList = new ArrayList( (ICollection)instance ); - - instance = arrayList.ToArray( type ); - } - else - { - MethodInfo addMethod = collectionType.GetMethod( "Add" ); - - addMethod.Invoke( instance, new[] { instanceToAddToCollectionInstance } ); - } - } - } - - return instance; - } - - /// - /// Provides a means of getting/storing data in the host application's - /// appropriate context. - /// - //internal interface IContextStorage - //{ - // /// - // /// Get a stored item. - // /// - // /// Object type - // /// Item key - // /// Reference to the requested object - // T Get( string key ); - - // /// - // /// Stores an item. - // /// - // /// Item key - // /// Object to store - // void Store( string key, object obj ); - - // /// - // /// Removes an item. - // /// - // /// Item key - // void Remove( string key ); - //} - - /// - /// Provides a means of getting/storing data in the host application's - /// appropriate context. - /// - /// - /// For ASP.NET applications, it will store in the data in the current HTTPContext. - /// For all other applications, it will store the data in the logical call context. - /// - //public class InternalContextStorage : IContextStorage - //{ - // /// - // /// Get a stored item. - // /// - // /// Object type - // /// Item key - // /// Reference to the requested object - // public T Get( string key ) - // { - // try - // { - // if ( ReflectionHelper.HttpContext.GetCurrentHttpContext() == null ) - // { - // return (T)CallContext.LogicalGetData( key ); - // } - - // return ReflectionHelper.HttpContext.GetItemFromHttpContext( key ); - // } - // catch ( Exception ex ) - // { - // Logging.Logger.Log( Logging.LogLevel.Error, ex, "An error occurred in ContextStorage.Get() retrieving key: {0} for type: {1}.", key, typeof( T ) ); - // } - - // return default( T ); - // } - - // /// - // /// Stores an item. - // /// - // /// Item key - // /// Object to store - // public void Store( string key, object obj ) - // { - // if ( ReflectionHelper.HttpContext.GetCurrentHttpContext() == null ) - // { - // CallContext.LogicalSetData( key, obj ); - // } - // else - // { - // ReflectionHelper.HttpContext.StoreItemInHttpContext( key, obj ); - // } - // } - - // /// - // /// Removes an item. - // /// - // /// Item key - // public void Remove( string key ) - // { - // if ( ReflectionHelper.HttpContext.GetCurrentHttpContext() == null ) - // { - // CallContext.FreeNamedDataSlot( key ); - // } - // else - // { - // ReflectionHelper.HttpContext.RemoveItemFromHttpContext( key ); - // } - // } - //} - - ///// - ///// Provides a means of getting/storing data in the host application's - ///// appropriate context. - ///// - ///// - ///// For ASP.NET applications, it will store in the data in the current HTTPContext. - ///// For all other applications, it will store the data in the logical call context. - ///// - //internal static class ContextStorage - //{ - // /// - // /// Provides a means of getting/storing data in the host application's - // /// appropriate context. - // /// - // public static IContextStorage ContextStorageImplementation { get; set; } - - // static ContextStorage() - // { - // ContextStorageImplementation = new InternalContextStorage(); - // } - - // /// - // /// Get a stored item. - // /// - // /// Object type - // /// Item key - // /// Reference to the requested object - // public static T Get( string key ) - // { - // return ContextStorageImplementation.Get( key ); - // } - - // /// - // /// Stores an item. - // /// - // /// Item key - // /// Object to store - // public static void Store( string key, object obj ) - // { - // ContextStorageImplementation.Store( key, obj ); - // } - - // /// - // /// Removes an item. - // /// - // /// Item key - // public static void Remove( string key ) - // { - // ContextStorageImplementation.Remove( key ); - // } - //} - - /// - /// Contains the methods and members responsible for this libraries reflection concerns. - /// - //private static class ReflectionHelper - //{ - // /// - // /// Provides access to System.Web.HttpContext.Current.Items via reflection. - // /// - // public static class HttpContext - // { - // /// - // /// Attempts to load and cache System.Web.HttpContext.Current.Items. - // /// - // static HttpContext() - // { - // try - // { - // SystemDotWeb = Assembly - // .Load( "System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" ); - - // if ( SystemDotWeb == null ) return; - - // SystemDotWebDotHttpContext = SystemDotWeb.GetType( "System.Web.HttpContext", false, true ); - - // if ( SystemDotWebDotHttpContext == null ) return; - - // // Get the current HTTP context property info - // CurrentHttpContextPropertyInfo = SystemDotWebDotHttpContext - // .GetProperty( "Current", (BindingFlags.Public | BindingFlags.Static) ); - - // // Get the property info for the requested property - // ItemsPropertyInfo = SystemDotWebDotHttpContext - // .GetProperty( "Items", (BindingFlags.Public | BindingFlags.Instance) ); - // } - // catch ( Exception ex ) - // { - // Logging.Logger.Log( Logging.LogLevel.Error, ex, "An error occurred attempting to get the current HttpContext." ); - // } - // } - - // /// - // /// System.Web assembly reference. - // /// - // public static readonly Assembly SystemDotWeb; - - // /// - // /// System.Web.HttpContext type reference. - // /// - // public static readonly Type SystemDotWebDotHttpContext; - - // /// - // /// System.Web.HttpContext.Current PropertyInfo reference. - // /// - // public static readonly PropertyInfo CurrentHttpContextPropertyInfo; - - // /// - // /// System.Web.HttpContext.Current.Items PropertyInfo reference. - // /// - // public static readonly PropertyInfo ItemsPropertyInfo; - - // /// - // /// Retrieves an item of type from the current HttpContext. - // /// - // /// - // /// This is functionally equivalent to: - // /// T obj = ( T ) System.Web.HttpContext.Current.Items[ "SomeKeyName" ]; - // /// - // /// Type requested - // /// Key name - // /// Requested item - // public static T GetItemFromHttpContext( string key ) - // { - // if ( SystemDotWeb != null && SystemDotWebDotHttpContext != null - // && CurrentHttpContextPropertyInfo != null && ItemsPropertyInfo != null ) - // { - // // Get a reference to the current HTTP context - // object currentHttpContext = CurrentHttpContextPropertyInfo.GetValue( null, null ); - - // if ( currentHttpContext != null ) - // { - // var items = ItemsPropertyInfo.GetValue( currentHttpContext, null ) as IDictionary; - - // if ( items != null ) - // { - // object value = items[ key ]; - - // if ( value != null ) - // { - // return (T)value; - // } - // } - // } - // } - - // return default( T ); - // } - - // /// - // /// Stores an item in the current HttpContext. - // /// - // /// Item key - // /// Item value - // public static void StoreItemInHttpContext( object key, object value ) - // { - // object currentHttpContext = GetCurrentHttpContext(); - - // if ( currentHttpContext != null ) - // { - // var items = ItemsPropertyInfo.GetValue( currentHttpContext, null ) as IDictionary; - - // if ( items != null ) - // { - // items.Add( key, value ); - // } - // } - // } - - // /// - // /// Removes an item from the current HttpContext. - // /// - // /// Item key - // public static void RemoveItemFromHttpContext( object key ) - // { - // object currentHttpContext = GetCurrentHttpContext(); - - // if ( currentHttpContext != null ) - // { - // var items = ItemsPropertyInfo.GetValue( currentHttpContext, null ) as IDictionary; - - // if ( items != null ) - // { - // items.Remove( key ); - // } - // } - // } - - // /// - // /// Gets the current HttpContext. - // /// - // /// Reference to the current HttpContext. - // public static object GetCurrentHttpContext() - // { - // if ( SystemDotWeb != null && SystemDotWebDotHttpContext != null - // && CurrentHttpContextPropertyInfo != null && ItemsPropertyInfo != null ) - // { - // // Get a reference to the current HTTP context - // object currentHttpContext = CurrentHttpContextPropertyInfo.GetValue( null, null ); - - // return currentHttpContext; - // } - - // return null; - // } - // } - //} - } - - #endregion Internal Helpers - } + // TJT: Customized Automapper is now in /Querying/EntityMaterializer.cs + // Remove after beta. + //public static partial class AutoMapper + //{ + // #region Internal Helpers + + // /// + // /// Contains the methods and members responsible for this libraries internal concerns. + // /// + // internal static class InternalHelpers + // { + // /// + // /// Combine several hashcodes into a single new one. This implementation was grabbed from http://stackoverflow.com/a/34229665 where it is introduced + // /// as MS implementation of GetHashCode() for strings. + // /// + // /// Hascodes to be combined. + // /// A new Hascode value combining those passed as parameters. + // private static int CombineHashCodes( params int[] hashCodes ) + // { + // int hash1 = (5381 << 16) + 5381; + // int hash2 = hash1; + + // int i = 0; + // foreach ( var hashCode in hashCodes ) + // { + // if ( i % 2 == 0 ) + // hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ hashCode; + // else + // hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ hashCode; + + // ++i; + // } + + // return hash1 + (hash2 * 1566083941); + // } + + // /// + // /// Defines the key for caching instances. Overrides Equality as to get unicity for a given set of identifiers values + // /// for a given type. + // /// + // public struct InstanceKey : IEquatable + // { + // public bool Equals( InstanceKey other ) + // { + // return Equals( Type, other.Type ) + // && Equals( ParentInstance, other.ParentInstance ) + // && StructuralComparisons.StructuralEqualityComparer.Equals( IdentifierValues, other.IdentifierValues ); + // } + + // public override bool Equals( object obj ) + // { + // if ( ReferenceEquals( null, obj ) ) return false; + // return obj is InstanceKey && Equals( (InstanceKey)obj ); + // } + + // public override int GetHashCode() + // { + // unchecked + // { + // return CombineHashCodes( Type?.GetHashCode() ?? 0, StructuralComparisons.StructuralEqualityComparer.GetHashCode( IdentifierValues ), ParentInstance?.GetHashCode() ?? 0 ); + // } + // } + + // public static bool operator ==( InstanceKey left, InstanceKey right ) { return left.Equals( right ); } + + // public static bool operator !=( InstanceKey left, InstanceKey right ) { return !left.Equals( right ); } + + // public InstanceKey( Type type, object[] identifierValues, object parentInstance ) + // { + // Type = type; + // IdentifierValues = identifierValues; + // ParentInstance = parentInstance; + // } + + // public Type Type { get; } + // public object[] IdentifierValues { get; } + // public object ParentInstance { get; } + // } + + // /// + // /// Gets the identifiers for the given type. Returns NULL if not found. + // /// Results are cached for subsequent use and performance. + // /// + // /// + // /// If no identifiers have been manually added, this method will attempt + // /// to first find an attribute on the + // /// and if not found will then try to match based upon any specified identifier conventions. + // /// + // /// Type + // /// Identifier + // public static IEnumerable GetIdentifiers( Type type ) + // { + // var typeMap = Cache.TypeMapCache.GetOrAdd( type, CreateTypeMap( type ) ); + + // return typeMap.Identifiers.Any() ? typeMap.Identifiers : null; + // } + + // /// + // /// Get a Dictionary of a type's property names and field names and their corresponding PropertyInfo or FieldInfo. + // /// Results are cached for subsequent use and performance. + // /// + // /// Type + // /// Dictionary of a type's property names and their corresponding PropertyInfo + // public static Dictionary GetFieldsAndProperties( Type type ) + // { + // var typeMap = Cache.TypeMapCache.GetOrAdd( type, CreateTypeMap( type ) ); + + // return typeMap.PropertiesAndFieldsInfo; + // } + + // /// + // /// Creates an instance of the specified type using that type's default constructor. + // /// + // /// The type of object to create. + // /// + // /// A reference to the newly created object. + // /// + // public static object CreateInstance( Type type ) + // { + // if ( type == typeof( string ) ) + // { + // return string.Empty; + // } + + // if ( Configuration.TypeActivators.Count > 0 ) + // { + // foreach ( var typeActivator in Configuration.TypeActivators.OrderBy( ta => ta.Order ) ) + // { + // if ( typeActivator.CanCreate( type ) ) + // { + // return typeActivator.Create( type ); + // } + // } + // } + + // return Activator.CreateInstance( type ); + // } + + // /// + // /// Creates a TypeMap for a given Type. + // /// + // /// Type + // /// TypeMap + // public static Cache.TypeMap CreateTypeMap( Type type ) + // { + // var conventionIdentifiers = Configuration.IdentifierConventions.Select( applyIdentifierConvention => applyIdentifierConvention( type ) ).ToList(); + + // var fieldsAndProperties = CreateFieldAndPropertyInfoDictionary( type ); + + // var identifiers = new List(); + + // foreach ( var fieldOrProperty in fieldsAndProperties ) + // { + // var memberName = fieldOrProperty.Key; + + // var member = fieldOrProperty.Value; + + // var fieldInfo = member as FieldInfo; + + // if ( fieldInfo != null ) + // { + // if ( fieldInfo.GetCustomAttributes( Configuration.IdentifierAttributeType, false ).Length > 0 ) + // { + // identifiers.Add( memberName ); + // } + // else if ( conventionIdentifiers.Exists( x => x.ToLower() == memberName.ToLower() ) ) + // { + // identifiers.Add( memberName ); + // } + // } + // else + // { + // var propertyInfo = member as PropertyInfo; + + // if ( propertyInfo != null ) + // { + // if ( propertyInfo.GetCustomAttributes( Configuration.IdentifierAttributeType, false ).Length > 0 ) + // { + // identifiers.Add( memberName ); + // } + // else if ( conventionIdentifiers.Exists( x => x.ToLower() == memberName.ToLower() ) ) + // { + // identifiers.Add( memberName ); + // } + // } + // } + // } + + // var typeMap = new Cache.TypeMap( type, identifiers, fieldsAndProperties ); + + // return typeMap; + // } + + // /// + // /// Creates a Dictionary of field or property names and their corresponding FieldInfo or PropertyInfo objects + // /// + // /// Type + // /// Dictionary of member names and member info objects + // public static Dictionary CreateFieldAndPropertyInfoDictionary( Type type ) + // { + // var dictionary = new Dictionary(); + + // var properties = type.GetProperties(); + + // foreach ( var propertyInfo in properties ) + // { + // dictionary.Add( propertyInfo.Name, propertyInfo ); + // } + + // var fields = type.GetFields(); + + // foreach ( var fieldInfo in fields ) + // { + // dictionary.Add( fieldInfo.Name, fieldInfo ); + // } + + // return dictionary; + // } + + // /// + // /// Gets the Type of the Field or Property + // /// + // /// FieldInfo or PropertyInfo object + // /// Type + // public static Type GetMemberType( object member ) + // { + // Type type = null; + + // var fieldInfo = member as FieldInfo; + + // if ( fieldInfo != null ) + // { + // type = fieldInfo.FieldType; + // } + // else + // { + // var propertyInfo = member as PropertyInfo; + + // if ( propertyInfo != null ) + // { + // type = propertyInfo.PropertyType; + // } + // } + + // return type; + // } + + // /// + // /// Sets the value on a Field or Property + // /// + // /// FieldInfo or PropertyInfo object + // /// Object to set the value on + // /// Value + // public static void SetMemberValue( object member, object obj, object value ) + // { + // var fieldInfo = member as FieldInfo; + + // if ( fieldInfo != null ) + // { + // value = ConvertValuesTypeToMembersType( value, fieldInfo.Name, fieldInfo.FieldType, fieldInfo.DeclaringType ); + + // try + // { + // fieldInfo.SetValue( obj, value ); + // } + // catch ( Exception e ) + // { + // string errorMessage = + // string.Format( "{0}: An error occurred while mapping the value '{1}' of type {2} to the member name '{3}' of type {4} on the {5} class.", + // e.Message, value, value.GetType(), fieldInfo.Name, fieldInfo.FieldType, fieldInfo.DeclaringType ); + + // throw new Exception( errorMessage, e ); + // } + // } + // else + // { + // var propertyInfo = member as PropertyInfo; + + // if ( propertyInfo != null ) + // { + // value = ConvertValuesTypeToMembersType( value, propertyInfo.Name, propertyInfo.PropertyType, propertyInfo.DeclaringType ); + + // try + // { + // propertyInfo.SetValue( obj, value, null ); + // } + // catch ( Exception e ) + // { + // string errorMessage = + // string.Format( "{0}: An error occurred while mapping the value '{1}' of type {2} to the member name '{3}' of type {4} on the {5} class.", + // e.Message, value, value.GetType(), propertyInfo.Name, propertyInfo.PropertyType, propertyInfo.DeclaringType ); + + // throw new Exception( errorMessage, e ); + // } + // } + // } + // } + + // /// + // /// Converts the values type to the members type if needed. + // /// + // /// Object value. + // /// Member name. + // /// Member type. + // /// Declarying class type. + // /// Value converted to the same type as the member type. + // private static object ConvertValuesTypeToMembersType( object value, string memberName, Type memberType, Type classType ) + // { + // if ( value == null || value == DBNull.Value ) + // return null; + + // var valueType = value.GetType(); + + // try + // { + // if ( valueType != memberType ) + // { + // foreach ( var typeConverter in Configuration.TypeConverters.OrderBy( x => x.Order ) ) + // { + // if ( typeConverter.CanConvert( value, memberType ) ) + // { + // var convertedValue = typeConverter.Convert( value, memberType ); + + // return convertedValue; + // } + // } + // } + // } + // catch ( Exception e ) + // { + // string errorMessage = string.Format( "{0}: An error occurred while mapping the value '{1}' of type {2} to the member name '{3}' of type {4} on the {5} class.", + // e.Message, value, valueType, memberName, memberType, classType ); + + // throw new Exception( errorMessage, e ); + // } + + // return value; + // } + + // /// + // /// Gets the value of the member + // /// + // /// FieldInfo or PropertyInfo object + // /// Object to get the value from + // /// Value of the member + // private static object GetMemberValue( object member, object obj ) + // { + // object value = null; + + // var fieldInfo = member as FieldInfo; + + // if ( fieldInfo != null ) + // { + // value = fieldInfo.GetValue( obj ); + // } + // else + // { + // var propertyInfo = member as PropertyInfo; + + // if ( propertyInfo != null ) + // { + // value = propertyInfo.GetValue( obj, null ); + // } + // } + + // return value; + // } + + // /// + // /// Computes a key for storing and identifying an instance in the cache. + // /// + // /// Type of instance to get + // /// List of properties and values + // /// Parent instance. Can be NULL if this is the root instance. + // /// + // /// InstanceKey that will be unique for given set of identifiers values for the type. If the type isn't associated with any + // /// identifier, the return value is made unique by generating a Guid. + // /// ASSUMES GetIdentifiers(type) ALWAYS RETURN IDENTIFIERS IN THE SAME ORDER FOR A GIVEN TYPE. + // /// This is certainly the case as long as GetIdentifiers caches its result for a given type (which it does by 2016-11-25). + // /// + // private static InstanceKey GetCacheKey( Type type, IDictionary properties, object parentInstance ) + // { + // var identifierValues = GetIdentifiers( type )?.Select( id => properties[ id ] ).DefaultIfEmpty( Guid.NewGuid() ).ToArray() + // ?? new object[] { Guid.NewGuid() }; + + // var key = new InstanceKey( type, identifierValues, parentInstance ); + // return key; + // } + + + // /// + // /// Gets a new or existing instance depending on whether an instance with the same identifiers already existing + // /// in the instance cache. + // /// + // /// Type of instance to get + // /// List of properties and values + // /// Parent instance. Can be NULL if this is the root instance. + // /// + // /// Tuple of bool, object, int where bool represents whether this is a newly created instance, + // /// object being an instance of the requested type and int being the instance's identifier hash. + // /// + // internal static Tuple GetInstance( Type type, IDictionary properties, object parentInstance = null ) + // { + // var key = GetCacheKey( type, properties, parentInstance ); + + // var instanceCache = Cache.GetInstanceCache(); + + // object instance; + + // var isNewlyCreatedInstance = !instanceCache.TryGetValue( key, out instance ); + + // if ( isNewlyCreatedInstance ) + // { + // instance = CreateInstance( type ); + // instanceCache[ key ] = instance; + // } + + // return Tuple.Create( isNewlyCreatedInstance, instance, key ); + // } + + // /// + // /// Populates the given instance's properties where the IDictionary key property names + // /// match the type's property names case insensitively. + // /// + // /// Population of complex nested child properties is supported by underscoring "_" into the + // /// nested child properties in the property name. + // /// + // /// Dictionary of property names and values + // /// Instance to populate + // /// Optional parent instance of the instance being populated + // /// Populated instance + // internal static object Map( IDictionary dictionary, object instance, object parentInstance = null ) + // { + // if ( instance.GetType().IsPrimitive || instance is string ) + // { + // object value; + // if ( !dictionary.TryGetValue( "$", out value ) ) + // { + // throw new InvalidCastException( "For lists of primitive types, include $ as the name of the property" ); + // } + + // instance = value; + // return instance; + // } + + // var fieldsAndProperties = GetFieldsAndProperties( instance.GetType() ); + + // foreach ( var fieldOrProperty in fieldsAndProperties ) + // { + // var memberName = fieldOrProperty.Key.ToLower(); + + // var member = fieldOrProperty.Value; + + // object value; + + // // Handle populating simple members on the current type + // if ( dictionary.TryGetValue( memberName, out value ) ) + // { + // SetMemberValue( member, instance, value ); + // } + // else + // { + // Type memberType = GetMemberType( member ); + + // // Handle populating complex members on the current type + // if ( memberType.IsClass || memberType.IsInterface ) + // { + // // Try to find any keys that start with the current member name + // var nestedDictionary = dictionary.Where( x => x.Key.ToLower().StartsWith( memberName + "_" ) ).ToList(); + + // // If there weren't any keys + // if ( !nestedDictionary.Any() ) + // { + // // And the parent instance was not null + // if ( parentInstance != null ) + // { + // // And the parent instance is of the same type as the current member + // if ( parentInstance.GetType() == memberType ) + // { + // // Then this must be a 'parent' to the current type + // SetMemberValue( member, instance, parentInstance ); + // } + // } + + // continue; + // } + // var regex = new Regex( Regex.Escape( memberName + "_" ) ); + // var newDictionary = nestedDictionary.ToDictionary( pair => regex.Replace( pair.Key.ToLower(), string.Empty, 1 ), + // pair => pair.Value, StringComparer.OrdinalIgnoreCase ); + + // // Try to get the value of the complex member. If the member + // // hasn't been initialized, then this will return null. + // object nestedInstance = GetMemberValue( member, instance ); + + // var genericCollectionType = typeof( IEnumerable<> ); + // var isEnumerableType = memberType.IsGenericType && genericCollectionType.IsAssignableFrom( memberType.GetGenericTypeDefinition() ) + // || memberType.GetInterfaces().Any( x => x.IsGenericType && x.GetGenericTypeDefinition() == genericCollectionType ); + + // // If the member is null and is a class or interface (not ienumerable), try to create an instance of the type + // if ( nestedInstance == null && (memberType.IsClass || (memberType.IsInterface && !isEnumerableType)) ) + // { + // if ( memberType.IsArray ) + // { + // nestedInstance = new ArrayList().ToArray( memberType.GetElementType() ); + // } + // else + // { + // nestedInstance = typeof( IEnumerable ).IsAssignableFrom( memberType ) + // ? CreateInstance( memberType ) + // : GetInstance( memberType, newDictionary, parentInstance == null ? 0 : parentInstance.GetHashCode() ).Item2; + // } + // } + + // if ( isEnumerableType ) + // { + // var innerType = memberType.GetGenericArguments().FirstOrDefault() ?? memberType.GetElementType(); + // nestedInstance = MapCollection( innerType, newDictionary, nestedInstance, instance ); + // } + // else + // { + // if ( newDictionary.Values.All( v => v == null ) ) + // { + // nestedInstance = null; + // } + // else + // { + // nestedInstance = Map( newDictionary, nestedInstance, instance ); + // } + // } + + // SetMemberValue( member, instance, nestedInstance ); + // } + // } + // } + + // return instance; + // } + + // /// + // /// Populates the given instance's properties where the IDictionary key property names + // /// match the type's property names case insensitively. + // /// + // /// Population of complex nested child properties is supported by underscoring "_" into the + // /// nested child properties in the property name. + // /// + // /// Underlying instance type + // /// Dictionary of property names and values + // /// Instance to populate + // /// Optional parent instance of the instance being populated + // /// Populated instance + // internal static object MapCollection( Type type, IDictionary dictionary, object instance, object parentInstance = null ) + // { + // Type baseListType = typeof( List<> ); + // Type collectionType = instance == null ? baseListType.MakeGenericType( type ) : instance.GetType(); + + // if ( instance == null ) + // { + // instance = CreateInstance( collectionType ); + // } + + // // If the dictionnary only contains null values, we return an empty instance + // if ( dictionary.Values.FirstOrDefault( v => v != null ) == null ) + // { + // return instance; + // } + + // var getInstanceResult = GetInstance( type, dictionary, parentInstance ); + + // // Is this a newly created instance? If false, then this item was retrieved from the instance cache. + // bool isNewlyCreatedInstance = getInstanceResult.Item1; + + // bool isArray = instance.GetType().IsArray; + + // object instanceToAddToCollectionInstance = getInstanceResult.Item2; + + // instanceToAddToCollectionInstance = Map( dictionary, instanceToAddToCollectionInstance, parentInstance ); + + // if ( isNewlyCreatedInstance ) + // { + // if ( isArray ) + // { + // var arrayList = new ArrayList { instanceToAddToCollectionInstance }; + + // instance = arrayList.ToArray( type ); + // } + // else + // { + // MethodInfo addMethod = collectionType.GetMethod( "Add" ); + + // addMethod.Invoke( instance, new[] { instanceToAddToCollectionInstance } ); + // } + // } + // else + // { + // MethodInfo containsMethod = collectionType.GetMethod( "Contains" ); + + // var alreadyContainsInstance = (bool)containsMethod.Invoke( instance, new[] { instanceToAddToCollectionInstance } ); + + // if ( alreadyContainsInstance == false ) + // { + // if ( isArray ) + // { + // var arrayList = new ArrayList( (ICollection)instance ); + + // instance = arrayList.ToArray( type ); + // } + // else + // { + // MethodInfo addMethod = collectionType.GetMethod( "Add" ); + + // addMethod.Invoke( instance, new[] { instanceToAddToCollectionInstance } ); + // } + // } + // } + + // return instance; + // } + + // /// + // /// Provides a means of getting/storing data in the host application's + // /// appropriate context. + // /// + // //internal interface IContextStorage + // //{ + // // /// + // // /// Get a stored item. + // // /// + // // /// Object type + // // /// Item key + // // /// Reference to the requested object + // // T Get( string key ); + + // // /// + // // /// Stores an item. + // // /// + // // /// Item key + // // /// Object to store + // // void Store( string key, object obj ); + + // // /// + // // /// Removes an item. + // // /// + // // /// Item key + // // void Remove( string key ); + // //} + + // /// + // /// Provides a means of getting/storing data in the host application's + // /// appropriate context. + // /// + // /// + // /// For ASP.NET applications, it will store in the data in the current HTTPContext. + // /// For all other applications, it will store the data in the logical call context. + // /// + // //public class InternalContextStorage : IContextStorage + // //{ + // // /// + // // /// Get a stored item. + // // /// + // // /// Object type + // // /// Item key + // // /// Reference to the requested object + // // public T Get( string key ) + // // { + // // try + // // { + // // if ( ReflectionHelper.HttpContext.GetCurrentHttpContext() == null ) + // // { + // // return (T)CallContext.LogicalGetData( key ); + // // } + + // // return ReflectionHelper.HttpContext.GetItemFromHttpContext( key ); + // // } + // // catch ( Exception ex ) + // // { + // // Logging.Logger.Log( Logging.LogLevel.Error, ex, "An error occurred in ContextStorage.Get() retrieving key: {0} for type: {1}.", key, typeof( T ) ); + // // } + + // // return default( T ); + // // } + + // // /// + // // /// Stores an item. + // // /// + // // /// Item key + // // /// Object to store + // // public void Store( string key, object obj ) + // // { + // // if ( ReflectionHelper.HttpContext.GetCurrentHttpContext() == null ) + // // { + // // CallContext.LogicalSetData( key, obj ); + // // } + // // else + // // { + // // ReflectionHelper.HttpContext.StoreItemInHttpContext( key, obj ); + // // } + // // } + + // // /// + // // /// Removes an item. + // // /// + // // /// Item key + // // public void Remove( string key ) + // // { + // // if ( ReflectionHelper.HttpContext.GetCurrentHttpContext() == null ) + // // { + // // CallContext.FreeNamedDataSlot( key ); + // // } + // // else + // // { + // // ReflectionHelper.HttpContext.RemoveItemFromHttpContext( key ); + // // } + // // } + // //} + + // ///// + // ///// Provides a means of getting/storing data in the host application's + // ///// appropriate context. + // ///// + // ///// + // ///// For ASP.NET applications, it will store in the data in the current HTTPContext. + // ///// For all other applications, it will store the data in the logical call context. + // ///// + // //internal static class ContextStorage + // //{ + // // /// + // // /// Provides a means of getting/storing data in the host application's + // // /// appropriate context. + // // /// + // // public static IContextStorage ContextStorageImplementation { get; set; } + + // // static ContextStorage() + // // { + // // ContextStorageImplementation = new InternalContextStorage(); + // // } + + // // /// + // // /// Get a stored item. + // // /// + // // /// Object type + // // /// Item key + // // /// Reference to the requested object + // // public static T Get( string key ) + // // { + // // return ContextStorageImplementation.Get( key ); + // // } + + // // /// + // // /// Stores an item. + // // /// + // // /// Item key + // // /// Object to store + // // public static void Store( string key, object obj ) + // // { + // // ContextStorageImplementation.Store( key, obj ); + // // } + + // // /// + // // /// Removes an item. + // // /// + // // /// Item key + // // public static void Remove( string key ) + // // { + // // ContextStorageImplementation.Remove( key ); + // // } + // //} + + // /// + // /// Contains the methods and members responsible for this libraries reflection concerns. + // /// + // //private static class ReflectionHelper + // //{ + // // /// + // // /// Provides access to System.Web.HttpContext.Current.Items via reflection. + // // /// + // // public static class HttpContext + // // { + // // /// + // // /// Attempts to load and cache System.Web.HttpContext.Current.Items. + // // /// + // // static HttpContext() + // // { + // // try + // // { + // // SystemDotWeb = Assembly + // // .Load( "System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" ); + + // // if ( SystemDotWeb == null ) return; + + // // SystemDotWebDotHttpContext = SystemDotWeb.GetType( "System.Web.HttpContext", false, true ); + + // // if ( SystemDotWebDotHttpContext == null ) return; + + // // // Get the current HTTP context property info + // // CurrentHttpContextPropertyInfo = SystemDotWebDotHttpContext + // // .GetProperty( "Current", (BindingFlags.Public | BindingFlags.Static) ); + + // // // Get the property info for the requested property + // // ItemsPropertyInfo = SystemDotWebDotHttpContext + // // .GetProperty( "Items", (BindingFlags.Public | BindingFlags.Instance) ); + // // } + // // catch ( Exception ex ) + // // { + // // Logging.Logger.Log( Logging.LogLevel.Error, ex, "An error occurred attempting to get the current HttpContext." ); + // // } + // // } + + // // /// + // // /// System.Web assembly reference. + // // /// + // // public static readonly Assembly SystemDotWeb; + + // // /// + // // /// System.Web.HttpContext type reference. + // // /// + // // public static readonly Type SystemDotWebDotHttpContext; + + // // /// + // // /// System.Web.HttpContext.Current PropertyInfo reference. + // // /// + // // public static readonly PropertyInfo CurrentHttpContextPropertyInfo; + + // // /// + // // /// System.Web.HttpContext.Current.Items PropertyInfo reference. + // // /// + // // public static readonly PropertyInfo ItemsPropertyInfo; + + // // /// + // // /// Retrieves an item of type from the current HttpContext. + // // /// + // // /// + // // /// This is functionally equivalent to: + // // /// T obj = ( T ) System.Web.HttpContext.Current.Items[ "SomeKeyName" ]; + // // /// + // // /// Type requested + // // /// Key name + // // /// Requested item + // // public static T GetItemFromHttpContext( string key ) + // // { + // // if ( SystemDotWeb != null && SystemDotWebDotHttpContext != null + // // && CurrentHttpContextPropertyInfo != null && ItemsPropertyInfo != null ) + // // { + // // // Get a reference to the current HTTP context + // // object currentHttpContext = CurrentHttpContextPropertyInfo.GetValue( null, null ); + + // // if ( currentHttpContext != null ) + // // { + // // var items = ItemsPropertyInfo.GetValue( currentHttpContext, null ) as IDictionary; + + // // if ( items != null ) + // // { + // // object value = items[ key ]; + + // // if ( value != null ) + // // { + // // return (T)value; + // // } + // // } + // // } + // // } + + // // return default( T ); + // // } + + // // /// + // // /// Stores an item in the current HttpContext. + // // /// + // // /// Item key + // // /// Item value + // // public static void StoreItemInHttpContext( object key, object value ) + // // { + // // object currentHttpContext = GetCurrentHttpContext(); + + // // if ( currentHttpContext != null ) + // // { + // // var items = ItemsPropertyInfo.GetValue( currentHttpContext, null ) as IDictionary; + + // // if ( items != null ) + // // { + // // items.Add( key, value ); + // // } + // // } + // // } + + // // /// + // // /// Removes an item from the current HttpContext. + // // /// + // // /// Item key + // // public static void RemoveItemFromHttpContext( object key ) + // // { + // // object currentHttpContext = GetCurrentHttpContext(); + + // // if ( currentHttpContext != null ) + // // { + // // var items = ItemsPropertyInfo.GetValue( currentHttpContext, null ) as IDictionary; + + // // if ( items != null ) + // // { + // // items.Remove( key ); + // // } + // // } + // // } + + // // /// + // // /// Gets the current HttpContext. + // // /// + // // /// Reference to the current HttpContext. + // // public static object GetCurrentHttpContext() + // // { + // // if ( SystemDotWeb != null && SystemDotWebDotHttpContext != null + // // && CurrentHttpContextPropertyInfo != null && ItemsPropertyInfo != null ) + // // { + // // // Get a reference to the current HTTP context + // // object currentHttpContext = CurrentHttpContextPropertyInfo.GetValue( null, null ); + + // // return currentHttpContext; + // // } + + // // return null; + // // } + // // } + // //} + // } + + // #endregion Internal Helpers + //} } \ No newline at end of file diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Mapper.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Mapper.cs index d87650a..68fdd53 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Mapper.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/AutoMapper/Mapper.cs @@ -34,199 +34,202 @@ dynamic data into static types and populate complex nested child objects. using System.Collections.Generic; using System.Linq; -namespace Achilles.Entities.Mapping -{ - /// - /// Provides auto-mapping to static type capabilities for ORMs. Slap your ORM into submission. - /// - public static partial class AutoMapper - { - #region Attributes - - /// - /// Attribute for specifying that a field or property is an identifier. - /// - [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false )] - public class Id : Attribute - { - } - - #endregion Attributes - - #region Mapping - - /// - /// Converts a dynamic object to a type . - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// Dynamic list of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// The type - /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. - public static T MapDynamic( object dynamicObject, bool keepCache = true ) - { - return (T)MapDynamic( typeof( T ), dynamicObject, keepCache ); - } - - /// - /// Converts a dynamic object to a specified Type. - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// Dynamic list of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// The specified Type - /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. - public static object MapDynamic( Type type, object dynamicObject, bool keepCache = true ) - { - if ( dynamicObject == null ) - { - return type.IsValueType ? Activator.CreateInstance( type ) : null; - } - - var dictionary = dynamicObject as IDictionary; - - if ( dictionary == null ) - throw new ArgumentException( "Object type cannot be converted to an IDictionary", "dynamicObject" ); - - var propertiesList = new List> { dictionary }; - - return Map( type, propertiesList, keepCache ).FirstOrDefault(); - } - - /// - /// Converts a list of dynamic objects to a list of type of . - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// Dynamic list of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// List of type - /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. - public static IEnumerable MapDynamic( IEnumerable dynamicListOfProperties, bool keepCache = true ) - { - return MapDynamic( typeof( T ), dynamicListOfProperties, keepCache ).Cast(); - } - - /// - /// Converts a list of dynamic objects to a list of specified Type. - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// Dynamic list of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// List of specified Type - /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. - public static IEnumerable MapDynamic( Type type, IEnumerable dynamicListOfProperties, bool keepCache = true ) - { - if ( dynamicListOfProperties == null ) - return new List(); - - var dictionary = dynamicListOfProperties.Select( dynamicItem => dynamicItem as IDictionary ).ToList(); - - if ( dictionary == null ) - throw new ArgumentException( "Object types cannot be converted to an IDictionary", "dynamicListOfProperties" ); - - if ( dictionary.Count == 0 || dictionary[ 0 ] == null ) - return new List(); - - return Map( type, dictionary, keepCache ); - } - - /// - /// Converts a dictionary of property names and values to a type . - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// List of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// The type - public static T Map( IDictionary listOfProperties, bool keepCache = true ) - { - return (T)Map( typeof( T ), listOfProperties, keepCache ); - } - - /// - /// Converts a dictionary of property names and values to a specified Type. - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// List of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// The specified Type - public static object Map( Type type, IDictionary listOfProperties, bool keepCache = true ) - { - var propertiesList = new List> { listOfProperties }; - - return Map( type, propertiesList, keepCache ).FirstOrDefault(); - } - - /// - /// Converts a list of dictionaries of property names and values to a list of type of . - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// List of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// List of type - public static IEnumerable Map( IEnumerable> listOfProperties, bool keepCache = true ) - { - return Map( typeof( T ), listOfProperties, keepCache ).Cast(); - } - - /// - /// Converts a list of dictionaries of property names and values to a list of specified Type. - /// - /// Population of complex nested child properties is supported by underscoring "_" into the - /// nested child properties in the property name. - /// - /// Type to instantiate and automap to - /// List of property names and values - /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// List of specified Type - public static IEnumerable Map( Type type, IEnumerable> listOfProperties, bool keepCache = true ) - { - var instanceCache = new Dictionary(); - - foreach ( var properties in listOfProperties ) - { - var getInstanceResult = InternalHelpers.GetInstance( type, properties ); - - object instance = getInstanceResult.Item2; - - var key = getInstanceResult.Item3; - - if ( instanceCache.ContainsKey( key ) == false ) - { - instanceCache.Add( key, instance ); - } - - var caseInsensitiveDictionary = new Dictionary( properties, StringComparer.OrdinalIgnoreCase ); - - InternalHelpers.Map( caseInsensitiveDictionary, instance ); - } - - if ( !keepCache ) - Cache.ClearInstanceCache(); - - return instanceCache.Select( pair => pair.Value ); - } - - #endregion Mapping - } -} \ No newline at end of file +// TJT: Customized Automapper is now in /Querying/EntityMaterializer.cs +// Remove after beta. + +//namespace Achilles.Entities.Mapping +//{ +// /// +// /// Provides auto-mapping to static type capabilities for ORMs. Slap your ORM into submission (Too funny!). +// /// +// public static partial class AutoMapper +// { +// #region Attributes + +// /// +// /// Attribute for specifying that a field or property is an identifier. +// /// +// [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false )] +// public class Id : Attribute +// { +// } + +// #endregion Attributes + +// #region Mapping + +// /// +// /// Converts a dynamic object to a type . +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// Dynamic list of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// The type +// /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. +// public static T MapDynamic( object dynamicObject, bool keepCache = true ) +// { +// return (T)MapDynamic( typeof( T ), dynamicObject, keepCache ); +// } + +// /// +// /// Converts a dynamic object to a specified Type. +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// Dynamic list of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// The specified Type +// /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. +// public static object MapDynamic( Type type, object dynamicObject, bool keepCache = true ) +// { +// if ( dynamicObject == null ) +// { +// return type.IsValueType ? Activator.CreateInstance( type ) : null; +// } + +// var dictionary = dynamicObject as IDictionary; + +// if ( dictionary == null ) +// throw new ArgumentException( "Object type cannot be converted to an IDictionary", "dynamicObject" ); + +// var propertiesList = new List> { dictionary }; + +// return Map( type, propertiesList, keepCache ).FirstOrDefault(); +// } + +// /// +// /// Converts a list of dynamic objects to a list of type of . +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// Dynamic list of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// List of type +// /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. +// public static IEnumerable MapDynamic( IEnumerable dynamicListOfProperties, bool keepCache = true ) +// { +// return MapDynamic( typeof( T ), dynamicListOfProperties, keepCache ).Cast(); +// } + +// /// +// /// Converts a list of dynamic objects to a list of specified Type. +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// Dynamic list of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// List of specified Type +// /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. +// public static IEnumerable MapDynamic( Type type, IEnumerable dynamicListOfProperties, bool keepCache = true ) +// { +// if ( dynamicListOfProperties == null ) +// return new List(); + +// var dictionary = dynamicListOfProperties.Select( dynamicItem => dynamicItem as IDictionary ).ToList(); + +// if ( dictionary == null ) +// throw new ArgumentException( "Object types cannot be converted to an IDictionary", "dynamicListOfProperties" ); + +// if ( dictionary.Count == 0 || dictionary[ 0 ] == null ) +// return new List(); + +// return Map( type, dictionary, keepCache ); +// } + +// /// +// /// Converts a dictionary of property names and values to a type . +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// List of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// The type +// public static T Map( IDictionary listOfProperties, bool keepCache = true ) +// { +// return (T)Map( typeof( T ), listOfProperties, keepCache ); +// } + +// /// +// /// Converts a dictionary of property names and values to a specified Type. +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// List of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// The specified Type +// public static object Map( Type type, IDictionary listOfProperties, bool keepCache = true ) +// { +// var propertiesList = new List> { listOfProperties }; + +// return Map( type, propertiesList, keepCache ).FirstOrDefault(); +// } + +// /// +// /// Converts a list of dictionaries of property names and values to a list of type of . +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// List of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// List of type +// public static IEnumerable Map( IEnumerable> listOfProperties, bool keepCache = true ) +// { +// return Map( typeof( T ), listOfProperties, keepCache ).Cast(); +// } + +// /// +// /// Converts a list of dictionaries of property names and values to a list of specified Type. +// /// +// /// Population of complex nested child properties is supported by underscoring "_" into the +// /// nested child properties in the property name. +// /// +// /// Type to instantiate and automap to +// /// List of property names and values +// /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. +// /// List of specified Type +// public static IEnumerable Map( Type type, IEnumerable> listOfProperties, bool keepCache = true ) +// { +// var instanceCache = new Dictionary(); + +// foreach ( var properties in listOfProperties ) +// { +// var getInstanceResult = InternalHelpers.GetInstance( type, properties ); + +// object instance = getInstanceResult.Item2; + +// var key = getInstanceResult.Item3; + +// if ( instanceCache.ContainsKey( key ) == false ) +// { +// instanceCache.Add( key, instance ); +// } + +// var caseInsensitiveDictionary = new Dictionary( properties, StringComparer.OrdinalIgnoreCase ); + +// InternalHelpers.Map( caseInsensitiveDictionary, instance ); +// } + +// if ( !keepCache ) +// Cache.ClearInstanceCache(); + +// return instanceCache.Select( pair => pair.Value ); +// } + +// #endregion Mapping +// } +//} \ No newline at end of file diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs index d93f0e6..08ad67a 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs @@ -33,8 +33,6 @@ public class EntityMappingBuilder : IEntityMappingBuilder wher private readonly List _columnMappingBuilders = new List(); private readonly List _indexMappingBuilders = new List(); - - // TODO: Make single RelationshipMappingBuilder private readonly List _hasManyMappingBuilders = new List(); private readonly List> _hasOneMappingBuilders = new List>(); @@ -100,8 +98,8 @@ public IHasManyMappingBuilder HasMany( Expression> relatio { var relationship = ReflectionHelper.GetMemberInfo( relationshipPropertyLambda ); - if ( _hasManyMappingBuilders.Any( builder => builder.Relationship == relationship ) || - _hasOneMappingBuilders.Any( builder => builder.Relationship == relationship ) ) + if ( _hasManyMappingBuilders.Any( builder => builder.RelationshipProperty == relationship ) || + _hasOneMappingBuilders.Any( builder => builder.RelationshipProperty == relationship ) ) { throw new Exception( $"Duplicate mapping detected. Relationship property '{relationship.Name}' is already mapped." ); } @@ -117,8 +115,8 @@ public IHasOneMappingBuilder HasOne( Expression> { var relationship = ReflectionHelper.GetMemberInfo( relationshipPropertyLambda ); - if ( _hasManyMappingBuilders.Any( builder => builder.Relationship == relationship ) || - _hasOneMappingBuilders.Any( builder => builder.Relationship == relationship ) ) + if ( _hasManyMappingBuilders.Any( builder => builder.RelationshipProperty == relationship ) || + _hasOneMappingBuilders.Any( builder => builder.RelationshipProperty == relationship ) ) { throw new Exception( $"Duplicate mapping detected. Relationship property '{relationship.Name}' is already mapped." ); } @@ -162,25 +160,32 @@ public IEntityMapping Build() // Add foreign key mappings... // HasMany mappings may have the foreign key constaint on another Entity ( unless self referencing ) - var hasManyFKMappings = _hasManyMappingBuilders.Select( b => b.Build( EntityMapping ) ).ToList(); + var hasManyMappings = _hasManyMappingBuilders.Select( b => b.Build( EntityMapping ) ).ToList(); - foreach ( var foreignKeyMapping in hasManyFKMappings ) + foreach ( var hasManyMapping in hasManyMappings ) { - var t = foreignKeyMapping.ForeignKeyProperty.DeclaringType; + var t = hasManyMapping.ForeignKeyMapping.ForeignKeyProperty.DeclaringType; if ( t != EntityType ) { var foreignKeyConstraintMapping = _entityMappings.GetOrAddEntityMapping( t ); - foreignKeyConstraintMapping.ForeignKeyMappings.Add( foreignKeyMapping ); + foreignKeyConstraintMapping.ForeignKeyMappings.Add( hasManyMapping.ForeignKeyMapping ); } else { - EntityMapping.ForeignKeyMappings.Add( foreignKeyMapping ); + EntityMapping.ForeignKeyMappings.Add( hasManyMapping.ForeignKeyMapping ); } + + EntityMapping.RelationshipMappings.Add( hasManyMapping ); } - var hasOneFKMappings = _hasOneMappingBuilders.Select( b => b.Build( EntityMapping) ).ToList(); - EntityMapping.ForeignKeyMappings.AddRange( hasOneFKMappings ); + var hasOneMappings = _hasOneMappingBuilders.Select( b => b.Build( EntityMapping ) ).ToList(); + + foreach ( var hasOneMapping in hasOneMappings ) + { + EntityMapping.RelationshipMappings.Add( hasOneMapping ); + EntityMapping.ForeignKeyMappings.Add( hasOneMapping.ForeignKeyMapping ); + } return EntityMapping; } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasManyMappingBuilder.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasManyMappingBuilder.cs index b596292..05c8711 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasManyMappingBuilder.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasManyMappingBuilder.cs @@ -21,7 +21,7 @@ namespace Achilles.Entities.Modelling.Mapping.Builders { /// - /// Implements the interface to fluently configure + /// Implements the interface to fluently build /// a 1-many foreign key relationship. /// public class HasManyMappingBuilder : IHasManyMappingBuilder @@ -37,41 +37,28 @@ public class HasManyMappingBuilder : IHasManyMappingBuilder /// /// Constructs a HasManyMappingBuilder from the propertyInfo paarameter. /// - /// The HasMany relationship property or field. + /// The HasMany relationship property or field. /// /// Thrown when the relationship property or field is null. /// /// - /// Thrown when the relationship property or field is not an . + /// Thrown when the relationship property or field type is not an . /// - public HasManyMappingBuilder( MemberInfo relationship ) + public HasManyMappingBuilder( MemberInfo relationshipProperty ) { - Relationship = relationship ?? throw new ArgumentNullException( nameof( relationship ) ); + RelationshipProperty = relationshipProperty ?? throw new ArgumentNullException( nameof( relationshipProperty ) ); - if ( Relationship.GetPropertyType().GetInterface( nameof( IEntityCollection ) ) == null ) + if ( RelationshipProperty.GetPropertyType().GetInterface( nameof( IEntityCollection ) ) == null ) { - throw new ArgumentException( nameof( relationship ) ); + throw new ArgumentException( nameof( relationshipProperty ) ); } - - ForeignKeyMapping = CreateForeignKeyMapping( relationship ); } #endregion - private IForeignKeyMapping ForeignKeyMapping { get; } - - #region Public Properties - - /// - /// - /// - internal MemberInfo Relationship { get; } - - #endregion - - public Type PropertyType => Relationship.GetPropertyType(); + #region Public API - /// + // public IForeignKeyMappingBuilder WithForeignKey( Expression> foreignKeyLambda ) { var foreignKey = ReflectionHelper.GetMemberInfo( foreignKeyLambda ); @@ -81,9 +68,29 @@ public IForeignKeyMappingBuilder WithForeignKey( Expression _foreignKeyMappingBuilder.Build(); + #endregion + + #region Private/Internal Properties and Methods - protected virtual IForeignKeyMapping CreateForeignKeyMapping( MemberInfo relationshipInfo ) - => new ForeignKeyMapping( relationshipInfo ); + /// + /// Gets the 1-1 relationship property instance associated with this builder. + /// + internal MemberInfo RelationshipProperty { get; } + + /// + /// Builds a . + /// + /// A instance. + internal RelationshipMapping Build( IEntityMapping entityMapping ) + { + return new RelationshipMapping() + { + RelationshipProperty = RelationshipProperty, + ForeignKeyMapping = _foreignKeyMappingBuilder.Build(), + IsMany = true, + }; + } + + #endregion } } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasOneMappingBuilder.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasOneMappingBuilder.cs index 45853a1..9ad7f67 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasOneMappingBuilder.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/HasOneMappingBuilder.cs @@ -21,6 +21,11 @@ namespace Achilles.Entities.Modelling.Mapping.Builders { + /// + /// Implements the interface to fluently build + /// a 1-many foreign key relationship. + /// + /// public class HasOneMappingBuilder : IHasOneMappingBuilder where TEntity : class { #region Private Fields @@ -34,40 +39,32 @@ public class HasOneMappingBuilder : IHasOneMappingBuilder wher /// /// Constructs a HasOneMappingBuilder with the provided property to build the 1-1 relationship on. /// - /// The relationship property or field . + /// The relationship property or field . /// /// /// Thrown when the HasOne relationship property or field is null. /// /// /// Thrown when the HasOne relationship property or field is not an . /// - public HasOneMappingBuilder( MemberInfo relationship ) + public HasOneMappingBuilder( MemberInfo relationshipProperty ) { - Relationship = relationship ?? throw new ArgumentNullException( nameof( relationship ) ); + RelationshipProperty = relationshipProperty ?? throw new ArgumentNullException( nameof( relationshipProperty ) ); - if ( Relationship.GetPropertyType().GetInterface( nameof( IEntityReference ) ) == null ) + if ( RelationshipProperty.GetPropertyType().GetInterface( nameof( IEntityReference ) ) == null ) { - throw new ArgumentException( nameof( relationship ) ); + throw new ArgumentException( nameof( relationshipProperty ) ); } - - ForeignKeyMapping = CreateForeignKeyMapping( relationship ); } #endregion #region Private, Internal Properties - private IForeignKeyMapping ForeignKeyMapping { get; } - /// - internal MemberInfo Relationship { get; } + internal MemberInfo RelationshipProperty { get; } #endregion - #region Public Properties and Methods - - public Type PropertyType => Relationship.GetPropertyType(); - - //public string PropertyName => Relationship.Name; + #region Public API /// public IForeignKeyMappingBuilder WithForeignKey( Expression> foreignKeyLambda ) @@ -79,18 +76,23 @@ public IForeignKeyMappingBuilder WithForeignKey( Expression - internal IForeignKeyMapping Build( IEntityMapping entityMapping ) - { - return _foreignKeyMappingBuilder.Build(); - } - #endregion #region Private Methods - private IForeignKeyMapping CreateForeignKeyMapping( MemberInfo relationshipInfo ) - => new ForeignKeyMapping( relationshipInfo ); + /// + /// Builds a . + /// + /// A instance. + internal RelationshipMapping Build( IEntityMapping entityMapping ) + { + return new RelationshipMapping() + { + RelationshipProperty = RelationshipProperty, + ForeignKeyMapping = _foreignKeyMappingBuilder.Build(), + IsMany = false + }; + } #endregion } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/IHasOneMappingBuilder.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/IHasOneMappingBuilder.cs index dca8887..c5108fb 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/IHasOneMappingBuilder.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/IHasOneMappingBuilder.cs @@ -12,30 +12,22 @@ using System; using System.Linq.Expressions; -using System.Reflection; #endregion namespace Achilles.Entities.Modelling.Mapping.Builders { + /// + /// The public API methods used to fluently build a 1-One or HasOne relationship. + /// + /// The Entity type. public interface IHasOneMappingBuilder { - /// - /// Gets the 1-1 relationship instance associated with this builder. - /// - //PropertyInfo RelationshipProperty { get; } - /// /// Sets the foreign key. /// /// The foreign key column property from the TEntity being mapped. /// IForeignKeyMappingBuilder WithForeignKey( Expression> foreignKey ); - - /// - /// Builds a . - /// - /// A instance. - //IForeignKeyMapping Build( IEntityMapping entityMapping ); } } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs index 7ebef1d..2da01bc 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs @@ -60,6 +60,8 @@ public EntityMapping() public List ForeignKeyMappings { get; set; } = new List(); + public List RelationshipMappings { get; set; } = new List(); + public Type EntityType => typeof( TEntity ); public string SchemaName { get; set; } = string.Empty; diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs index 25d6261..cf5a613 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs @@ -23,6 +23,8 @@ public interface IEntityMapping List IndexMappings { get; } + List RelationshipMappings { get; } + List ForeignKeyMappings { get; } Type EntityType { get; } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs index 8d7d4a0..82976f7 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs @@ -39,21 +39,6 @@ public interface IForeignKeyMapping /// string Name { get; set; } - /// - /// Gets the foreign key column name. - /// - //string ForeignKeyColumn { get; } - - /// - /// Gets the table that a foreign key constraint refers to. - /// - //string ReferenceTable { get; } - - /// - /// Gets the key which is the column or set of columns in the reference table that the foreign key constraint refers to. - /// - //string ReferenceColumn { get; set; } - /// /// Gets the flag that indictes whether ... /// diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/IRelationshipMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/IRelationshipMapping.cs new file mode 100644 index 0000000..6d7cb37 --- /dev/null +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/IRelationshipMapping.cs @@ -0,0 +1,36 @@ +#region Copyright Notice + +// Copyright (c) by Achilles Software, All rights reserved. +// +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// Send questions regarding this copyright notice to: mailto:Todd.Thomson@achilles-software.com + +#endregion + +#region Namespaces + +using System.Reflection; + +#endregion + +namespace Achilles.Entities.Modelling.Mapping +{ + public interface IRelationshipMapping + { + /// + /// Gets the property for this relationship mapping. + /// + MemberInfo RelationshipProperty { get; } + + /// + /// Gets the for this relationship mapping. + /// + IForeignKeyMapping ForeignKeyMapping { get; } + + /// + /// Gets a flag indicating the type of relationship. True indicates that the relationship is 1-Many. If False then the relationship is 1-One. + /// + bool IsMany { get; } + } +} diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/RelationshipMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/RelationshipMapping.cs new file mode 100644 index 0000000..57c570a --- /dev/null +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/RelationshipMapping.cs @@ -0,0 +1,34 @@ +#region Copyright Notice + +// Copyright (c) by Achilles Software, All rights reserved. +// +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// Send questions regarding this copyright notice to: mailto:Todd.Thomson@achilles-software.com + +#endregion + +#region Namespaces + +using System; +using System.Reflection; + +#endregion + +namespace Achilles.Entities.Modelling.Mapping +{ + /// + /// Relation mapping data. + /// + public class RelationshipMapping : IRelationshipMapping + { + /// + public MemberInfo RelationshipProperty { get; internal set; } + + /// + public IForeignKeyMapping ForeignKeyMapping { get; internal set; } + + /// + public bool IsMany { get; internal set; } + } +} diff --git a/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs b/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs index df2d338..ffb61ba 100644 --- a/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs +++ b/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs @@ -10,6 +10,7 @@ #region Namespaces +using Achilles.Entities.Extensions; using Achilles.Entities.Modelling; using Achilles.Entities.Querying.TypeConverters; using System; @@ -34,15 +35,17 @@ internal class EntityMaterializer private static readonly List TypeConverters = new List(); - private readonly IEntityModel _model; + private DataContext _context; + + private IEntityModel _model => _context.Model; #endregion #region Constructor(s) - internal EntityMaterializer( IEntityModel model ) + internal EntityMaterializer( DataContext context ) { - _model = model; + _context = context; Initialize(); } @@ -131,12 +134,12 @@ private void ApplyDefaultTypeConverters() /// nested child properties in the property name. /// /// Dictionary of property names and values - /// Instance to populate + /// Instance to populate /// Optional parent instance of the instance being populated /// Populated instance - internal object Materialize( IDictionary dictionary, object instance, object parentInstance = null ) + internal object Materialize( IDictionary dictionary, object entity, object parentInstance = null ) { - if ( instance.GetType().IsPrimitive || instance is string ) + if ( entity.GetType().IsPrimitive || entity is string ) { object value; if ( !dictionary.TryGetValue( "$", out value ) ) @@ -144,28 +147,76 @@ internal object Materialize( IDictionary dictionary, object inst throw new InvalidCastException( "For lists of primitive types, include $ as the name of the property" ); } - instance = value; - return instance; + entity = value; + + return entity; } - var fieldsAndProperties = GetFieldsAndProperties( instance.GetType() ); + // Once we have the instance we can attach an entity set source to the instance relationship properties + var entityMapping = _model.GetEntityMapping( entity.GetType() ); + + var relationshipMappings = entityMapping.RelationshipMappings; + + var fieldsAndProperties = GetFieldsAndProperties( entity.GetType() ); foreach ( var fieldOrProperty in fieldsAndProperties ) { var memberName = fieldOrProperty.Key.ToLower(); + var memberInfo = fieldOrProperty.Value; - var member = fieldOrProperty.Value; + var relationshipMapping = relationshipMappings.Where( r => r.RelationshipProperty == memberInfo ).FirstOrDefault(); + + if ( relationshipMapping != null ) + { + if ( relationshipMapping.IsMany ) + { + IEntityCollection entityCollection = memberInfo as IEntityCollection; + } + else + { + // First Get the type entity we are materializing + Type entityType = entity.GetType(); + + object entityReferenceProperty; + + if ( memberInfo.MemberType == MemberTypes.Property ) + { + PropertyInfo propertyInfo = memberInfo as PropertyInfo; + + entityReferenceProperty = propertyInfo.GetValue( entity ); + } + else + { + FieldInfo fieldInfo = memberInfo as FieldInfo; + + entityReferenceProperty = fieldInfo.GetValue( entity ); + } + + Type entityReferencePropertyType = entityReferenceProperty.GetType(); + MethodInfo methodOfMainProperty = entityReferencePropertyType.GetMethod( "AttachSource" ); + + // Get the EntitySet + var entityReference = entityReferencePropertyType.GetGenericArguments().First(); + // Get the EntitySet from the foreign key type + var entitySet = _context.EntitySets[ entityReference ]; + + methodOfMainProperty.Invoke( entityReferenceProperty, new object[] { entitySet } ); + + } + + continue; + } object value; // Handle populating simple members on the current type if ( dictionary.TryGetValue( memberName, out value ) ) { - SetMemberValue( member, instance, value ); + SetMemberValue( memberInfo, entity, value ); } else { - Type memberType = GetMemberType( member ); + Type memberType = GetMemberType( memberInfo ); // Handle populating complex members on the current type if ( memberType.IsClass || memberType.IsInterface ) @@ -183,7 +234,7 @@ internal object Materialize( IDictionary dictionary, object inst if ( parentInstance.GetType() == memberType ) { // Then this must be a 'parent' to the current type - SetMemberValue( member, instance, parentInstance ); + SetMemberValue( memberInfo, entity, parentInstance ); } } @@ -195,7 +246,7 @@ internal object Materialize( IDictionary dictionary, object inst // Try to get the value of the complex member. If the member // hasn't been initialized, then this will return null. - object nestedInstance = GetMemberValue( member, instance ); + object nestedInstance = GetMemberValue( memberInfo, entity ); var genericCollectionType = typeof( IEnumerable<> ); var isEnumerableType = memberType.IsGenericType && genericCollectionType.IsAssignableFrom( memberType.GetGenericTypeDefinition() ) @@ -219,7 +270,7 @@ internal object Materialize( IDictionary dictionary, object inst if ( isEnumerableType ) { var innerType = memberType.GetGenericArguments().FirstOrDefault() ?? memberType.GetElementType(); - nestedInstance = MaterializeCollection( innerType, newDictionary, nestedInstance, instance ); + nestedInstance = MaterializeCollection( innerType, newDictionary, nestedInstance, entity ); } else { @@ -229,16 +280,16 @@ internal object Materialize( IDictionary dictionary, object inst } else { - nestedInstance = Materialize( newDictionary, nestedInstance, instance ); + nestedInstance = Materialize( newDictionary, nestedInstance, entity ); } } - SetMemberValue( member, instance, nestedInstance ); + SetMemberValue( memberInfo, entity, nestedInstance ); } } } - return instance; + return entity; } /// @@ -335,21 +386,10 @@ private object CreateInstance( Type type ) return string.Empty; } - // TJT: Not needed. To be removed... - - //if ( TypeActivators.Count > 0 ) - //{ - // foreach ( var typeActivator in TypeActivators.OrderBy( ta => ta.Order ) ) - // { - // if ( typeActivator.CanCreate( type ) ) - // { - // return typeActivator.Create( type ); - // } - // } - //} - var instance = Activator.CreateInstance( type ); + + return instance; } @@ -484,9 +524,9 @@ private static object ConvertValuesTypeToMembersType( object value, string membe /// /// Type /// Dictionary of member names and member info objects - private Dictionary CreateFieldAndPropertyInfoDictionary( Type type ) + private Dictionary CreateFieldAndPropertyInfoDictionary( Type type ) { - var dictionary = new Dictionary(); + var dictionary = new Dictionary(); var properties = type.GetProperties(); @@ -612,7 +652,7 @@ private IEnumerable GetIdentifiers( Type type ) /// /// Type /// Dictionary of a type's property names and their corresponding PropertyInfo - public Dictionary GetFieldsAndProperties( Type type ) + public Dictionary GetFieldsAndProperties( Type type ) { var typeMap = TypeMapCache.GetOrAdd( type, CreateTypeMap( type ) ); diff --git a/Achilles.Entities.Sqlite/Querying/EntityTypeMap.cs b/Achilles.Entities.Sqlite/Querying/EntityTypeMap.cs index ebd279b..647b0ec 100644 --- a/Achilles.Entities.Sqlite/Querying/EntityTypeMap.cs +++ b/Achilles.Entities.Sqlite/Querying/EntityTypeMap.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; +using System.Reflection; #endregion @@ -25,7 +26,7 @@ internal class EntityTypeMap /// Entity Type to materialize. /// The s identifiers. /// The s properties and fields. - public EntityTypeMap( Type type, IEnumerable identifiers, Dictionary propertiesAndFields ) + public EntityTypeMap( Type type, IEnumerable identifiers, Dictionary propertiesAndFields ) { Type = type; Identifiers = identifiers; @@ -45,6 +46,7 @@ public EntityTypeMap( Type type, IEnumerable identifiers, Dictionary /// Property/field names and their corresponding PropertyInfo/FieldInfo objects /// - public Dictionary PropertiesAndFieldsInfo; + //public Dictionary PropertiesAndFieldsInfo; + public Dictionary PropertiesAndFieldsInfo; } } diff --git a/Achilles.Entities.Sqlite/Relational/Storage/IRelationalConnection.cs b/Achilles.Entities.Sqlite/Relational/Storage/IRelationalConnection.cs index 8b3327f..ee8e34a 100644 --- a/Achilles.Entities.Sqlite/Relational/Storage/IRelationalConnection.cs +++ b/Achilles.Entities.Sqlite/Relational/Storage/IRelationalConnection.cs @@ -25,7 +25,7 @@ public interface IRelationalConnection : IDisposable /// /// Opens the connection to the database. /// - /// True if the connection not previously opened. + /// True if the connection was not previously opened. bool Open(); Task OpenAsync( CancellationToken cancellationToken );