|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|