Skip to content

Commit

Permalink
EntityCollection lazy loading added (see #1, #12)
Browse files Browse the repository at this point in the history
  • Loading branch information
ToddThomson committed Aug 25, 2018
1 parent 5a4ee28 commit 1a9fda2
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 36 deletions.
86 changes: 67 additions & 19 deletions Achilles.Entities.Sqlite/Linq/EntityCollectionOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,24 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;

#endregion

namespace Achilles.Entities.Linq
{
public sealed class EntityCollection<TEntity> : IEntityCollection<TEntity>, IEntityCollection, ICollection<TEntity>, IListSource
public sealed class EntityCollection<TEntity> : IEntityCollection<TEntity>, IEntitySource, IEntityCollection, ICollection<TEntity>, IListSource
where TEntity : class
{
#region Private Fields

private IEnumerable<TEntity> _source;
private HashSet<TEntity> _entities;

private EntitySet<TEntity> _source;
private List<TEntity> _entities;

private object _foreignKeyValue;
private string _referenceKey;

private bool _isLoaded;

#endregion
Expand All @@ -35,59 +40,79 @@ public sealed class EntityCollection<TEntity> : IEntityCollection<TEntity>, IEnt

public EntityCollection()
{
var test = 6;
}

#endregion

#region IEntityCollection Implementation
#region Internal IEntitySource Implementation

bool IEntitySource.HasSource => (_source != null);

void IEntitySource.SetSource( IEntitySet source, string referenceKey, object foreignKeyValue )
{
_source = source as EntitySet<TEntity>;
_referenceKey = referenceKey;
_foreignKeyValue = foreignKeyValue;
}

#endregion

#region Private Properties

void IEntityCollection<TEntity>.AttachSource( IEnumerable<TEntity> source )
private List<TEntity> Entities
{
_source = source;
get
{
if ( !_isLoaded )
{
Load();
}

return _entities;
}
}

#endregion

#region ICollection<TEntity> Implementation

public int Count => ((ICollection<TEntity>)_entities).Count;
public int Count => ((ICollection<TEntity>)Entities).Count;

public bool IsReadOnly => ((ICollection<TEntity>)_entities).IsReadOnly;
public bool IsReadOnly => ((ICollection<TEntity>)Entities).IsReadOnly;

public void Add( TEntity item )
{
((ICollection<TEntity>)_entities).Add( item );
((ICollection<TEntity>)Entities).Add( item );
}

public void Clear()
{
((ICollection<TEntity>)_entities).Clear();
((ICollection<TEntity>)Entities).Clear();
}

public bool Contains( TEntity item )
{
return ((ICollection<TEntity>)_entities).Contains( item );
return ((ICollection<TEntity>)Entities).Contains( item );
}

public void CopyTo( TEntity[] array, int arrayIndex )
{
((ICollection<TEntity>)_entities).CopyTo( array, arrayIndex );
((ICollection<TEntity>)Entities).CopyTo( array, arrayIndex );
}

public IEnumerator<TEntity> GetEnumerator()
{
return ((ICollection<TEntity>)_entities).GetEnumerator();
return ((ICollection<TEntity>)Entities).GetEnumerator();
}

public bool Remove( TEntity item )
{
return ((ICollection<TEntity>)_entities).Remove( item );
return ((ICollection<TEntity>)Entities).Remove( item );
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((ICollection<TEntity>)_entities).GetEnumerator();
return ((ICollection<TEntity>)Entities).GetEnumerator();
}

#endregion
Expand All @@ -96,12 +121,35 @@ IEnumerator IEnumerable.GetEnumerator()

bool IListSource.ContainsListCollection => throw new NotImplementedException();

public bool IsLoaded => _isLoaded;

IList IListSource.GetList()
{
throw new NotImplementedException();
}


#endregion

#region Private Implementation

private void Load()
{
if ( !_isLoaded && _source != null )
{
_entities = _source.Where( FilterByForeignKeyPredicate( _foreignKeyValue ) ).ToList();
_isLoaded = true;
}
}

private Expression<Func<TEntity, bool>> FilterByForeignKeyPredicate( object foreignKey )
{
var entity = Expression.Parameter( typeof( TEntity ), "e" );
var referenceKey = Expression.Property( entity, _referenceKey );
var value = Expression.Constant( foreignKey, referenceKey.Type );
var body = Expression.Equal( referenceKey, value );

return Expression.Lambda<Func<TEntity, bool>>( body, entity );
}

#endregion
}
Expand Down
6 changes: 3 additions & 3 deletions Achilles.Entities.Sqlite/Linq/EntityReferenceOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

namespace Achilles.Entities.Linq
{
public sealed class EntityReference<TEntity> : IEntityReference<TEntity>, IEntityReference, IEntityReferenceSource
public sealed class EntityReference<TEntity> : IEntityReference<TEntity>, IEntityReference, IEntitySource
where TEntity : class
{
#region Private Fields
Expand Down Expand Up @@ -66,9 +66,9 @@ public TEntity Value

#region Internal IEntityReferenceSource API

bool IEntityReferenceSource.HasSource => (_source != null);
bool IEntitySource.HasSource => (_source != null);

void IEntityReferenceSource.SetSource( IEntitySet source, string referenceKey, object foreignKeyValue )
void IEntitySource.SetSource( IEntitySet source, string referenceKey, object foreignKeyValue )
{
_source = source as EntitySet<TEntity>;
_referenceKey = referenceKey;
Expand Down
4 changes: 2 additions & 2 deletions Achilles.Entities.Sqlite/Linq/IEntityCollectionOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

namespace Achilles.Entities.Linq
{
internal interface IEntityCollection<TEntity>
public interface IEntityCollection<TEntity>
{
void AttachSource( IEnumerable<TEntity> source );
bool IsLoaded { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace Achilles.Entities.Linq
{
internal interface IEntityReferenceSource
internal interface IEntitySource
{
bool HasSource { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,60 @@

#endregion

#region Namespaces

using Achilles.Entities.Linq;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

#endregion

namespace Achilles.Entities.Modelling.Mapping.Accessors
{
internal class EntityCollectionAccessor<TEntity, TValue> : MemberAccessor<TEntity,TValue>
where TEntity : class
{
MemberInfo _entityCollectionInfo;
IEntityMapping _entityMapping;
EntityMapping<TEntity> _entityMapping;

public EntityCollectionAccessor( IEntityMapping entityMapping, MemberInfo entityReferenceInfo )
: base( entityReferenceInfo )
public EntityCollectionAccessor( EntityMapping<TEntity> entityMapping, MemberInfo entityCollectionInfo )
: base( entityCollectionInfo )
{
_entityMapping = entityMapping;
_entityCollectionInfo = entityReferenceInfo;
_entityCollectionInfo = entityCollectionInfo;
}

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<TEntity> class
var entityCollection = base.GetValue( entity ) as IEntitySource;

// The The foreign key mapping comes from the value passed to this method
IForeignKeyMapping foreignKeyMapping = (IForeignKeyMapping)value;

var entityCollectionType = foreignKeyMapping.ForeignKeyProperty.DeclaringType;

// TJT: Clean the EntitySet access up!
var entitySetSource = _entityMapping.Model.DataContext.EntitySets[ entityCollectionType ];

var keyName = foreignKeyMapping.ForeignKeyProperty.Name;
var keyValue = _entityMapping.GetColumn( entity, foreignKeyMapping.ReferenceKeyProperty.Name );

if ( !entityCollection.HasSource )
{
entityCollection.SetSource( entitySetSource, keyName, keyValue );
}
else
{
throw new InvalidOperationException( "Entity reference source already set." );
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public override object GetValue( TEntity entity )
public override void SetValue( TEntity entity, object value )
{
// The base.GetValue gets the entityReference<TEntity> class
var entityReference = base.GetValue( entity ) as IEntityReferenceSource;
var entityReference = base.GetValue( entity ) as IEntitySource;

// The The foreign key mapping comes from the value passed to this method
IForeignKeyMapping foreignKeyMapping = (IForeignKeyMapping)value;
Expand Down
3 changes: 3 additions & 0 deletions Achilles.Entities.Sqlite/Modelling/Mapping/EntityMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ public object GetForeignKey<T>( T entity, string propertyName ) where T : class
public void SetEntityReference<T>( T Entity, string propertyName, object source ) where T : class
=> EntityReferenceAccessors[ propertyName ].SetValue( Entity as TEntity, source );

public void SetEntityCollection<T>( T Entity, string propertyName, object source ) where T : class
=> EntityCollectionAccessors[ propertyName ].SetValue( Entity as TEntity, source );

public List<IColumnMapping> ColumnMappings { get; set; } = new List<IColumnMapping>();

public List<IIndexMapping> IndexMappings { get; set; } = new List<IIndexMapping>();
Expand Down
2 changes: 2 additions & 0 deletions Achilles.Entities.Sqlite/Modelling/Mapping/IEntityMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public interface IEntityMapping

void SetEntityReference<T>( T entity, string propertyName, object value ) where T : class;

void SetEntityCollection<T>( T entity, string propertyName, object value ) where T : class;

object GetForeignKey<T>( T entity, string propertyName ) where T : class;

void Compile();
Expand Down
8 changes: 4 additions & 4 deletions Achilles.Entities.Sqlite/Querying/EntityMaterializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,10 @@ private void SetDeferredLoading ( object entity )

if ( relationshipMapping.IsMany )
{
//entityMapping.SetEntityCollection(
// entity,
// relationshipMapping.RelationshipProperty.Name,
// relationshipMapping.ForeignKeyMapping );
entityMapping.SetEntityCollection(
entity,
relationshipMapping.RelationshipProperty.Name,
relationshipMapping.ForeignKeyMapping );
}
else
{
Expand Down
33 changes: 33 additions & 0 deletions Entities.Sqlite.Tests/DbContext/Database/DatabaseQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,39 @@ public void Database_ComplexQuery_CanReadListWithLazyEntityReference()
}
}

[Fact]
public void Database_ComplexQuery_CanReadListWithLazyEntityCollection()
{
const string connectionString = "Data Source=:memory:";
var options = new DataContextOptionsBuilder().UseSqlite( connectionString ).Options;

using ( var context = new TestDataContext( options ) )
{
InitializeContext( context );

Assert.Equal( 2, context.Products.Count() );

var query = from p in context.Products
where p.Name == "Banana"
select p;

var products = query.ToList<Product>();

Assert.False( products[ 0 ].Parts.IsLoaded );

var count = products[ 0 ].Parts.Count;
Assert.Equal( 3, count );

var parts = products[ 0 ].Parts.ToList();

Assert.Equal( "Bolt", parts[ 0 ].Name );
Assert.Equal( "Wrench", parts[ 1 ].Name );
Assert.Equal( "Hammer", parts[ 2 ].Name );

Assert.True( products[ 0 ].Parts.IsLoaded );
}
}

[Fact]
public void Database_ComplexQuery_CanReadList()
{
Expand Down

0 comments on commit 1a9fda2

Please sign in to comment.