diff --git a/Achilles.Entities.Sqlite/IEntitySet.cs b/Achilles.Entities.Sqlite/IEntitySet.cs index b7e6f45..6fe8518 100644 --- a/Achilles.Entities.Sqlite/IEntitySet.cs +++ b/Achilles.Entities.Sqlite/IEntitySet.cs @@ -29,6 +29,6 @@ public interface IEntitySet /// Type EntityType { get; } - IEnumerable GetSource( TSource source ) where TSource : class; + //IEnumerable GetSource( TSource source ) where TSource : class; } } diff --git a/Achilles.Entities.Sqlite/Linq/EntityReferenceOfT.cs b/Achilles.Entities.Sqlite/Linq/EntityReferenceOfT.cs index b15d7b3..11ff013 100644 --- a/Achilles.Entities.Sqlite/Linq/EntityReferenceOfT.cs +++ b/Achilles.Entities.Sqlite/Linq/EntityReferenceOfT.cs @@ -17,7 +17,7 @@ namespace Achilles.Entities.Linq { - public sealed class EntityReference : IEntityReference, IEntityReference + public sealed class EntityReference : IEntityReference, IEntityReference, IEntityReferenceSource where TEntity : class { #region Private Fields @@ -40,6 +40,8 @@ public EntityReference() #region Public API + public bool IsLoaded => _isLoaded; + public TEntity Entity { get @@ -54,10 +56,11 @@ public TEntity Entity } } - // TJT: Make a direct internal property (Source) reference through a compiled lambda expression - public void AttachSource( IEnumerable source ) + public bool HasSource => (_source != null); + + public void SetSource( IEnumerable source ) where TSource : class { - _source = source; + _source = source as IEnumerable; } #endregion diff --git a/Achilles.Entities.Sqlite/Linq/IEntityReference.cs b/Achilles.Entities.Sqlite/Linq/IEntityReference.cs index 3910da2..cd05bf4 100644 --- a/Achilles.Entities.Sqlite/Linq/IEntityReference.cs +++ b/Achilles.Entities.Sqlite/Linq/IEntityReference.cs @@ -8,8 +8,6 @@ #endregion -using System.Collections.Generic; - namespace Achilles.Entities.Linq { /// @@ -17,6 +15,6 @@ namespace Achilles.Entities.Linq /// internal interface IEntityReference { - //void AttachSource( IEnumerable source ) where TEntityRef: class; + // No API } } diff --git a/Achilles.Entities.Sqlite/Linq/IEntityReferenceOfT.cs b/Achilles.Entities.Sqlite/Linq/IEntityReferenceOfT.cs index b512c28..abc44da 100644 --- a/Achilles.Entities.Sqlite/Linq/IEntityReferenceOfT.cs +++ b/Achilles.Entities.Sqlite/Linq/IEntityReferenceOfT.cs @@ -10,7 +10,7 @@ namespace Achilles.Entities.Linq { - public interface IEntityReference + public interface IEntityReference where TEntity : class { /// /// Gets the wrapped entity. diff --git a/Achilles.Entities.Sqlite/Linq/IEntityReferenceSource.cs b/Achilles.Entities.Sqlite/Linq/IEntityReferenceSource.cs new file mode 100644 index 0000000..600b566 --- /dev/null +++ b/Achilles.Entities.Sqlite/Linq/IEntityReferenceSource.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Achilles.Entities.Linq +{ + internal interface IEntityReferenceSource + { + bool HasSource { get; } + + void SetSource( IEnumerable source ) where TSource : class; + } +} diff --git a/Achilles.Entities.Sqlite/Modelling/EntityModel.cs b/Achilles.Entities.Sqlite/Modelling/EntityModel.cs index dccf1d5..9f05e44 100644 --- a/Achilles.Entities.Sqlite/Modelling/EntityModel.cs +++ b/Achilles.Entities.Sqlite/Modelling/EntityModel.cs @@ -22,47 +22,47 @@ public class EntityModel : IEntityModel { #region Private Fields - private readonly EntityMappingCollection _entityMappings; + private DataContext _context; + private EntityMappingCollection _entityMappings; #endregion #region Constructor(s) - public EntityModel( EntityMappingCollection entityMappings ) + public EntityModel( DataContext context ) { - _entityMappings = entityMappings ?? throw new ArgumentNullException( nameof( entityMappings ) ); + _context = context; + _entityMappings = new EntityMappingCollection( this ); } #endregion #region Public API + /// public IReadOnlyCollection EntityMappings => _entityMappings.Values as IReadOnlyCollection; - //public IEntityMapping GetOrAddEntityMapping( Type entityType ) - //{ - // IEntityMapping mapping; - - // if ( !_entityMappings.TryGetValue( entityType, out mapping ) ) - // { - // var EntityMappingType = typeof( EntityMapping<> ); - // var mapType = EntityMappingType.MakeGenericType( entityType ); - - // mapping = Activator.CreateInstance( mapType ) as IEntityMapping; - - // _entityMappings[ entityType ] = mapping; - // } - - // return mapping; - //} - + /// public IEntityMapping GetEntityMapping() where TEntity : class { return GetEntityMapping( typeof( TEntity ) ); } + /// public IEntityMapping GetEntityMapping( Type entityType ) => _entityMappings.GetEntityMapping( entityType ); #endregion + + #region Internal + + internal IEntityMapping GetOrAddEntityMapping( Type entityType ) + => _entityMappings.GetOrAddEntityMapping( entityType ); + + internal void TryAddEntityMapping( Type entityType, IEntityMapping entityMapping ) + => _entityMappings.TryAddEntityMapping( entityType, entityMapping ); + + internal DataContext DataContext => _context; + + #endregion } } diff --git a/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs b/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs index 365abb0..bd0d292 100644 --- a/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs +++ b/Achilles.Entities.Sqlite/Modelling/EntityModelBuilder.cs @@ -19,10 +19,12 @@ namespace Achilles.Entities.Modelling { /// - /// implementation of the fluent builder inteface for relational database modelling. + /// implementation of the fluent builder inteface for entity data modelling. /// public class EntityModelBuilder : IEntityModelBuilder { + private DataContext _context; + #region Constructor(s) /// @@ -30,15 +32,15 @@ public class EntityModelBuilder : IEntityModelBuilder /// public EntityModelBuilder() { - EntityMappings = new EntityMappingCollection(); } #endregion - #region Public Properties + #region Private Properties - /// - private EntityMappingCollection EntityMappings { get; } + private EntityModel Model { get; set; } + + //private EntityMappingCollection EntityMappings => Model.EntityMappings; #endregion @@ -47,7 +49,7 @@ public EntityModelBuilder() /// public void Entity( Action> action ) where TEntity : class { - var builder = new EntityMappingBuilder( EntityMappings ); + var builder = new EntityMappingBuilder( Model ); action( builder ); @@ -57,25 +59,26 @@ public void Entity( Action> action ) whe /// public IEntityModel Build( DataContext context ) { + _context = context; + + Model = new EntityModel( context ); + // TJT: Should all entities be added in the user's data context constructor? // What should happen if an entity is referenced during model building that is // not in the context? foreach ( var entitySetType in context.EntitySets.Keys ) { - EntityMappings.GetOrAddEntityMapping( entitySetType ); + Model.GetOrAddEntityMapping( entitySetType ); } context.OnModelBuilding( this ); - // TODO: Model Validation... - // The model entity mappings have now been configured. // Resolve Entity references and validate the model. - ResolveMappingsAndValidateModel(); - return new EntityModel( EntityMappings ); + return Model; } #endregion @@ -86,12 +89,18 @@ private void AddEntityMapping( EntityMappingBuilder entityMapp { var EntityMapping = entityMappingBuilder.Build(); - EntityMappings.TryAddEntityMapping( typeof( TEntity ), EntityMapping ); + Model.TryAddEntityMapping( typeof( TEntity ), EntityMapping ); } private void ResolveMappingsAndValidateModel() { + foreach ( var entityMapping in Model.EntityMappings ) + { + // TJT: Better name? + entityMapping.Compile(); + } + // TODO: Model Validation... } #endregion diff --git a/Achilles.Entities.Sqlite/Modelling/IEntityModel.cs b/Achilles.Entities.Sqlite/Modelling/IEntityModel.cs index 7574fb5..f429388 100644 --- a/Achilles.Entities.Sqlite/Modelling/IEntityModel.cs +++ b/Achilles.Entities.Sqlite/Modelling/IEntityModel.cs @@ -26,8 +26,6 @@ public interface IEntityModel /// A read only collection of . IReadOnlyCollection EntityMappings { get; } - //IEntityMapping GetOrAddEntityMapping( Type entityType ); - /// /// Gets an entity mapping by entity type. /// @@ -36,7 +34,7 @@ public interface IEntityModel IEntityMapping GetEntityMapping( Type entityType ); /// - /// TODO + /// Gets an entity mapping by the generic TEntity type. /// /// /// diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/ColumnAccessor.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/ColumnAccessor.cs new file mode 100644 index 0000000..dd49e4b --- /dev/null +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/ColumnAccessor.cs @@ -0,0 +1,30 @@ +#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.Accessors +{ + internal class ColumnAccessor : MemberAccessor + where TEntity : class + { + private readonly MemberInfo _columnInfo; + + public ColumnAccessor( MemberInfo columnInfo ) + : base( columnInfo ) + { + _columnInfo = columnInfo; + } + } +} diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/EntityCollectionAccessor.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/EntityCollectionAccessor.cs new file mode 100644 index 0000000..aca1db2 --- /dev/null +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/EntityCollectionAccessor.cs @@ -0,0 +1,28 @@ +#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 + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Achilles.Entities.Modelling.Mapping.Accessors +{ + internal class EntityCollectionAccessor : MemberAccessor + { + MemberInfo _entityCollectionInfo; + + public EntityCollectionAccessor( MemberInfo entityReferenceInfo ) + : base( entityReferenceInfo ) + { + _entityCollectionInfo = entityReferenceInfo; + } + } +} diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/EntityReferenceAccessor.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/EntityReferenceAccessor.cs new file mode 100644 index 0000000..13e864e --- /dev/null +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/EntityReferenceAccessor.cs @@ -0,0 +1,62 @@ +#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 Achilles.Entities.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +#endregion + +namespace Achilles.Entities.Modelling.Mapping.Accessors +{ + internal class EntityReferenceAccessor : MemberAccessor + where TEntity : class + { + MemberInfo _entityReferenceInfo; + EntityReference _entityReference; + + public EntityReferenceAccessor( MemberInfo entityReferenceInfo ) + : base( entityReferenceInfo ) + { + _entityReferenceInfo = entityReferenceInfo; + } + + public Type EntityType => typeof( TEntity ); + + public override object GetValue( TEntity entity ) + { + return base.GetValue( entity ); + } + + public override void SetValue( TEntity entity, object value ) + { + // The base.GetValue gets the entityReference<> class + var entityReference = base.GetValue( entity ) as IEntityReferenceSource; + + //Type entityReferencePropertyType = entityReferenceProperty.GetType(); + + //// Get the EntitySet + //var entityReference = entityReferencePropertyType.GetGenericArguments().First(); + //var entitySet = _context.EntitySets[ entityReference ]; + + + if ( !entityReference.HasSource ) + { + entityReference.SetSource( value as IEnumerable ); + } + + //base.SetValue( entity, value ); + } + } +} diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/ForeignKeyAccessor.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/ForeignKeyAccessor.cs new file mode 100644 index 0000000..8c2225b --- /dev/null +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/ForeignKeyAccessor.cs @@ -0,0 +1,30 @@ +#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.Accessors +{ + public class ForeignKeyAccessor : MemberAccessor + where TEntity : class + { + private readonly MemberInfo _foreignKeyInfo; + + public ForeignKeyAccessor( MemberInfo foreignKeyInfo ) + : base( foreignKeyInfo ) + { + _foreignKeyInfo = foreignKeyInfo; + } + } +} diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/ColumnAccessor.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/MemberAccessor.cs similarity index 76% rename from Achilles.Entities.Sqlite/Modelling/Mapping/ColumnAccessor.cs rename to Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/MemberAccessor.cs index 1a7738d..cc11807 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/ColumnAccessor.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Accessors/MemberAccessor.cs @@ -11,38 +11,35 @@ #region Namespaces using System; -using System.Linq; using System.Linq.Expressions; using System.Reflection; #endregion -namespace Achilles.Entities.Modelling.Mapping +namespace Achilles.Entities.Modelling.Mapping.Accessors { - internal class ColumnAccessor : MemberAccessor - where TEntity : class + public abstract class MemberAccessor { - private readonly MemberInfo _columnInfo; + protected Func _getter; + protected Action _setter; - private Func _getter; - private Action _setter; - - public ColumnAccessor( MemberInfo columnInfo ) - : base( columnInfo ) + public MemberAccessor( MemberInfo memberInfo ) { - _columnInfo = columnInfo; + MemberInfo = memberInfo ?? throw new System.ArgumentNullException( nameof( memberInfo ) ); CreateGetter(); CreateSetter(); } - public override object GetValue( TMember entity ) => _getter( entity as TEntity ); + protected MemberInfo MemberInfo { get; } + + public virtual object GetValue( TEntity entity ) => _getter( entity ); - public override void SetValue( TMember entity, object value ) => _setter( entity as TEntity, value ); + public virtual void SetValue( TEntity entity, object value ) => _setter( entity, value ); private void CreateGetter() { - if ( _columnInfo is PropertyInfo propertyInfo ) + if ( MemberInfo is PropertyInfo propertyInfo ) { ParameterExpression instance = Expression.Parameter( typeof( TEntity ), "instance" ); @@ -52,7 +49,7 @@ private void CreateGetter() _getter = Expression.Lambda>( conversion, parameters ).Compile(); } - else if ( _columnInfo is FieldInfo field ) + else if ( MemberInfo is FieldInfo field ) { ParameterExpression instance = Expression.Parameter( typeof( TEntity ), "instance" ); @@ -66,10 +63,10 @@ private void CreateGetter() private void CreateSetter() { - if ( _columnInfo is PropertyInfo propertyInfo ) + if ( MemberInfo is PropertyInfo propertyInfo ) { var columnPropertySetMethod = propertyInfo.GetSetMethod(); - var setMethodParameterType = columnPropertySetMethod.GetParameters().First().ParameterType; + var setMethodParameterType = columnPropertySetMethod.GetParameters()[0].ParameterType; var entityInstanceParameter = Expression.Parameter( typeof( TEntity ), "instance" ); var valueParameter = Expression.Parameter( typeof( object ), "value" ); @@ -81,7 +78,7 @@ private void CreateSetter() _setter = Expression.Lambda>( body, parameters ).Compile(); } - else if ( _columnInfo is FieldInfo field ) + else if ( MemberInfo is FieldInfo field ) { var instanceParameter = Expression.Parameter( typeof( TEntity ), "instance" ); var valueParameter = Expression.Parameter( typeof( object ), "value" ); diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs index 08ad67a..6e1a378 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/Builders/EntityMappingBuilder.cs @@ -29,7 +29,7 @@ public class EntityMappingBuilder : IEntityMappingBuilder wher { #region Private Fields - private EntityMappingCollection _entityMappings; + private EntityModel _model; private readonly List _columnMappingBuilders = new List(); private readonly List _indexMappingBuilders = new List(); @@ -43,11 +43,11 @@ public class EntityMappingBuilder : IEntityMappingBuilder wher /// /// Constructs a new EntityMappingBuilder instance. /// - public EntityMappingBuilder( EntityMappingCollection entityMappings ) + public EntityMappingBuilder( EntityModel model ) { - _entityMappings = entityMappings; + _model = model; - EntityMapping = entityMappings.GetOrAddEntityMapping( EntityType ); + EntityMapping = model.GetOrAddEntityMapping( EntityType ); } #endregion @@ -168,7 +168,7 @@ public IEntityMapping Build() if ( t != EntityType ) { - var foreignKeyConstraintMapping = _entityMappings.GetOrAddEntityMapping( t ); + var foreignKeyConstraintMapping = _model.GetOrAddEntityMapping( t ); foreignKeyConstraintMapping.ForeignKeyMappings.Add( hasManyMapping.ForeignKeyMapping ); } else diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs index 8e755bf..323f377 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs @@ -10,6 +10,7 @@ #region Namespaces +using Achilles.Entities.Modelling.Mapping.Accessors; using Achilles.Entities.Relational.Modelling.Mapping; using System; using System.Collections.Generic; @@ -29,15 +30,24 @@ public class EntityMapping : IEntityMapping where TEntity : class { #region Fields + private EntityModel _model; + private Dictionary _columnProperties; private Dictionary _foreignKeyProperties; private Dictionary _relationshipProperties; - //private Dictionary> ColumnGetters = new Dictionary>(); - //private Dictionary> ColumnSetters = new Dictionary>(); + private Dictionary> ColumnAccessors + = new Dictionary>(); + + private Dictionary> ForeignKeyAccessors + = new Dictionary>(); - private Dictionary ColumnAccessors = new Dictionary(); + private Dictionary>> EntityReferenceAccessors + = new Dictionary>>(); + private Dictionary>> EntityCollectionAccessors + = new Dictionary>>(); + #endregion #region Constructor(s) @@ -45,8 +55,10 @@ public class EntityMapping : IEntityMapping where TEntity : class /// /// Constructs a new instance of . /// - public EntityMapping() + public EntityMapping( EntityModel model ) { + _model = model; + InitializePropertyAndFieldMappings(); } @@ -54,9 +66,14 @@ public EntityMapping() #region Public Properties - public object GetColumn( T entity, string propertyName ) where T : class => ColumnAccessors[ propertyName ].GetValue( entity as TEntity ); + public object GetColumn( T entity, string propertyName ) where T : class + => ColumnAccessors[ propertyName ].GetValue( entity as TEntity ); - public void SetColumn( T entity, string propertyName, object value ) where T: class => ColumnAccessors[ propertyName ].SetValue( entity as TEntity, value ); + public void SetColumn( T entity, string propertyName, object value ) where T: class + => ColumnAccessors[ propertyName ].SetValue( entity as TEntity, value ); + + public void SetEntityReference( T Entity, string propertyName, object source ) where T : class + => EntityReferenceAccessors[ propertyName ].SetValue( Entity as TEntity, source ); public List ColumnMappings { get; set; } = new List(); @@ -76,8 +93,14 @@ public EntityMapping() #endregion + #region Internal Properties + + internal EntitySet EntitySet => (EntitySet< TEntity>)_model.DataContext.EntitySets[ EntityType ]; + + #endregion + #region Public Methods - + public void Compile() { _columnProperties = ColumnMappings.ToDictionary( m => m.ColumnName, m => m.ColumnInfo, IsCaseSensitive? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase ); @@ -87,6 +110,8 @@ public void Compile() #endregion + + #region Private Methods private void CreateAccessors() @@ -94,13 +119,32 @@ private void CreateAccessors() // Columns... foreach ( var columnMapping in ColumnMappings ) { - ColumnAccessors.Add( columnMapping.PropertyName, new ColumnAccessor( columnMapping.ColumnInfo ) ); + ColumnAccessors.Add( columnMapping.PropertyName, new ColumnAccessor( columnMapping.ColumnInfo ) ); } - } + // Foreign Keys... + foreach ( var foreignKeyMapping in ForeignKeyMappings ) + { + ForeignKeyAccessors.Add( foreignKeyMapping.PropertyName, new ForeignKeyAccessor( foreignKeyMapping.ForeignKeyProperty ) ); + } - private void CreateRelationshipSetters() - { + // Relationship Mappings... + foreach ( var relationshipMapping in RelationshipMappings ) + { + if ( relationshipMapping.IsMany ) + { + EntityCollectionAccessors.Add( + relationshipMapping.RelationshipProperty.Name, + new EntityCollectionAccessor>( relationshipMapping.RelationshipProperty ) ); + } + else + { + EntityReferenceAccessors.Add( + relationshipMapping.RelationshipProperty.Name, + new EntityReferenceAccessor>( relationshipMapping.RelationshipProperty ) ); + + } + } } /// diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMappingCollection.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMappingCollection.cs index 7c3ebfb..aa67b76 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMappingCollection.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/EntityMappingCollection.cs @@ -22,16 +22,26 @@ namespace Achilles.Entities.Modelling.Mapping /// /// Collection class for entity mappings. /// - public class EntityMappingCollection //: IDictionary + internal class EntityMappingCollection { #region Fields + private EntityModel _model; private ConcurrentDictionary _entityMappings = new ConcurrentDictionary(); #endregion + #region Constructor(s) + + public EntityMappingCollection( EntityModel model ) + { + _model = model; + } + + #endregion + /// - /// TODO: + /// Gets an entity type if it is already in the collection or creates and adds the entity to the collection. /// /// /// @@ -39,15 +49,12 @@ public IEntityMapping GetOrAddEntityMapping( Type entityType ) { if ( !_entityMappings.TryGetValue( entityType, out IEntityMapping mapping ) ) { - var EntityMappingType = typeof( EntityMapping<> ); - var mappingType = EntityMappingType.MakeGenericType( entityType ); + var entityMappingType = typeof( EntityMapping<> ); + var genericEntityMappingType = entityMappingType.MakeGenericType( entityType ); - mapping = Activator.CreateInstance( mappingType ) as IEntityMapping; + mapping = Activator.CreateInstance( genericEntityMappingType, _model ) as IEntityMapping; _entityMappings[ entityType ] = mapping; - - // TJT: Review. Perhaps this is better done elsewhere - mapping.Compile(); } return mapping; @@ -55,10 +62,12 @@ public IEntityMapping GetOrAddEntityMapping( Type entityType ) public void TryAddEntityMapping( Type entityType, IEntityMapping entityMapping ) { + // TJT: Review this method. + if ( _entityMappings.TryAdd( entityType, entityMapping ) ) { // Compile the defined mapping into a column <-> member info lookup dictionary. - entityMapping.Compile(); + //entityMapping.Compile(); } } @@ -72,68 +81,9 @@ public IEntityMapping GetEntityMapping( Type entityType ) throw new ArgumentException( nameof( entityType ) ); } - //#region IDictionary Implementation through _EntityMappingpings - - //public IEntityMapping this[ Type key ] { get => ((IDictionary)_entityMappings)[ key ]; set => ((IDictionary)_entityMappings)[ key ] = value; } - public ICollection Keys => ((IDictionary)_entityMappings).Keys; public ICollection Values => ((IDictionary)_entityMappings).Values; - //public int Count => ((IDictionary)_entityMappings).Count; - - //public bool IsReadOnly => ((IDictionary)_entityMappings).IsReadOnly; - - //public void Add( KeyValuePair item ) - //{ - // ((IDictionary)_entityMappings).Add( item ); - //} - - //public void Clear() - //{ - // ((IDictionary)_entityMappings).Clear(); - //} - - //public bool Contains( KeyValuePair item ) - //{ - // return ((IDictionary)_entityMappings).Contains( item ); - //} - - //public bool ContainsKey( Type key ) - //{ - // return ((IDictionary)_entityMappings).ContainsKey( key ); - //} - - //public void CopyTo( KeyValuePair[] array, int arrayIndex ) - //{ - // ((IDictionary)_entityMappings).CopyTo( array, arrayIndex ); - //} - - //public IEnumerator> GetEnumerator() - //{ - // return ((IDictionary)_entityMappings).GetEnumerator(); - //} - - //public bool Remove( Type key ) - //{ - // return ((IDictionary)_entityMappings).Remove( key ); - //} - - //public bool Remove( KeyValuePair item ) - //{ - // return ((IDictionary)_entityMappings).Remove( item ); - //} - - //public bool TryGetValue( Type key, out IEntityMapping value ) - //{ - // return ((IDictionary)_entityMappings).TryGetValue( key, out value ); - //} - - ////IEnumerator IEnumerable.GetEnumerator() - ////{ - //// return ((IDictionary)_entityMappings).GetEnumerator(); - ////} - - //#endregion } } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs index ec474f6..3f49987 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs @@ -42,6 +42,8 @@ public interface IEntityMapping void SetColumn( T entity, string propertyName, object value ) where T : class; + void SetEntityReference( T entity, string propertyName, object value ) where T : class; + void Compile(); } } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs index 82976f7..92c3abe 100644 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs +++ b/Achilles.Entities.Sqlite/Modelling/Mapping/IForeignKeyMapping.cs @@ -30,7 +30,7 @@ public interface IForeignKeyMapping string PropertyName { get; } /// - /// Gets teh Reference key . + /// Gets the reference key property or field. /// MemberInfo ReferenceKeyProperty { get; set; } diff --git a/Achilles.Entities.Sqlite/Modelling/Mapping/MemberAccessor.cs b/Achilles.Entities.Sqlite/Modelling/Mapping/MemberAccessor.cs deleted file mode 100644 index c2d71b0..0000000 --- a/Achilles.Entities.Sqlite/Modelling/Mapping/MemberAccessor.cs +++ /dev/null @@ -1,32 +0,0 @@ -#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 abstract class MemberAccessor - { - public MemberAccessor( MemberInfo memberInfo ) - { - MemberInfo = memberInfo ?? throw new System.ArgumentNullException( nameof( memberInfo ) ); - } - - protected MemberInfo MemberInfo { get; } - - public abstract object GetValue( TMember member ); - - public abstract void SetValue( TMember member, object value ); - } -} diff --git a/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs b/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs index 33a8f49..2cdcfb3 100644 --- a/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs +++ b/Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs @@ -141,6 +141,7 @@ internal object Materialize( IDictionary dictionary, object enti if ( entity.GetType().IsPrimitive || entity is string ) { object value; + if ( !dictionary.TryGetValue( "$", out value ) ) { throw new InvalidCastException( "For lists of primitive types, include $ as the name of the property" ); @@ -151,11 +152,6 @@ internal object Materialize( IDictionary dictionary, object enti return entity; } - // 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 ) @@ -163,49 +159,6 @@ internal object Materialize( IDictionary dictionary, object enti var memberName = fieldOrProperty.Key.ToLower(); var memberInfo = 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; - - // Gets the property EntityReference class instance - entityReferenceProperty = propertyInfo.GetValue( entity ); - } - else - { - FieldInfo fieldInfo = memberInfo as FieldInfo; - - entityReferenceProperty = fieldInfo.GetValue( entity ); - } - - Type entityReferencePropertyType = entityReferenceProperty.GetType(); - - MethodInfo attachSourceMethod = entityReferencePropertyType.GetMethod( "AttachSource" ); - - // Get the EntitySet - var entityReference = entityReferencePropertyType.GetGenericArguments().First(); - var entitySet = _context.EntitySets[ entityReference ]; - - attachSourceMethod.Invoke( entityReferenceProperty, new object[] { entitySet } ); - } - - continue; - } - object value; // Handle populating simple members on the current type @@ -288,9 +241,42 @@ internal object Materialize( IDictionary dictionary, object enti } } + SetDeferredLoading( entity ); + return entity; } + private void SetDeferredLoading ( object entity ) + { + // Once we have the entity instance we can attach an entity set source to the instance relationship properties + var entityMapping = _model.GetEntityMapping( entity.GetType() ); + + var relationshipMappings = entityMapping.RelationshipMappings; + + foreach ( var relationshipMapping in relationshipMappings ) + { + var foreignKey = relationshipMapping.ForeignKeyMapping; + + if ( relationshipMapping.IsMany ) + { + //entityMapping.SetEntityCollection( + // entity, + // relationshipMapping.RelationshipProperty.Name, + // relationshipMapping.ForeignKeyMapping ); + } + else + { + entityMapping.SetEntityReference( + entity, + relationshipMapping.RelationshipProperty.Name, + relationshipMapping.ForeignKeyMapping ); + } + + continue; + } + + } + /// /// Populates the given instance's properties where the IDictionary key property names /// match the type's property names case insensitively.