You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Framework/Connected.Entities/EntitiesExtensions.cs

259 lines
8.2 KiB

using System.Collections.Immutable;
using System.Linq.Expressions;
using System.Reflection;
using Connected.Data;
using Connected.Entities.Annotations;
using Connected.Entities.Query;
using Connected.Interop;
using Connected.Notifications;
using Connected.ServiceModel;
namespace Connected.Entities;
public static class EntitiesExtensions
{
/// <summary>
/// Converts the <see cref="IDto"/> object into entity and overwrites the provided properties.
/// </summary>
/// <remarks>
/// All provided arguments are used when overwriting properties in the order they are specified. This means
/// the value from the last defined property is used when setting the entity's value.
/// </remarks>
/// <typeparam name="TEntity">The type of the entity to create.</typeparam>
/// <param name="args">The arguments containing base property set.</param>
/// <param name="state">The state modifier to which entity is set.</param>
/// <param name="sources">An array of additional modifier objects providing modified values.</param>
/// <returns>A new instance of the entity with modified values.</returns>
public static TEntity AsEntity<TEntity>(this IDto args, State state, params object[] sources)
where TEntity : IEntity
{
if (typeof(TEntity).CreateInstance<TEntity>() is not TEntity instance)
throw new NullReferenceException(typeof(TEntity).Name);
return Merge(instance, args, state, sources);
}
public static TArgs AsArguments<TArgs>(this IEntity entity) where TArgs : IDto
{
var instance = typeof(TArgs).CreateInstance<TArgs>();
return Serializer.Merge(instance, entity);
}
public static TArgs AsArguments<TArgs, TPrimaryKey>(this IPrimaryKey<TPrimaryKey> entity)
where TArgs : IDto
where TPrimaryKey : notnull
{
var instance = typeof(TArgs).CreateInstance<TArgs>();
return Serializer.Merge(instance, entity);
}
public static TArgs Patch<TArgs, TEntity>(this IDto args, TEntity? entity, params object[] sources)
where TArgs : IDto
where TEntity : IEntity
{
if (entity is null)
{
var e = args.AsEntity<TEntity>(State.Default);
return e.AsArguments<TArgs>(args);
}
return Merge(entity, args, State.Default, sources).AsArguments<TArgs>();
}
public static TArgs AsArguments<TArgs>(this IEntity entity, params object[] sources) where TArgs : IDto
{
var instance = typeof(TArgs).CreateInstance<TArgs>();
return Serializer.Merge(instance, entity, sources);
}
public static TArgs AsEventArguments<TArgs>(this IEntity entity) where TArgs : IEventArgs
{
var instance = typeof(TArgs).CreateInstance<TArgs>();
return Serializer.Merge(instance, entity);
}
public static TEntity Merge<TEntity>(this TEntity existing, IDto modifier, State state, params object[] sources)
where TEntity : IEntity
{
var newEntity = Activator.CreateInstance<TEntity>();
return Serializer.Merge(newEntity, existing, modifier, new StateModifier { State = state }, sources);
}
public static async Task<ImmutableList<TSource>> AsEntities<TSource>(this IQueryable<TSource> source, CancellationToken cancellationToken = default)
{
if (source is null)
return ImmutableList<TSource>.Empty;
var list = new List<TSource>();
await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken))
list.Add(element);
return list.ToImmutableList();
}
public static async Task<ImmutableList<TSource>> AsEntities<TSource>(this IEnumerable<TSource> source)
{
if (source is null)
return ImmutableList<TSource>.Empty;
await Task.CompletedTask;
return source.ToImmutableList();
}
public static async Task<TSource?> AsEntity<TSource>(this IQueryable<TSource> source, CancellationToken cancellationToken = default)
{
if (source is null)
return default;
await Task.CompletedTask;
return Execute<TSource, TSource>(QueryableMethods.SingleOrDefaultWithoutPredicate, source, cancellationToken);
}
public static async Task<TSource?> AsEntity<TSource>(this IEnumerable<TSource> source)
{
if (source is null)
return default;
await Task.CompletedTask;
return source.FirstOrDefault();
}
public static IEnumerable<TEntity> WithArguments<TEntity>(this IEnumerable<TEntity> source, QueryArgs args)
{
return source.WithOrderBy(args).WithPaging(args);
}
private static IEnumerable<TEntity> WithPaging<TEntity>(this IEnumerable<TEntity> source, QueryArgs args)
{
if (args.Paging.Size < 1)
return source;
return source.Skip((args.Paging.Index - 1) * args.Paging.Size)
.Take(args.Paging.Size);
}
private static IEnumerable<TEntity> WithOrderBy<TEntity>(this IEnumerable<TEntity> entities, QueryArgs args)
{
if (entities.AsQueryable() as IOrderedQueryable<TEntity> is not IOrderedQueryable<TEntity> result)
return entities;
var top = true;
foreach (var criteria in args.OrderBy)
{
if (top)
{
if (criteria.Mode == OrderByMode.Ascending)
result = result.OrderBy(ResolvePropertyPredicate<TEntity>(criteria.Property));
else
result = result.OrderByDescending(ResolvePropertyPredicate<TEntity>(criteria.Property));
}
else
{
if (criteria.Mode == OrderByMode.Ascending)
result = result.ThenBy(ResolvePropertyPredicate<TEntity>(criteria.Property));
else
result = result.ThenByDescending(ResolvePropertyPredicate<TEntity>(criteria.Property));
}
top = false;
}
return result;
}
private static Expression<Func<T, object>> ResolvePropertyPredicate<T>(string propToOrder)
{
var param = Expression.Parameter(typeof(T));
var memberAccess = Expression.Property(param, propToOrder);
var convertedMemberAccess = Expression.Convert(memberAccess, typeof(object));
var orderPredicate = Expression.Lambda<Func<T, object>>(convertedMemberAccess, param);
return orderPredicate;
}
private static TResult? Execute<TSource, TResult>(MethodInfo operatorMethodInfo, IQueryable<TSource> source, Expression? expression, CancellationToken cancellationToken = default)
{
if (source.Provider is IAsyncQueryProvider provider)
{
if (operatorMethodInfo.IsGenericMethod)
{
operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2
? operatorMethodInfo.MakeGenericMethod(typeof(TSource), typeof(TResult).GetGenericArguments().Single())
: operatorMethodInfo.MakeGenericMethod(typeof(TSource));
}
return (TResult)provider.Execute(Expression.Call(instance: null, method: operatorMethodInfo, arguments: expression == null
? new[] { source.Expression }
: new[] { source.Expression, expression }), cancellationToken);
}
throw new InvalidOperationException();
}
private static TResult? Execute<TSource, TResult>(MethodInfo operatorMethodInfo, IQueryable<TSource> source, CancellationToken cancellationToken = default)
{
return Execute<TSource, TResult>(operatorMethodInfo, source, (Expression?)null, cancellationToken);
}
public static PropertyInfo? PrimaryKeyProperty(this IEntity entity)
{
foreach (var property in entity.GetType().GetInheritedProperites())
{
if (property.FindAttribute<PrimaryKeyAttribute>() is not null)
return property;
}
return null;
}
public static object? PrimaryKeyValue(this IEntity entity)
{
if (PrimaryKeyProperty(entity) is not PropertyInfo property)
return null;
return property.GetValue(entity);
}
public static string EntityId(this IEntity entity)
{
var attribute = entity.GetSchemaAttribute();
return $"{attribute.Schema}.{attribute.Name}";
}
public static SchemaAttribute GetSchemaAttribute(this IEntity entity)
{
var definedAttribute = entity.GetType().GetCustomAttribute<SchemaAttribute>();
if (definedAttribute is not null && definedAttribute.Schema is not null && definedAttribute.Name is not null)
return definedAttribute;
var schema = string.IsNullOrEmpty(definedAttribute?.Schema) ? SchemaAttribute.DefaultSchema : definedAttribute.Schema;
var name = string.IsNullOrEmpty(definedAttribute?.Name) ? entity.GetType().Name.ToPascalCase() : definedAttribute.Name;
return new TableAttribute
{
Id = definedAttribute?.Id,
Name = name,
Schema = schema
};
}
public static IAsyncEnumerable<TSource> AsAsyncEnumerable<TSource>(this IQueryable<TSource> source)
{
if (source is IAsyncEnumerable<TSource> asyncEnumerable)
return asyncEnumerable;
throw new InvalidOperationException();
}
}