Skip to content

Commit

Permalink
Select Projections feature added; Progress towards eager loading; (Se…
Browse files Browse the repository at this point in the history
…e issues #1, #14, #15)
  • Loading branch information
ToddThomson committed Sep 7, 2018
1 parent e9319b7 commit 89b7a60
Show file tree
Hide file tree
Showing 24 changed files with 680 additions and 98 deletions.
2 changes: 2 additions & 0 deletions Achilles.Entities.Sqlite.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1F41A4A4-7A97-4704-BCCA-FA13DCC6BD3C}"
ProjectSection(SolutionItems) = preProject
achilles-icon.png = achilles-icon.png
Conduct.md = Conduct.md
Contribute.md = Contribute.md
LICENSE.txt = LICENSE.txt
NuGet.config = NuGet.config
README.md = README.md
Expand Down
1 change: 1 addition & 0 deletions Achilles.Entities.Sqlite/Achilles.Entities.Sqlite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Remotion.Linq" Version="2.2.0" />
<PackageReference Include="Remotion.Linq.EagerFetching" Version="2.2.0" />
<PackageReference Include="System.Interactive.Async" Version="3.2.0" />
</ItemGroup>

Expand Down
21 changes: 20 additions & 1 deletion Achilles.Entities.Sqlite/EntitySet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#region Namespaces

using Achilles.Entities.Linq;
using Remotion.Linq.EagerFetching.Parsing;
using Remotion.Linq.Parsing.ExpressionVisitors.Transformation;
using Remotion.Linq.Parsing.Structure;
using Remotion.Linq.Parsing.Structure.NodeTypeProviders;
using System;
using System.Collections;
using System.Collections.Generic;
Expand Down Expand Up @@ -96,7 +100,22 @@ private EntityQueryable<TEntity> EntityQueryable
{
if ( _entityQueryable == null )
{
_entityQueryable = new EntityQueryable<TEntity>( _context );
var customNodeTypeRegistry = new MethodInfoBasedNodeTypeRegistry();

customNodeTypeRegistry.Register( new[] { typeof( EagerFetchingExtensions ).GetMethod( "FetchOne" ) }, typeof( FetchOneExpressionNode ) );
customNodeTypeRegistry.Register( new[] { typeof( EagerFetchingExtensions ).GetMethod( "FetchMany" ) }, typeof( FetchManyExpressionNode ) );
customNodeTypeRegistry.Register( new[] { typeof( EagerFetchingExtensions ).GetMethod( "ThenFetchOne" ) }, typeof( ThenFetchOneExpressionNode ) );
customNodeTypeRegistry.Register( new[] { typeof( EagerFetchingExtensions ).GetMethod( "ThenFetchMany" ) }, typeof( ThenFetchManyExpressionNode ) );

var nodeTypeProvider = ExpressionTreeParser.CreateDefaultNodeTypeProvider();
nodeTypeProvider.InnerProviders.Add( customNodeTypeRegistry );

var transformerRegistry = ExpressionTransformerRegistry.CreateDefault();
var processor = ExpressionTreeParser.CreateDefaultProcessor( transformerRegistry );
var expressionTreeParser = new ExpressionTreeParser( nodeTypeProvider, processor );
var queryParser = new QueryParser( expressionTreeParser );

_entityQueryable = new EntityQueryable<TEntity>( _context, queryParser );
}

return _entityQueryable;
Expand Down
22 changes: 21 additions & 1 deletion Achilles.Entities.Sqlite/Extensions/StringBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,27 @@ public static void AppendEnumerable( this StringBuilder stringBuilder, IEnumerab
}
else
{
stringBuilder.AppendFormat( "{0}{1}", delimiter, item );
stringBuilder.AppendFormat( "{0}{1}{2}", delimiter, prefix, item );
}
}
}

