Added possibility to chain includes. (#331)
* Added possibility to chain includes. * Removed interface. * Removed the need for generating GUIDs as ids and added tests.
This commit is contained in:
26
src/ApplicationCore/Helpers/Query/IncludeAggregator.cs
Normal file
26
src/ApplicationCore/Helpers/Query/IncludeAggregator.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query
|
||||
{
|
||||
public class IncludeAggregator<TEntity>
|
||||
{
|
||||
public IncludeQuery<TEntity, TProperty> Include<TProperty>(Expression<Func<TEntity, TProperty>> selector)
|
||||
{
|
||||
var visitor = new IncludeVisitor();
|
||||
visitor.Visit(selector);
|
||||
|
||||
var pathMap = new Dictionary<IIncludeQuery, string>();
|
||||
var query = new IncludeQuery<TEntity, TProperty>(pathMap);
|
||||
|
||||
if (!string.IsNullOrEmpty(visitor.Path))
|
||||
{
|
||||
pathMap[query] = visitor.Path;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/ApplicationCore/Helpers/Query/IncludeQuery.cs
Normal file
19
src/ApplicationCore/Helpers/Query/IncludeQuery.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query
|
||||
{
|
||||
public class IncludeQuery<TEntity, TPreviousProperty> : IIncludeQuery<TEntity, TPreviousProperty>
|
||||
{
|
||||
public Dictionary<IIncludeQuery, string> PathMap { get; } = new Dictionary<IIncludeQuery, string>();
|
||||
public IncludeVisitor Visitor { get; } = new IncludeVisitor();
|
||||
|
||||
public IncludeQuery(Dictionary<IIncludeQuery, string> pathMap)
|
||||
{
|
||||
PathMap = pathMap;
|
||||
}
|
||||
|
||||
public HashSet<string> Paths => PathMap.Select(x => x.Value).ToHashSet();
|
||||
}
|
||||
}
|
||||
66
src/ApplicationCore/Helpers/Query/IncludeQueryExtensions.cs
Normal file
66
src/ApplicationCore/Helpers/Query/IncludeQueryExtensions.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query
|
||||
{
|
||||
public static class IncludeQueryExtensions
|
||||
{
|
||||
public static IIncludeQuery<TEntity, TNewProperty> Include<TEntity, TPreviousProperty, TNewProperty>(
|
||||
this IIncludeQuery<TEntity, TPreviousProperty> query,
|
||||
Expression<Func<TEntity, TNewProperty>> selector)
|
||||
{
|
||||
query.Visitor.Visit(selector);
|
||||
|
||||
var includeQuery = new IncludeQuery<TEntity, TNewProperty>(query.PathMap);
|
||||
query.PathMap[includeQuery] = query.Visitor.Path;
|
||||
|
||||
return includeQuery;
|
||||
}
|
||||
|
||||
public static IIncludeQuery<TEntity, TNewProperty> ThenInclude<TEntity, TPreviousProperty, TNewProperty>(
|
||||
this IIncludeQuery<TEntity, TPreviousProperty> query,
|
||||
Expression<Func<TPreviousProperty, TNewProperty>> selector)
|
||||
{
|
||||
query.Visitor.Visit(selector);
|
||||
|
||||
// If the visitor did not generated a path, return a new IncludeQuery with an unmodified PathMap.
|
||||
if (string.IsNullOrEmpty(query.Visitor.Path))
|
||||
{
|
||||
return new IncludeQuery<TEntity, TNewProperty>(query.PathMap);
|
||||
}
|
||||
|
||||
var pathMap = query.PathMap;
|
||||
var existingPath = pathMap[query];
|
||||
pathMap.Remove(query);
|
||||
|
||||
var includeQuery = new IncludeQuery<TEntity, TNewProperty>(query.PathMap);
|
||||
pathMap[includeQuery] = $"{existingPath}.{query.Visitor.Path}";
|
||||
|
||||
return includeQuery;
|
||||
}
|
||||
|
||||
public static IIncludeQuery<TEntity, TNewProperty> ThenInclude<TEntity, TPreviousProperty, TNewProperty>(
|
||||
this IIncludeQuery<TEntity, IEnumerable<TPreviousProperty>> query,
|
||||
Expression<Func<TPreviousProperty, TNewProperty>> selector)
|
||||
{
|
||||
query.Visitor.Visit(selector);
|
||||
|
||||
// If the visitor did not generated a path, return a new IncludeQuery with an unmodified PathMap.
|
||||
if (string.IsNullOrEmpty(query.Visitor.Path))
|
||||
{
|
||||
return new IncludeQuery<TEntity, TNewProperty>(query.PathMap);
|
||||
}
|
||||
|
||||
var pathMap = query.PathMap;
|
||||
var existingPath = pathMap[query];
|
||||
pathMap.Remove(query);
|
||||
|
||||
var includeQuery = new IncludeQuery<TEntity, TNewProperty>(query.PathMap);
|
||||
pathMap[includeQuery] = $"{existingPath}.{query.Visitor.Path}";
|
||||
|
||||
return includeQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/ApplicationCore/Helpers/Query/IncludeVisitor.cs
Normal file
16
src/ApplicationCore/Helpers/Query/IncludeVisitor.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query
|
||||
{
|
||||
public class IncludeVisitor : ExpressionVisitor
|
||||
{
|
||||
public string Path { get; private set; } = string.Empty;
|
||||
|
||||
protected override Expression VisitMember(MemberExpression node)
|
||||
{
|
||||
Path = string.IsNullOrEmpty(Path) ? node.Member.Name : $"{node.Member.Name}.{Path}";
|
||||
|
||||
return base.VisitMember(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/ApplicationCore/Interfaces/IIncludeQuery.cs
Normal file
16
src/ApplicationCore/Interfaces/IIncludeQuery.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Helpers.Query;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
||||
{
|
||||
public interface IIncludeQuery
|
||||
{
|
||||
Dictionary<IIncludeQuery, string> PathMap { get; }
|
||||
IncludeVisitor Visitor { get; }
|
||||
HashSet<string> Paths { get; }
|
||||
}
|
||||
|
||||
public interface IIncludeQuery<TEntity, out TPreviousProperty> : IIncludeQuery
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Helpers.Query;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
||||
{
|
||||
@@ -26,6 +27,13 @@ namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
||||
{
|
||||
Includes.Add(includeExpression);
|
||||
}
|
||||
|
||||
protected virtual void AddIncludes<TProperty>(Func<IncludeAggregator<T>, IIncludeQuery<T, TProperty>> includeGenerator)
|
||||
{
|
||||
var includeQuery = includeGenerator(new IncludeAggregator<T>());
|
||||
IncludeStrings.AddRange(includeQuery.Paths);
|
||||
}
|
||||
|
||||
protected virtual void AddInclude(string includeString)
|
||||
{
|
||||
IncludeStrings.Add(includeString);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Helpers.Query;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
||||
{
|
||||
@@ -7,8 +8,7 @@ namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
||||
public CustomerOrdersWithItemsSpecification(string buyerId)
|
||||
: base(o => o.BuyerId == buyerId)
|
||||
{
|
||||
AddInclude(o => o.OrderItems);
|
||||
AddInclude($"{nameof(Order.OrderItems)}.{nameof(OrderItem.ItemOrdered)}");
|
||||
AddIncludes(query => query.Include(o => o.OrderItems).ThenInclude(i => i.ItemOrdered));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user