Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/VONK-8001-copy-to-public-facade #8

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ For more details about developing a Firely Server facade, please consult [Firely

### Build dependencies
The following configuration has been succesfully tested for building and running the project:
* Firely Server (Vonk) - Version 4.0.0
* Visual Studio for Mac - Version 8.x.x
* Visual Studio for Windows - Version 16.x.x
* .Net Core - Version 3.1
* Firely Server (Vonk) - Version 5.x.0
* Visual Studio for Mac - Version 8.x.x and newer
* Visual Studio for Windows - Version 16.x.x and newer
* .Net 8.0

## License

Expand Down
180 changes: 180 additions & 0 deletions Vonk.Facade.Relational/LICENSE.md

Large diffs are not rendered by default.

56 changes: 26 additions & 30 deletions Vonk.Facade.Relational/LinqKitExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;
using LinqKit;

namespace Vonk.Core.Support
namespace Vonk.Core.Support;

public static class LinqKitExtensions
{
public static class LinqKitExtensions
{
public static Expression<Func<T, bool>> And<T>(params Expression<Func<T, bool>>[] expressions)
public static Expression<Func<T, bool>> And<T>(params Expression<Func<T, bool>>[] expressions)
{
if (expressions == null)
return null;
var result = expressions[0];
for (int i = 1; i < expressions.Length; i++)
{
if (expressions == null)
return null;
var result = expressions[0];
for (int i = 1; i < expressions.Length; i++)
{
if (result == null)
result = expressions[i];
else if (expressions[i] != null)
result = result.And(expressions[i]);
}
return result;
if (result == null)
result = expressions[i];
else if (expressions[i] != null)
result = result.And(expressions[i]);
}
return result;
}

public static Expression<Func<T, bool>> Or<T>(params Expression<Func<T, bool>>[] expressions)
public static Expression<Func<T, bool>> Or<T>(params Expression<Func<T, bool>>[] expressions)
{
if (expressions == null)
return null;
var result = expressions[0];
for (int i = 1; i < expressions.Length; i++)
{
if (expressions == null)
return null;
var result = expressions[0];
for (int i = 1; i < expressions.Length; i++)
{
if (result == null)
result = expressions[i];
else if (expressions[i] != null)
result = result.Or(expressions[i]);
}
return result;
if (result == null)
result = expressions[i];
else if (expressions[i] != null)
result = result.Or(expressions[i]);
}
return result;
}

}
22 changes: 10 additions & 12 deletions Vonk.Facade.Relational/QueryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
using Vonk.Core.Repository;

namespace Vonk.Facade.Relational
namespace Vonk.Facade.Relational;

public static class QueryExtensions
{
public static class QueryExtensions
public static Q CreateQuery<E, Q>(this ReferenceFromValue refFromValue, RelationalQueryFactory<E, Q> queryFactory) where E : class where Q : RelationalQuery<E>, new()
{
//TODO: Move these to Vonk.Core.Repository, they are also useful for MemoryQueryRepo and probably also DocumentQueryRepo
public static Q CreateQuery<E, Q>(this ReferenceFromValue refFromValue, RelationalQueryFactory<E, Q> queryFactory) where E : class where Q : RelationalQuery<E>, new()
{
return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level);
}

public static Q CreateQuery<E, Q>(this ReferenceToValue refFromValue, RelationalQueryFactory<E, Q> queryFactory) where E : class where Q : RelationalQuery<E>, new()
{
return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level);
}
return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level);
}

public static Q CreateQuery<E, Q>(this ReferenceToValue refFromValue, RelationalQueryFactory<E, Q> queryFactory) where E : class where Q : RelationalQuery<E>, new()
{
return refFromValue.Context.CreateQuery(queryFactory, refFromValue.Arguments, refFromValue.Options, refFromValue.Level);
}

}
251 changes: 125 additions & 126 deletions Vonk.Facade.Relational/RelationalQuery.cs
Original file line number Diff line number Diff line change
@@ -1,152 +1,151 @@
using LinqKit;
using Microsoft.EntityFrameworkCore;
using System;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using LinqKit;
using Microsoft.EntityFrameworkCore;
using Vonk.Core.Repository;
using Vonk.Core.Repository.ResultShaping;
using Vonk.Core.Support;

namespace Vonk.Facade.Relational
namespace Vonk.Facade.Relational;

