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