using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace InABox.Core { public static class AggregateUtils { private static string RemoveConvert(Expression expression) { // We were running a ToString on expression and removing the convert using string functions, however this failed when // .NET has a different string representation for .NET 6.0; // Compare "Login => Convert(Login.User.ID)" and "Login => Convert(Login.User.ID, Object)" if (expression is LambdaExpression lambda) { var body = lambda.Body; if (body is UnaryExpression unary && body.NodeType == ExpressionType.Convert) { var operand = unary.Operand; return operand.ToString(); } return body.ToString(); } // Probably not, but it is for now return expression.ToString(); //String result = expression.ToString().Split(new String[] { "=>" }, StringSplitOptions.RemoveEmptyEntries).Last().Trim(); //if (result.ToUpper().StartsWith("CONVERT(")) // result = result.Split('(', ')')[1]; //return result; } private static string ProcessConstantExpression(Expression expression) { var result = expression.ToString(); return result; } public static string ProcessExpression(Expression expr) { if (expr.NodeType == ExpressionType.Convert) expr = ((UnaryExpression)expr).Operand; //if (expr.NodeType == ExpressionType.MemberAccess) //{ // var result = Expression.Lambda(expr).Compile().DynamicInvoke(); // return result == null ? "null" : result.ToString(); //} if (expr is ConstantExpression) return ProcessConstantExpression(expr); if (expr is MemberExpression && ((MemberExpression)expr).Expression == null) { var result = Expression.Lambda(expr).Compile().DynamicInvoke(); return expr.Type.IsDefault(result) ? "NULL" : expr.Type == typeof(string) ? string.Format("\"{0}\"", result) : result.ToString(); } return string.Join(".", RemoveConvert(expr).Split('.').Skip(1)); } } #region Aggregates public enum AggregateCalculation { None, Sum, Count, Maximum, Minimum, Average } public interface ICoreAggregate { Expression> Aggregate { get; } AggregateCalculation Calculation { get; } } public abstract class CoreAggregate : ICoreAggregate { public abstract Expression> Aggregate { get; } public abstract AggregateCalculation Calculation { get; } public string GetAggregate() { return string.Join(".", Aggregate.ToString().Split('.').Skip(1)); } } public interface ICoreAggregate { Expression> Aggregate { get; } Filter? Filter { get; } Dictionary>, Expression>> Links { get; } AggregateCalculation Calculation { get; } Dictionary GetLinks(); } public abstract class CoreAggregate : ICoreAggregate { public abstract Expression> Aggregate { get; } public virtual Filter? Filter => null; public abstract Dictionary>, Expression>> Links { get; } public Dictionary GetLinks() { var result = new Dictionary(); foreach (var link in Links) { var childkey = AggregateUtils.ProcessExpression(link.Key); // String.Join(".", link.Key.ToString().Split('.').Skip(1)); var parentkey = AggregateUtils.ProcessExpression(link.Value); // String.Join(".", link.Value.ToString().Split('.').Skip(1); result[childkey] = parentkey; } return result; } public abstract AggregateCalculation Calculation { get; } public string GetAggregate() { return string.Join(".", Aggregate.ToString().Split('.').Skip(1)); } } public class AggregateAttribute : Attribute { public AggregateAttribute(Type calculator) { Calculator = Activator.CreateInstance(calculator); } public object Calculator { get; } public Type Source => GetSource(); public AggregateCalculation Calculation => GetCalculation(); public string Aggregate => GetAggregate(); public Dictionary Links => GetLinks(); public IFilter? Filter => GetFilter(); #region Internal (Reflection) functions private Type GetSource() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>)); if (intf != null) return intf.GenericTypeArguments[1]; intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>)); if (intf != null) return intf.GenericTypeArguments[0]; throw new Exception("Unable to Locate Type Information for Aggregate"); } private string GetAggregate() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>)); if (intf == null) { intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>)); } if (intf != null) { var prop = intf.GetProperty("Aggregate"); if (prop != null) { var obj = prop.GetValue(Calculator); if (obj != null) { var expr = obj as Expression; if (expr != null) return AggregateUtils.ProcessExpression(expr); } } } return ""; } private Dictionary GetLinks() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>)); if (intf != null) { var method = intf.GetMethod("GetLinks"); if (method != null) { var dict = method.Invoke(Calculator, new object[] { }); return (dict as Dictionary)!; } } return new Dictionary(); } private AggregateCalculation GetCalculation() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>)); if (intf == null) intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,>)); if (intf != null) { var prop = intf.GetProperty("Calculation"); if (prop != null) return (AggregateCalculation)prop.GetValue(Calculator); } return AggregateCalculation.None; } private IFilter? GetFilter() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICoreAggregate<,,>)); if (intf != null) { var prop = intf.GetProperty("Filter"); if (prop != null) return prop.GetValue(Calculator) as IFilter; } return null; } #endregion } #endregion #region Formulas public enum FormulaType { Virtual, Permanent } public enum FormulaOperator { None, Add, Subtract, Multiply, Divide, Minumum, Maximum } public interface IFormula { Expression> Value { get; } Expression>[] Modifiers { get; } FormulaOperator Operator { get; } FormulaType Type { get; } } public interface IFormula { String Value { get; } String[] Modifiers { get; } FormulaOperator Operator { get; } FormulaType Type { get; } } public class FormulaAttribute : Attribute, IFormula { public FormulaAttribute(Type calculator) { Calculator = Activator.CreateInstance(calculator); } public object Calculator { get; } public string Value => GetExpressionName("Value"); public string[] Modifiers => GetExpressionNames("Modifiers"); public FormulaOperator Operator => GetFormulaOperator(); public FormulaType Type => GetFormulaType(); #region Internal (Reflection) functions private FormulaOperator GetFormulaOperator() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>)); if (intf != null) { var prop = intf.GetProperty("Operator"); if (prop != null) return (FormulaOperator)prop.GetValue(Calculator); } return FormulaOperator.None; } private FormulaType GetFormulaType() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>)); if (intf != null) { var prop = intf.GetProperty("Type"); if (prop != null) return (FormulaType)prop.GetValue(Calculator); } return FormulaType.Virtual; } private string GetExpressionName(string property) { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>)); if (intf != null) { var prop = intf.GetProperty(property); if (prop != null) { if(prop.GetValue(Calculator) is LambdaExpression expr) { var result = AggregateUtils.ProcessExpression(expr.Body); return result; } } } return ""; } private string[] GetExpressionNames(string property) { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFormula<,>)); if (intf != null) { var prop = intf.GetProperty(property); if (prop != null) { if(prop.GetValue(Calculator) is LambdaExpression[] expressions) { var result = new List(); foreach (var expression in expressions) result.Add(AggregateUtils.ProcessExpression(expression.Body)); return result.ToArray(); } //return expressions.Select(x => x.Body is ConstantExpression ? ProcessConstantExpression(x.Body) : String.Join(".", RemoveConvert(x.Body).Split('.').Skip(1))).ToArray(); } } return new string[] { }; } #endregion } #endregion #region Conditions public enum Condition { None, Equals, NotEqual, GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo } public enum ConditionType { Virtual, Permanent } public interface ICondition { Expression> Left { get; } Condition Condition { get; } Expression> Right { get; } Expression> True { get; } Expression> False { get; } ConditionType Type { get; } } public class ConditionAttribute : Attribute { public ConditionAttribute(Type calculator) { Calculator = Activator.CreateInstance(calculator); } public object Calculator { get; } public string Left => GetExpressionName("Left"); public Condition Condition => GetCondition(); public string Right => GetExpressionName("Right"); public string True => GetExpressionName("True"); public string False => GetExpressionName("False"); public ConditionType Type => GetConditionType(); #region Internal (Reflection) functions private Condition GetCondition() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICondition<,,>)); if (intf != null) { var prop = intf.GetProperty("Condition"); if (prop != null) return (Condition)prop.GetValue(Calculator); } return Condition.None; } private ConditionType GetConditionType() { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICondition<,,>)); if (intf != null) { var prop = intf.GetProperty("Type"); if (prop != null) return (ConditionType)prop.GetValue(Calculator); } return ConditionType.Virtual; } private string GetExpressionName(string property) { var intf = Calculator.GetType().GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICondition<,,>)); if (intf != null) { var prop = intf.GetProperty(property); if (prop?.GetValue(Calculator) is LambdaExpression expr) { return AggregateUtils.ProcessExpression(expr.Body); } } return ""; } #endregion } #endregion }