public static void AppendEnumerable( this StringBuilder stringBuilder, IEnumerable<string> e, string prefix, string delimiter, string alias )
{
bool first = true;

foreach ( var item in e )
{
if ( first )
{
first = false;
stringBuilder.AppendFormat( "{0}{1}", prefix, item );
stringBuilder.AppendFormat( " AS {0}_{1}", alias, item );
}
else
{
stringBuilder.AppendFormat( "{0} {1}{2}", delimiter, prefix, item );
stringBuilder.AppendFormat( " AS {0}_{1}", alias, item );
}
}
}
Expand Down
14 changes: 0 additions & 14 deletions Achilles.Entities.Sqlite/Linq/AsyncQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,6 @@ namespace Achilles.Entities.Linq
{
public static class AsyncQueryableExtensions
{
public static IJoinQueryable<TEntity, TProperty> SelectRelated<TEntity, TProperty>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, bool>> predicate,
CancellationToken cancellationToken = default )
where TEntity : class
{
if ( source.Provider is IAsyncQueryProvider provider )
{
throw new NotImplementedException();
}

throw new InvalidOperationException( "Provider is not async" );
}

public static Task<List<TSource>> ToListAsync<TSource>(
this IQueryable<TSource> source,
CancellationToken cancellationToken = default )
Expand Down
110 changes: 110 additions & 0 deletions Achilles.Entities.Sqlite/Linq/EagerFetchingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#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:[email protected]

#endregion

#region Namespaces

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

#endregion

namespace Achilles.Entities.Linq
{
public static class EagerFetchingExtensions
{
public static FluentFetchRequest<TOriginating, TRelated> FetchMany<TOriginating, TRelated>(
this IQueryable<TOriginating> query,
Expression<Func<TOriginating, IEnumerable<TRelated>>> relatedObjectSelector )
{
if ( query == null )
{
throw new ArgumentNullException( nameof( query ) );
}

if ( relatedObjectSelector == null )
{
throw new ArgumentNullException( nameof( relatedObjectSelector ) );
}

var methodInfo = ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod( typeof( TOriginating ), typeof( TRelated ) );

return CreateFluentFetchRequest<TOriginating, TRelated>( methodInfo, query, relatedObjectSelector );
}

public static FluentFetchRequest<TOriginating, TRelated> FetchOne<TOriginating, TRelated>(
this IQueryable<TOriginating> query,
Expression<Func<TOriginating, TRelated>> relatedObjectSelector )
{
if ( query == null )
{
throw new ArgumentNullException( nameof( query ) );
}
if ( relatedObjectSelector == null )
{
throw new ArgumentNullException( nameof( relatedObjectSelector ) );
}

var methodInfo = ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod( typeof( TOriginating ), typeof( TRelated ) );

return CreateFluentFetchRequest<TOriginating, TRelated>( methodInfo, query, relatedObjectSelector );
}

public static FluentFetchRequest<TQueried, TRelated> ThenFetchMany<TQueried, TFetch, TRelated>(
this FluentFetchRequest<TQueried, TFetch> query,
Expression<Func<TFetch, IEnumerable<TRelated>>> relatedObjectSelector )
{
if ( query == null )
{
throw new ArgumentNullException( nameof( query ) );
}
if ( relatedObjectSelector == null )
{
throw new ArgumentNullException( nameof( relatedObjectSelector ) );
}

var methodInfo = ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod( typeof( TQueried ), typeof( TFetch ), typeof( TRelated ) );

return CreateFluentFetchRequest<TQueried, TRelated>( methodInfo, query, relatedObjectSelector );
}

public static FluentFetchRequest<TQueried, TRelated> ThenFetchOne<TQueried, TFetch, TRelated>(
this FluentFetchRequest<TQueried, TFetch> query,
Expression<Func<TFetch, TRelated>> relatedObjectSelector )
{
if ( query == null )
{
throw new ArgumentNullException( nameof( query ) );
}
if ( relatedObjectSelector == null )
{
throw new ArgumentNullException( nameof( relatedObjectSelector ) );
}

var methodInfo = ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod( typeof( TQueried ), typeof( TFetch ), typeof( TRelated ) );

return CreateFluentFetchRequest<TQueried, TRelated>( methodInfo, query, relatedObjectSelector );
}

private static FluentFetchRequest<TOriginating, TRelated> CreateFluentFetchRequest<TOriginating, TRelated>(
MethodInfo currentFetchMethod,
IQueryable<TOriginating> query,
LambdaExpression relatedObjectSelector )
{
var queryProvider = query.Provider; // ArgumentUtility.CheckNotNullAndType<QueryProviderBase>( "query.Provider", query.Provider );

var callExpression = Expression.Call( currentFetchMethod, query.Expression, relatedObjectSelector );

return new FluentFetchRequest<TOriginating, TRelated>( queryProvider, callExpression );
}
}
}
4 changes: 2 additions & 2 deletions Achilles.Entities.Sqlite/Linq/EntityQueryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class EntityQueryable<TResult> : QueryableBase<TResult> , IEntityAsyncQue
{
#region Constructor(s)

public EntityQueryable( DataContext context )
: base( new EntityQueryProvider( context, typeof( EntityQueryable<> ), QueryParser.CreateDefault(), new EntityQueryExecutor( context ) ) )
public EntityQueryable( DataContext context, QueryParser queryParser )
: base( new EntityQueryProvider( context, typeof( EntityQueryable<> ), queryParser, new EntityQueryExecutor( context ) ) )
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
#region Namespaces
#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:[email protected]

#endregion

#region Namespaces

using Achilles.Entities.Extensions;
using Achilles.Entities.Relational;
Expand All @@ -10,36 +20,68 @@

namespace Achilles.Entities.Linq.ExpressionVisitors
{
/// <summary>
///
/// </summary>
public class SelectExpressionVisitor : SqlExpressionVisitor
{
#region Fields

private bool _inProjection = false;
private MemberAssignment _projectionBinding;

#endregion

#region Constructor(s)

public SelectExpressionVisitor( DataContext dbContext, SqlParameterCollection parameters )
: base( dbContext, parameters )
{
}

#endregion

public static string GetStatement( DataContext dbContext, SqlParameterCollection parameters, Expression expression )
{
var expressionVisitor = new SelectExpressionVisitor( dbContext, parameters );
var selectExpressionVisitor = new SelectExpressionVisitor( dbContext, parameters );

expressionVisitor.Visit( expression );
selectExpressionVisitor.Visit( expression );

return expressionVisitor.GetStatement();
return selectExpressionVisitor.GetStatement();
}

protected override Expression VisitQuerySourceReference( QuerySourceReferenceExpression expression )
{
var EntityMapping = _dbContext.Model.GetEntityMapping( expression.ReferencedQuerySource.ItemType );
// Review: Check for null?

Statement.AppendEnumerable(
EntityMapping.ColumnMappings.Select( p => p.ColumnName ),
string.Format( "{0}.",
expression.ReferencedQuerySource.ItemName ),
string.Format( ", {0}.", expression.ReferencedQuerySource.ItemName ) );
if ( _inProjection )
{
Statement.AppendEnumerable(
EntityMapping.ColumnMappings.Select( p => p.ColumnName ),
string.Format( "{0}.", expression.ReferencedQuerySource.ItemName ),
", ",
_projectionBinding.Member.Name );

}
else
{
Statement.AppendEnumerable(
EntityMapping.ColumnMappings.Select( p => p.ColumnName ),
string.Format( "{0}.", expression.ReferencedQuerySource.ItemName ),
", " );
}

return expression;
}

/// <summary>
/// The MemberInit expression creates a new object and initializes the object properties through the expression bindings.
/// </summary>
/// <example>
/// select new EntityType { }
/// </example>
/// <param name="expression">The MemberInit expression.</param>
/// <returns>The MemberInit expression parameter.</returns>
protected override Expression VisitMemberInit( MemberInitExpression expression )
{
for ( int i = 0; i < expression.Bindings.Count; i++ )
Expand All @@ -56,9 +98,22 @@ protected override Expression VisitMemberInit( MemberInitExpression expression )
Statement.Append( ", " );
}

if ( binding.Expression.NodeType == ExpressionType.Extension )
{
_inProjection = true;
_projectionBinding = binding;
}
else
{
_inProjection = false;
}

Visit( binding.Expression );

Statement.AppendFormat( " AS {0}", binding.Member.Name );
if ( binding.Expression.NodeType == ExpressionType.MemberAccess )
{
Statement.AppendFormat( " AS {0}", binding.Member.Name );
}
}

return expression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ protected override Expression VisitQuerySourceReference( QuerySourceReferenceExp
return expression;
}

//protected override Expression VisitSubQuery( SubQueryExpression expression )
//{
// return base.VisitSubQuery( expression );
//}

protected override Expression VisitMember( MemberExpression expression )
{
Visit( expression.Expression );
Expand Down
28 changes: 28 additions & 0 deletions Achilles.Entities.Sqlite/Linq/FluentFetchRequest.cs
Original file line number Diff line number Diff line change
@@ -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:[email protected]

#endregion

#region Namespaces

using Remotion.Linq;
using System.Linq;
using System.Linq.Expressions;

#endregion

namespace Achilles.Entities.Linq
{
public class FluentFetchRequest<TQueried, TFetch> : QueryableBase<TQueried>
{
public FluentFetchRequest( IQueryProvider provider, Expression expression )
: base( provider, expression )
{
}
}
}
Loading

0 comments on commit 89b7a60

Please sign in to comment.