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
{
///
/// Converts the object into entity and overwrites the provided properties.
///
///
/// 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.
///
/// The type of the entity to create.
/// The arguments containing base property set.
/// The state modifier to which entity is set.
/// An array of additional modifier objects providing modified values.
/// A new instance of the entity with modified values.
public static TEntity AsEntity(this IDto args, State state, params object[] sources)
where TEntity : IEntity
{
if (typeof(TEntity).CreateInstance() is not TEntity instance)
throw new NullReferenceException(typeof(TEntity).Name);
return Merge(instance, args, state, sources);
}
public static TArgs AsArguments(this IEntity entity) where TArgs : IDto
{
var instance = typeof(TArgs).CreateInstance();
return Serializer.Merge(instance, entity);
}
public static TArgs AsArguments(this IPrimaryKey entity)
where TArgs : IDto
where TPrimaryKey : notnull
{
var instance = typeof(TArgs).CreateInstance();
return Serializer.Merge(instance, entity);
}
public static TArgs Patch(this IDto args, TEntity? entity, params object[] sources)
where TArgs : IDto
where TEntity : IEntity
{
if (entity is null)
{
var e = args.AsEntity(State.Default);
return e.AsArguments(args);
}
return Merge(entity, args, State.Default, sources).AsArguments();
}
public static TArgs AsArguments(this IEntity entity, params object[] sources) where TArgs : IDto
{
var instance = typeof(TArgs).CreateInstance();
return Serializer.Merge(instance, entity, sources);
}
public static TArgs AsEventArguments(this IEntity entity) where TArgs : IEventArgs
{
var instance = typeof(TArgs).CreateInstance();
return Serializer.Merge(instance, entity);
}
public static TEntity Merge(this TEntity existing, IDto modifier, State state, params object[] sources)
where TEntity : IEntity
{
var newEntity = Activator.CreateInstance();
return Serializer.Merge(newEntity, existing, modifier, new StateModifier { State = state }, sources);
}
public static async Task> AsEntities(this IQueryable source, CancellationToken cancellationToken = default)
{
if (source is null)
return ImmutableList.Empty;
var list = new List();
await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken))
list.Add(element);
return list.ToImmutableList();
}
public static async Task> AsEntities(this IEnumerable source)
{
if (source is null)
return ImmutableList.Empty;
await Task.CompletedTask;
return source.ToImmutableList();
}
public static async Task AsEntity(this IQueryable source, CancellationToken cancellationToken = default)
{
if (source is null)
return default;
await Task.CompletedTask;
return Execute(QueryableMethods.SingleOrDefaultWithoutPredicate, source, cancellationToken);
}
public static async Task AsEntity(this IEnumerable source)
{
if (source is null)
return default;
await Task.CompletedTask;
return source.FirstOrDefault();
}
public static IEnumerable WithArguments(this IEnumerable source, QueryArgs args)
{
return source.WithOrderBy(args).WithPaging(args);
}
private static IEnumerable WithPaging(this IEnumerable 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 WithOrderBy(this IEnumerable entities, QueryArgs args)
{
if (entities.AsQueryable() as IOrderedQueryable is not IOrderedQueryable result)
return entities;
var top = true;
foreach (var criteria in args.OrderBy)
{
if (top)
{
if (criteria.Mode == OrderByMode.Ascending)
result = result.OrderBy(ResolvePropertyPredicate(criteria.Property));
else
result = result.OrderByDescending(ResolvePropertyPredicate(criteria.Property));
}
else
{
if (criteria.Mode == OrderByMode.Ascending)
result = result.ThenBy(ResolvePropertyPredicate(criteria.Property));
else
result = result.ThenByDescending(ResolvePropertyPredicate(criteria.Property));
}
top = false;
}
return result;
}
private static Expression> ResolvePropertyPredicate(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>(convertedMemberAccess, param);
return orderPredicate;
}
private static TResult? Execute(MethodInfo operatorMethodInfo, IQueryable 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(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default)
{
return Execute(operatorMethodInfo, source, (Expression?)null, cancellationToken);
}
public static PropertyInfo? PrimaryKeyProperty(this IEntity entity)
{
foreach (var property in entity.GetType().GetInheritedProperites())
{
if (property.FindAttribute() 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();
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 AsAsyncEnumerable(this IQueryable source)
{
if (source is IAsyncEnumerable asyncEnumerable)
return asyncEnumerable;
throw new InvalidOperationException();
}
}