public class RelationalSortShape<E> : SortShape
{
public class RelationalSortShape<E> : SortShape
public RelationalSortShape(SortShape originalSort, Func<IQueryable<E>, IQueryable<E>> sortFunction) : base(originalSort.ParameterCode, originalSort.ParameterType, originalSort.Direction, originalSort.Priority)
{
public RelationalSortShape(SortShape originalSort, Func<IQueryable<E>, IQueryable<E>> sortFunction) : base(originalSort.ParameterCode, originalSort.ParameterType, originalSort.Direction, originalSort.Priority)
{
Sort = sortFunction;
}

public Func<IQueryable<E>, IQueryable<E>> Sort { get; }
Sort = sortFunction;
}

public class RelationalQuery<E> : BaseQuery where E : class
{
public Expression<Func<E, bool>> Predicate { get; internal set; }
internal IShapeValue[] InternalShapes { get; set; }
public override IShapeValue[] Shapes { get => InternalShapes; }
public Func<IQueryable<E>, IQueryable<E>> Sort { get; }
}

/// <summary>
/// Get the entities of type <see cref="E"/> that adhere to the <see cref="Predicate"/>, with any provided Skip and Count applied.
/// </summary>
/// <param name="dbContext">The <seealso cref="DbContext"/> containing a <seealso cref="DbSet"/> of <see cref="E"/>. We use <seealso cref="DbContext"/> so it can also contain <seealso cref="DbSet"/>s for entities that are referenced to or from.</param>.
/// <returns></returns>
public virtual IQueryable<E> Execute(DbContext dbContext)
{
Check.NotNull(dbContext, nameof(dbContext));
return HandleShapes(Filter(dbContext));
}
public class RelationalQuery<E> : BaseQuery where E : class
{
public Expression<Func<E, bool>> Predicate { get; internal set; }
internal IShapeValue[] InternalShapes { get; set; }
public override IShapeValue[] Shapes { get => InternalShapes; }

/// <summary>
/// Get the total number of entities that adhere to the <see cref="Predicate"/>, irrespective of the Skip and Count.
/// </summary>
/// <param name="dbContext">The <seealso cref="DbContext"/> containing a <seealso cref="DbSet"/> of <see cref="E"/>. We use <seealso cref="DbContext"/> so it can also contain <seealso cref="DbSet"/>s for entities that are referenced to or from.</param>.
/// <returns></returns>
public virtual async Task<long> ExecuteCount(DbContext dbContext)
{
var entries = Filter(dbContext);
if (entries == null)
return 0;
else
return await entries.CountAsync();
}
/// <summary>
/// Get the entities of type <see cref="E"/> that adhere to the <see cref="Predicate"/>, with any provided Skip and Count applied.
/// </summary>
/// <param name="dbContext">The <seealso cref="DbContext"/> containing a <seealso cref="DbSet"/> of <see cref="E"/>. We use <seealso cref="DbContext"/> so it can also contain <seealso cref="DbSet"/>s for entities that are referenced to or from.</param>.
/// <returns></returns>
public virtual IQueryable<E> Execute(DbContext dbContext)
{
Check.NotNull(dbContext);
return HandleShapes(Filter(dbContext));
}

/// <summary>
/// Apply the <see cref="Predicate"/> on entities of type <see cref="E"/> in <paramref name="dbContext"/>.
/// </summary>
/// <param name="dbContext"></param>
/// <returns></returns>
protected virtual IQueryable<E> Filter(DbContext dbContext)
{
var entities = GetEntitySet(dbContext).AsExpandable();
if (Predicate != null)
return entities.Where(LinqKitExtensions.And(Predicate));
return entities;
}
/// <summary>
/// Get the total number of entities that adhere to the <see cref="Predicate"/>, irrespective of the Skip and Count.
/// </summary>
/// <param name="dbContext">The <seealso cref="DbContext"/> containing a <seealso cref="DbSet"/> of <see cref="E"/>. We use <seealso cref="DbContext"/> so it can also contain <seealso cref="DbSet"/>s for entities that are referenced to or from.</param>.
/// <returns></returns>
public virtual async Task<long> ExecuteCount(DbContext dbContext)
{
var entries = Filter(dbContext);
if (entries == null)
return 0;
else
return await entries.CountAsync();
}

/// <summary>
/// By default this will return dbContext.Set<typeparamref name="E"/>, without change tracking.
/// Override it to add custom .Include() statements or other adjustments.
/// </summary>
/// <param name="dbContext"></param>
/// <returns>A set of entities that will be used for filtering and counting</returns>
protected virtual IQueryable<E> GetEntitySet(DbContext dbContext)
{
return dbContext.Set<E>().AsNoTracking();
}
/// <summary>
/// Apply the <see cref="Predicate"/> on entities of type <see cref="E"/> in <paramref name="dbContext"/>.
/// </summary>
/// <param name="dbContext"></param>
/// <returns></returns>
protected virtual IQueryable<E> Filter(DbContext dbContext)
{
var entities = GetEntitySet(dbContext).AsExpandable();
if (Predicate != null)
return entities.Where(LinqKitExtensions.And(Predicate));
return entities;
}

/// <summary>
/// Apply the <see cref="Shapes"/> on <paramref name="source"/>.
/// By default only Skip, Count and Sort are applied.
/// <see cref="SortByDefault(IQueryable{E})"/> is always applied.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleShapes(IQueryable<E> source)
{
if (Shapes.HasAny())
return HandleCount(
HandleSkip(
HandleSort(
SortByDefault(source)
)));
return SortByDefault(source);
}
/// <summary>
/// By default this will return dbContext.Set<typeparamref name="E"/>, without change tracking.
/// Override it to add custom .Include() statements or other adjustments.
/// </summary>
/// <param name="dbContext"></param>
/// <returns>A set of entities that will be used for filtering and counting</returns>
protected virtual IQueryable<E> GetEntitySet(DbContext dbContext)
{
return dbContext.Set<E>().AsNoTracking();
}

/// <summary>
/// Apply the <seealso cref="SkipShape"/> on <paramref name="source"/>, if provided in <see cref="Shapes"/>.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleSkip(IQueryable<E> source)
{
var skip = Shapes.OfType<SkipShape>().FirstOrDefault();
if (skip != null)
return source.Skip(skip.Skip);
return source;
}
/// <summary>
/// Apply the <see cref="Shapes"/> on <paramref name="source"/>.
/// By default only Skip, Count and Sort are applied.
/// <see cref="SortByDefault(IQueryable{E})"/> is always applied.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleShapes(IQueryable<E> source)
{
if (Shapes.HasAny())
return HandleCount(
HandleSkip(
HandleSort(
SortByDefault(source)
)));
return SortByDefault(source);
}

/// <summary>
/// Apply the <seealso cref="CountShape"/> on <paramref name="source"/>, if provided in <see cref="Shapes"/>.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleCount(IQueryable<E> source)
{
var count = Shapes.OfType<CountShape>().FirstOrDefault();
if (count != null)
return source.Take(count.Count);
return source;
}
/// <summary>
/// Apply the <seealso cref="SkipShape"/> on <paramref name="source"/>, if provided in <see cref="Shapes"/>.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleSkip(IQueryable<E> source)
{
var skip = Shapes.OfType<SkipShape>().FirstOrDefault();
if (skip != null)
return source.Skip(skip.Skip);
return source;
}

/// <summary>
/// Apply the <seealso cref="CountShape"/> on <paramref name="source"/>, if provided in <see cref="Shapes"/>.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleCount(IQueryable<E> source)
{
var count = Shapes.OfType<CountShape>().FirstOrDefault();
if (count != null)
return source.Take(count.Count);
return source;
}

/// <summary>
/// Apply al the <seealso cref="SortShape"/>s on <paramref name="source"/>.
/// Take into account the <seealso cref="SortShape.Priority"/> and apply them in the right order.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleSort(IQueryable<E> source)
/// <summary>
/// Apply al the <seealso cref="SortShape"/>s on <paramref name="source"/>.
/// Take into account the <seealso cref="SortShape.Priority"/> and apply them in the right order.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> HandleSort(IQueryable<E> source)
{
if (Shapes.HasAny())
{
if (Shapes.HasAny())
foreach (var relationalSort in Shapes.OfType<RelationalSortShape<E>>().OrderByDescending(rss => rss.Priority))
{
foreach (var relationalSort in Shapes.OfType<RelationalSortShape<E>>().OrderByDescending(rss => rss.Priority))
{
source = relationalSort.Sort(source);
}
source = relationalSort.Sort(source);
}
return source;
}
return source;
}

/// <summary>
/// Lets you supply an OrderBy on e.g. a technical id so at least the order of the results is predictable.
/// Also, EFCore complains if there is no sorting at all.
/// Example: return source.OrderBy(e => e.SomeId);
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> SortByDefault(IQueryable<E> source)
{
return source;
}
/// <summary>
/// Lets you supply an OrderBy on e.g. a technical id so at least the order of the results is predictable.
/// Also, EFCore complains if there is no sorting at all.
/// Example: return source.OrderBy(e => e.SomeId);
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected virtual IQueryable<E> SortByDefault(IQueryable<E> source)
{
return source;
}
}
Loading