Here what we want to do.
We have data from the database that we need to format to make a report, including some calculation (Sum, Averages, and field to field calculation (ex : x.a / x.b)).
One of the limitations is that if, in a sum for exemple, one of the data is null, -1 or -2 we have to stop the calculation and display '-'. Since we have many reports to produce, with the same logic and many calculation in each, we want to centralise this logic. For now, the code we produce allow us to check for field to field calculation (x.a / x.b for exemple), but can't allow us to check for group total (ex: x.b / SUM(x.a))
Test case
Rules
The calcul should not be done if one of the value used in the calcul is -1, -2 or null. In this case, return "-" if you find -1 or null, and "C" if you find -2
If you have multiple "bad values" in the calcul, you need to respect a priority defined like this: null -> -1 -> -2. This priority is independant of the level where the value is in the calcul
Tests
Simple calcul
object: new DataInfo { A = 10, B = 2, C = 4 }
calcul: x => x.A / x.B + x.C
result: 9
object: new DataInfo { A = 10, B = 2, C = -2 }
calcul: x => x.A / x.B + x.C
result: C (because you have a '-2' value in the calcul)
object: new DataInfo { A = 10, B = -2, C = null }
calcul: x => x.A / x.B + x.C
result: - (because you have a 'null' value in the calcul and it win on the -2 value)
Complex calcul
object: var list = new List();
list.Add(new DataInfo { A = 10, B = 2, C = 4 });
list.Add(new DataInfo { A = 6, B = 3, C = 2 });
calcul: list.Sum(x => x.A / x.B + list.Max(y => y.C))
result: 15
object: var list = new List();
list.Add(new DataInfo { A = 10, B = 2, C = 4 });
list.Add(new DataInfo { A = 6, B = 3, C = -2 });
calcul: list.Sum(x => x.A / x.B + list.Max(y => y.C))
result: C (because you have a '-2' value in the calcul)
What we have done so far
Here the code we have to handle simple calculs, based on this thread:
How to extract properties used in a Expression<Func<T, TResult>> query and test their value?
We have created a strongly type class that perform a calcul and return the result as a String. But if any part of the expression is equal to a special value, the calculator has to return a special character.
It works well for a simple case, like this one:
var data = new Rapport1Data() { UnitesDisponibles = 5, ... };
var q = new Calculator<Rapport1Data>()
.Calcul(data, y => y.UnitesDisponibles, "N0");
But I need to be able to perform something more complicated like:
IEnumerable<Rapport1Data> data = ...;
var q = new Calculator<IEnumerable<Rapport1Data>>()
.Calcul(data, x => x.Sum(y => y.UnitesDisponibles), "N0");
When we start encapsulating or data in IEnurmarable<> we get an error:
Object does not match target type
As we understand it, it's because the Sub-Expression y => y.UnitesDisponibles is being applied to the IEnumerable instead of the Rapport1Data.
How can we fix it to ensure that it will be fully recursive if we some day have complex expression like:
IEnumerable<IEnumerable<Rapport1Data>> data = ...;
var q = new Calculator<IEnumerable<IEnumerable<Rapport1Data>>>()
.Calcul(data,x => x.Sum(y => y.Sum(z => z.UnitesDisponibles)), "N0");
Classes we've built
public class Calculator<T>
{
public string Calcul(
T data,
Expression<Func<T, decimal?>> query,
string format)
{
var rulesCheckerResult = RulesChecker<T>.Check(data, query);
// l'ordre des vérifications est importante car il y a une gestion
// des priorités des codes à retourner!
if (rulesCheckerResult.HasManquante)
{
return TypeDonnee.Manquante.ReportValue;
}
if (rulesCheckerResult.HasDivisionParZero)
{
return TypeDonnee.DivisionParZero.ReportValue;
}
if (rulesCheckerResult.HasNonDiffusable)
{
return TypeDonnee.NonDiffusable.ReportValue;
}
if (rulesCheckerResult.HasConfidentielle)
{
return TypeDonnee.Confidentielle.ReportValue;
}
// if the query respect the rules, apply the query and return the
// value
var result = query.Compile().Invoke(data);
return result != null
? result.Value.ToString(format)
: TypeDonnee.Manquante.ReportValue;
}
}
and the Custom ExpressionVisitor
class RulesChecker<T> : ExpressionVisitor
{
private readonly T data;
private bool hasConfidentielle = false;
private bool hasNonDiffusable = false;
private bool hasDivisionParZero = false;
private bool hasManquante = false;
public RulesChecker(T data)
{
this.data = data;
}
public static RulesCheckerResult Check(T data, Expression expression)
{
var visitor = new RulesChecker<T>(data);
visitor.Visit(expression);
return new RulesCheckerResult(
visitor.hasConfidentielle,
visitor.hasNonDiffusable,
visitor.hasDivisionParZero,
visitor.hasManquante);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (!this.hasDivisionParZero &&
node.NodeType == ExpressionType.Divide &&
node.Right.NodeType == ExpressionType.MemberAccess)
{
var rightMemeberExpression = (MemberExpression)node.Right;
var propertyInfo = (PropertyInfo)rightMemeberExpression.Member;
var value = Convert.ToInt32(propertyInfo.GetValue(this.data, null));
this.hasDivisionParZero = value == 0;
}
return base.VisitBinary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
// Si l'un d'eux n'est pas à true, alors continuer de faire les tests
if (!this.hasConfidentielle ||
!this.hasNonDiffusable ||
!this.hasManquante)
{
var propertyInfo = (PropertyInfo)node.Member;
object value = propertyInfo.GetValue(this.data, null);
int? valueNumber = MTO.Framework.Common.Convert.To<int?>(value);
// Si la valeur est à true, il n'y a pas lieu de tester davantage
if (!this.hasManquante)
{
this.hasManquante =
valueNumber == TypeDonnee.Manquante.BdValue;
}
// Si la valeur est à true, il n'y a pas lieu de tester davantage
if (!this.hasConfidentielle)
{
this.hasConfidentielle =
valueNumber == TypeDonnee.Confidentielle.BdValue;
}
// Si la valeur est à true, il n'y a pas lieu de tester davantage
if (!this.hasNonDiffusable)
{
this.hasNonDiffusable =
valueNumber == TypeDonnee.NonDiffusable.BdValue;
}
}
return base.VisitMember(node);
}
}
[UPDATE]
Adding more detail on what we want to do
There are a few things that you need to change to get this to work:
Create a new ExpressionVisitor that will preprocess your expression to execute the aggregates.
Use the new ExpressionVisitor in the Calculator.Calcul method.
Modify the RulesChecker to include an override for the VisitConstant method. This new method needs to include the same logic that is in the VisitMember method.
Modify the RulesChecker VisitBinary method to check the divide by zero condition for ConstantExpressions.
Here is a rough example of what I think needs to be done.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace WindowsFormsApplication1 {
internal static class Program {
[STAThread]
private static void Main() {
var calculator = new Calculator();
//// DivideByZero - the result should be -1
var data1 = new DataInfo { A = 10, B = 0, C = 1 };
Expression<Func<DataInfo, decimal?>> expression1 = x => x.A / x.B + x.C;
var result1 = calculator.Calcul(data1, expression1, "N0");
//// Negative 1 - the result should be -
var data2 = new DataInfo { A = 10, B = 5, C = -1 };
Expression<Func<DataInfo, decimal?>> expression2 = x => x.A / x.B + x.C;
var result2 = calculator.Calcul(data2, expression2, "N0");
//// Negative 2 - the result should be C
var data3 = new DataInfo { A = 10, B = 5, C = -2 };
Expression<Func<DataInfo, decimal?>> expression3 = x => x.A / x.B + x.C;
var result3 = calculator.Calcul(data3, expression3, "N0");
//// the result should be 3
var data4 = new DataInfo { A = 10, B = 5, C = 1 };
Expression<Func<DataInfo, decimal?>> expression4 = x => x.A / x.B + x.C;
var result4 = calculator.Calcul(data4, expression4, "N0");
//// DivideByZero - the result should be -1
var data5 = new List<DataInfo> {
new DataInfo {A = 10, B = 0, C = 1},
new DataInfo {A = 10, B = 0, C = 1}
};
Expression<Func<IEnumerable<DataInfo>, decimal?>> expression5 = x => x.Sum(y => y.A) / x.Sum(y => y.B) + x.Sum(y => y.C);
var result5 = calculator.Calcul(data5, expression5, "N0");
//// the result should be 4
var data6 = new List<DataInfo> {
new DataInfo {A = 10, B = 5, C = 1},
new DataInfo {A = 10, B = 5, C = 1}
};
Expression<Func<IEnumerable<DataInfo>, decimal?>> expression6 = x => x.Sum(y => y.A) / x.Sum(y => y.B) + x.Sum(y => y.C);
var result6 = calculator.Calcul(data6, expression6, "N0");
//// the result should be -
var data7 = new List<DataInfo> {
new DataInfo {A = 10, B = 5, C = -1},
new DataInfo {A = 10, B = 5, C = 1}
};
Expression<Func<IEnumerable<DataInfo>, decimal?>> expression7 = x => x.Sum(y => y.A) / x.Sum(y => y.B) + x.Sum(y => y.C);
var result7 = calculator.Calcul(data7, expression7, "N0");
//// the result should be 14
var c1 = 1;
var c2 = 2;
var data8 = new DataInfo { A = 10, B = 1, C = 1 };
Expression<Func<DataInfo, decimal?>> expression8 = x => x.A / x.B + x.C + c1 + c2;
var result8 = calculator.Calcul(data8, expression8, "N0");
}
}
public class Calculator {
public string Calcul<T>(T data, LambdaExpression query, string format) {
string reportValue;
if (HasIssue(data, query, out reportValue)) {
return reportValue;
}
// executes the aggregates
query = (LambdaExpression)ExpressionPreProcessor.PreProcessor(data, query);
// checks the rules against the results of the aggregates
if (HasIssue(data, query, out reportValue)) {
return reportValue;
}
Delegate lambda = query.Compile();
decimal? result = (decimal?)lambda.DynamicInvoke(data);
return result != null
? result.Value.ToString(format)
: TypeDonnee.Manquante.ReportValue;
}
private bool HasIssue(object data, LambdaExpression query, out string reportValue) {
reportValue = null;
var rulesCheckerResult = RulesChecker.Check(data, query);
if (rulesCheckerResult.HasManquante) {
reportValue = TypeDonnee.Manquante.ReportValue;
}
if (rulesCheckerResult.HasDivisionParZero) {
reportValue = TypeDonnee.DivisionParZero.ReportValue;
}
if (rulesCheckerResult.HasNonDiffusable) {
reportValue = TypeDonnee.NonDiffusable.ReportValue;
}
if (rulesCheckerResult.HasConfidentielle) {
reportValue = TypeDonnee.Confidentielle.ReportValue;
}
return reportValue != null;
}
}
internal class ExpressionPreProcessor : ExpressionVisitor {
private readonly object _source;
public static Expression PreProcessor(object source, Expression expression) {
if (!IsValidSource(source)) {
return expression;
}
var visitor = new ExpressionPreProcessor(source);
return visitor.Visit(expression);
}
private static bool IsValidSource(object source) {
if (source == null) {
return false;
}
var type = source.GetType();
return type.IsGenericType && type.GetInterface("IEnumerable") != null;
}
public ExpressionPreProcessor(object source) {
this._source = source;
}
protected override Expression VisitMethodCall(MethodCallExpression node) {
if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments.Count == 2) {
switch (node.Method.Name) {
case "Count":
case "Min":
case "Max":
case "Sum":
case "Average":
var lambda = node.Arguments[1] as LambdaExpression;
var lambaDelegate = lambda.Compile();
var value = node.Method.Invoke(null, new object[] { this._source, lambaDelegate });
return Expression.Constant(value);
}
}
return base.VisitMethodCall(node);
}
}
internal class RulesChecker : ExpressionVisitor {
private readonly object data;
private bool hasConfidentielle = false;
private bool hasNonDiffusable = false;
private bool hasDivisionParZero = false;
private bool hasManquante = false;
public RulesChecker(object data) {
this.data = data;
}
public static RulesCheckerResult Check(object data, Expression expression) {
if (IsIEnumerable(data)) {
var result = new RulesCheckerResult(false, false, false, false);
IEnumerable dataItems = (IEnumerable)data;
foreach (object dataItem in dataItems) {
result = MergeResults(result, GetResults(dataItem, expression));
}
return result;
}
else {
return GetResults(data, expression);
}
}
private static RulesCheckerResult MergeResults(RulesCheckerResult results1, RulesCheckerResult results2) {
var hasConfidentielle = results1.HasConfidentielle || results2.HasConfidentielle;
var hasDivisionParZero = results1.HasDivisionParZero || results2.HasDivisionParZero;
var hasManquante = results1.HasManquante || results2.HasManquante;
var hasNonDiffusable = results1.HasNonDiffusable || results2.HasNonDiffusable;
return new RulesCheckerResult(hasConfidentielle, hasNonDiffusable, hasDivisionParZero, hasManquante);
}
private static RulesCheckerResult GetResults(object data, Expression expression) {
var visitor = new RulesChecker(data);
visitor.Visit(expression);
return new RulesCheckerResult(
visitor.hasConfidentielle,
visitor.hasNonDiffusable,
visitor.hasDivisionParZero,
visitor.hasManquante);
}
private static bool IsIEnumerable(object source) {
if (source == null) {
return false;
}
var type = source.GetType();
return type.IsGenericType && type.GetInterface("IEnumerable") != null;
}
protected override Expression VisitBinary(BinaryExpression node) {
if (!this.hasDivisionParZero && node.NodeType == ExpressionType.Divide) {
if (node.Right.NodeType == ExpressionType.MemberAccess) {
var rightMemeberExpression = (MemberExpression)node.Right;
var propertyInfo = (PropertyInfo)rightMemeberExpression.Member;
var value = Convert.ToInt32(propertyInfo.GetValue(this.data, null));
this.hasDivisionParZero = value == 0;
}
if (node.Right.NodeType == ExpressionType.Constant) {
var rightConstantExpression = (ConstantExpression)node.Right;
var value = Convert.ToInt32(rightConstantExpression.Value);
this.hasDivisionParZero = value == 0;
}
}
return base.VisitBinary(node);
}
protected override Expression VisitConstant(ConstantExpression node) {
this.CheckValue(this.ConvertToNullableInt(node.Value));
return base.VisitConstant(node);
}
protected override Expression VisitMember(MemberExpression node) {
if (!this.hasConfidentielle || !this.hasNonDiffusable || !this.hasManquante) {
var propertyInfo = node.Member as PropertyInfo;
if (propertyInfo != null) {
var value = propertyInfo.GetValue(this.data, null);
this.CheckValue(this.ConvertToNullableInt(value));
}
}
return base.VisitMember(node);
}
private void CheckValue(int? value) {
if (!this.hasManquante) {
this.hasManquante = value == TypeDonnee.Manquante.BdValue;
}
if (!this.hasConfidentielle) {
this.hasConfidentielle = value == TypeDonnee.Confidentielle.BdValue;
}
if (!this.hasNonDiffusable) {
this.hasNonDiffusable = value == TypeDonnee.NonDiffusable.BdValue;
}
}
private int? ConvertToNullableInt(object value) {
if (!value.GetType().IsPrimitive) {
return int.MinValue;
}
// MTO.Framework.Common.Convert.To<int?>(value);
return (int?)value;
}
}
class RulesCheckerResult {
public bool HasConfidentielle { get; private set; }
public bool HasNonDiffusable { get; private set; }
public bool HasDivisionParZero { get; private set; }
public bool HasManquante { get; private set; }
public RulesCheckerResult(bool hasConfidentielle, bool hasNonDiffusable, bool hasDivisionParZero, bool hasManquante) {
this.HasConfidentielle = hasConfidentielle;
this.HasNonDiffusable = hasNonDiffusable;
this.HasDivisionParZero = hasDivisionParZero;
this.HasManquante = hasManquante;
}
}
class TypeDonnee {
public static readonly TypeValues Manquante = new TypeValues(null, "-");
public static readonly TypeValues Confidentielle = new TypeValues(-1, "-");
public static readonly TypeValues NonDiffusable = new TypeValues(-2, "C");
public static readonly TypeValues DivisionParZero = new TypeValues(0, "-1");
}
class TypeValues {
public int? BdValue { get; set; }
public string ReportValue { get; set; }
public TypeValues(int? bdValue, string reportValue) {
this.BdValue = bdValue;
this.ReportValue = reportValue;
}
}
class DataInfo {
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
}
If I understand correctly you're looking for a 'recursive sum' function. Might I suggest something like this?
var q = new Calculator<Rapport1Data>()
.Calcul(data, y => RecursiveCalc(y), "N0");
double RecursiveCalc(object toCalc)
{
var asRapport = toCalc as Rapport1Data;
if (asRapport != null)
return asRapport.UnitesDisponibles;
var asEnumerable = toCalc as IEnumerable;
if (asEnumerable != null)
return asEnumerable.Sum(y => RecursiveCalc(y));
return 0; // handle a condition for unexpected types
}
*note: code not tested, might not even compile
Related
I have a LINQ statement I want to convert it into Expression Tree
public class tblEmpLocation
{
public uint EmpLocationId { get; set; }
public uint? EmpId { get; set; }
public uint? LocationId { get; set; }
public DateTime EffectiveDt { get; set; } = DateTime.Now;
}
We have employee location class basically have the location the Employee Id and Location Id.
public class temptable
{
public uint Id{ get; set; }
public DateTime EffectiveDt { get; set; }
}
We have the temp class which basically contain the Id and Effective date that is a sample class. Now we have similar table like employee Department, employee salary etc. so we want to create an linq extension that basically take the class as input and get the desired result
List<tblEmpLocation> tbls = new List<tblEmpLocation>();
var data=tbls.GroupBy(p => p.EmpId).Select(q => new temptable{ Id=q.Key, EffectiveDt=q.Max(r => r.EffectiveDt) });
thanks for help
This is universal implementation of DistinctBy method, which returns last record of the group.
Schematically when you make the following call:
query = query.DistinctBy(e => e.EmpId, e => e.EffectiveDt);
// or with complex keys
query = query.DistinctBy(e => new { e.EmpId, e.Other }, e => new { e.EffectiveDt, e.SomeOther});
Or fully dynamic
query = query.DistinctBy("EmpId", "EffectiveDt");
Function generates the following query:
query =
from d in query.Select(d => d.EmpId).Distinct()
from e in query
.Where(e => e.EmpId == d)
.OrderByDescending(e => e.EffectiveDt)
.Take(1)
select e;
Or with complex keys:
query =
from d in query.Select(d => new { d.EmpId, d.Other }).Distinct()
from e in query
.Where(e => e.EmpId == d.EmpId && e.Other == d.Other)
.OrderByDescending(e => e.EffectiveDt)
.ThenByDescending(e => e.SomeOther)
.Take(1)
select e;
And realisation:
public static class QueryableExtensions
{
public static IQueryable<T> DistinctBy<T>(
this IQueryable<T> source,
string distinctPropName,
string maxPropName)
{
var entityParam = Expression.Parameter(typeof(T), "e");
var distinctBy = Expression.Lambda(MakePropPath(entityParam, distinctPropName), entityParam);
var maxBy = Expression.Lambda(MakePropPath(entityParam, maxPropName), entityParam);
var queryExpression = Expression.Call(typeof(QueryableExtensions), nameof(QueryableExtensions.DistinctBy),
new[] { typeof(T), distinctBy.Body.Type, maxBy.Body.Type },
Expression.Constant(source),
Expression.Quote(distinctBy),
Expression.Quote(maxBy));
var executionLambda = Expression.Lambda<Func<IQueryable<T>>>(queryExpression);
return executionLambda.Compile()();
}
public static IQueryable<T> DistinctBy<T, TKey, TMax>(
this IQueryable<T> source,
Expression<Func<T, TKey>> distinctBy,
Expression<Func<T, TMax>> maxBy)
{
var distinctQuery = source.Select(distinctBy).Distinct();
var distinctParam = Expression.Parameter(typeof(TKey), "d");
var entityParam = distinctBy.Parameters[0];
var mapping = MapMembers(distinctBy.Body, distinctParam).ToList();
var orderParam = maxBy.Parameters[0];
var oderMapping = CollectMembers(maxBy.Body).ToList();
var whereExpr = mapping.Select(t => Expression.Equal(t.Item1, t.Item2))
.Aggregate(Expression.AndAlso);
var whereLambda = Expression.Lambda(whereExpr, entityParam);
// d => query.Where(x => d.distinctBy == x.distinctBy).Take(1)
Expression selectExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Where), new[] { typeof(T) },
source.Expression,
whereLambda);
// prepare OrderByPart
for (int i = 0; i < oderMapping.Count; i++)
{
var orderMethod = i == 0 ? nameof(Queryable.OrderByDescending) : nameof(Queryable.ThenByDescending);
var orderItem = oderMapping[i];
selectExpression = Expression.Call(typeof(Queryable), orderMethod, new[] { typeof(T), orderItem.Type },
selectExpression, Expression.Lambda(orderItem, orderParam));
}
// Take(1)
selectExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Take), new[] { typeof(T) },
selectExpression,
Expression.Constant(1));
var selectManySelector =
Expression.Lambda<Func<TKey, IEnumerable<T>>>(selectExpression, distinctParam);
var selectManyQuery = Expression.Call(typeof(Queryable), nameof(Queryable.SelectMany),
new[] { typeof(TKey), typeof(T) }, distinctQuery.Expression, selectManySelector);
return source.Provider.CreateQuery<T>(selectManyQuery);
}
static Expression MakePropPath(Expression objExpression, string path)
{
return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
}
private static IEnumerable<Tuple<Expression, Expression>> MapMembers(Expression expr, Expression projectionPath)
{
switch (expr.NodeType)
{
case ExpressionType.New:
{
var ne = (NewExpression)expr;
for (int i = 0; i < ne.Arguments.Count; i++)
{
foreach (var e in MapMembers(ne.Arguments[i], Expression.MakeMemberAccess(projectionPath, ne.Members[i])))
{
yield return e;
}
}
break;
}
default:
yield return Tuple.Create(projectionPath, expr);
break;
}
}
private static IEnumerable<Expression> CollectMembers(Expression expr)
{
switch (expr.NodeType)
{
case ExpressionType.New:
{
var ne = (NewExpression)expr;
for (int i = 0; i < ne.Arguments.Count; i++)
{
yield return ne.Arguments[i];
}
break;
}
default:
yield return expr;
break;
}
}
}
i am trying to use projection map to map the property values from source to destination as mentioned here in this question Projection mapping looks like as below but getting error near select statement
Error is : The type arguments for the method
'Enumerable.Select<TSource, Tresult>(IEnumerable,
Func<Tsource, int, Tresult>)' cannot be inferred from usage.Try
specifying type arguments explicitly
and below is the code sample
public static IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
where TDest : new()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap = from d in destProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source)));
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, TDest>>(memberInitExpression, itemParam);
return sourceModel.Select(projection);
}
and then i am using above method below
private static MechanicalData TransformMechanicalData(MechanicalData sourceMechanicalData, Dictionary<string, MasterSection> masterSectionMappedLibrary)
{
return new MechanicalData()
{
Acoustic = sourceMechanicalData.Acoustic
.Where(a => a != null)
.Select(ProjectionMap<LibraryAcoustic, LibraryAcoustic>(sourceMechanicalData.Acoustic.AsQueryable())).ToList() ?? new(),
}
}
Could any one please let me know where I am doing wrong, many thanks in advance.
Update:
Acoustic = sourceMechanicalData.Acoustic
.Where(a => a != null)
.Select(acoustic => new LibraryAcoustic
{
Id = acoustic.Id,
IsApproved = true,
NoiseCriteria = acoustic.NoiseCriteria,
SourceOfData = acoustic.SourceOfData,
SourceOfDataId = acoustic.SourceOfData.Id,
MasterSection = masterSectionMappedLibrary["Library Acoustic"]
}).ToList() ?? new(),
calling that transformMechanicalData method in below
if (spaceTypeReader.HasRows)
{
while (spaceTypeReader.Read())
{
var id = spaceTypeReader.IsDBNull(0) ? default : Guid.Parse(spaceTypeReader.GetString(0));
var mechanicalDataJson = spaceTypeReader.IsDBNull(1) ? "null" : spaceTypeReader.GetString(1);
var srcMechanicalDataJson = JsonConvert.DeserializeObject<MechanicalData>(mechanicalDataJson);
fixedSpaceTypesMechanicalData[id] = TransformMechanicalData(srcMechanicalDataJson, masterSectionMappedLibrary);
}
}
and mechanical data class be like
public class MechanicalData
{
public List<LibraryAcoustic> Acoustic { get; set; }
.........
}
Update 2:
model for libraryAcoustic
public class LibraryAcoustic
{
public double? NoiseCriteria { get; set; }
[ForeignKey("SourceOfData")]
public Guid? SourceOfDataId { get; set; }
public virtual CodeStandardGuideline SourceOfData { get; set; }
public Guid Id { get; set; }
public MasterSection MasterSection { get; set; }
public bool? IsApproved { get; set; }
}
FROM Model
"Acoustic": [
{
"Id": "d9254132-d11d-48dd-9b74-c0b60d1e4b8a",
"IsApproved": null,
"SourceOfData": {
"Id": "c5bf3585-50b1-4894-8fad-0ac884343935",
"CodeStandardGuidelineType": "GUIDELINE_OR_STANDARD"
},
"MasterSection": null,
"SourceOfDataId": null,
"NoiseCriteria": 1,
}
],
TO model:
"Acoustic": [
{
"Id": "d9254132-d11d-48dd-9b74-c0b60d1e4b8a",
"IsApproved": true,
"SourceOfData": {
"Id": "c5bf3585-50b1-4894-8fad-0ac88434393",
"CodeStandardGuidelineType": "GUIDELINE_OR_STANDARD"
},
"MasterSection": {Name:"test"},
"SourceOfDataId": "c5bf3585-50b1-4894-8fad-0ac884343935",
"NoiseCriteria": 1,
}
],
Test Class update:
SourceOfData sourceOfData = new SourceOfData()
{
Id = new Guid("c5bf3585-50b1-4894-8fad-0ac884343935"),
Name = "test"
};
TestClassA170 testClassA170 = new TestClassA170()
{
Category = "test",
SourceOfData = sourceOfData,
SourceOfDataId = null,
IsApproved = true,
MinOutdoorAirACH = 1,
MinTotalAirACH = 2,
DirectExhaust = DirectExhaust.NO,
PressureRelationship = PressureRelationship.NEGATIVE,
RecirculatedAir = RecirculatedAir.NO,
SpaceFunction = "10"
};
List<TestClassA170> list = new List<TestClassA170>();
list.Add(testClassA170);
You have to create mapping helper class which accespts additionally Dictionary as paraneter:
public static class PropertyMapper<TSource, TDest>
{
private static Expression<Func<TSource Dictionary<string, MasterSection>, TDest>> _mappingExpression;
private static Func<TSource, Dictionary<string, MasterSection>, TDest> _mapper;
static PropertyMapper()
{
_mappingExpression = ProjectionMap();
_mapper = _mappingExpression.Compile();
}
public static Func<TSource, Dictionary<string, MasterSection>, TDest> Mapper => _mapper;
public static string MasterKeyFromClassName(string className)
{
// you have to do that yourself
throw new NotImplementedException();
}
public static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> ProjectionMap()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap =
from d in destProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
where d.Name != "MasterSection"
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var dictParam = Expression.Parameter(typeof(Dictionary<string, MasterSection>), "dict");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source))).ToList();
var masterSectionProp = destProperties.FirstOrDefault(s => s.Name == "MasterSection");
if (masterSectionProp != null)
{
Expression<Func<Dictionary<string, MasterSection>, string, MasterSection>> dictRetrievalTemplate = (dict, value) => dict[value];
var masterPropertyBind = Expression.Bind(masterSectionProp, ExpressionReplacer.GetBody(dictRetrievalTemplate, dictParam, Expression.Constant(MasterKeyFromClassName(typeof(TSource).Name)));
memberBindings.Add(masterPropertyBind);
}
var sourceOfDataProp = destProperties.FirstOrDefault(s => s.Name == "SourceOfDataId");
if (sourceOfDataProp != null)
{
memberBindings.Add(Expression.Bind(sourceOfDataProp, Expression.Property(Expression.Property(itemParam, "SourceOfData"), "Id")));
}
var isApprovedProp = destProperties.FirstOrDefault(s => s.Name == "IsApproved");
if (isApprovedProp != null)
{
memberBindings.Add(Expression.Bind(isApprovedProp, Expression.Constant(true)));
}
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, Dictionary<string, MasterSection>, TDest>>(memberInitExpression, itemParam, dictParam);
return projection;
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}
Then rewrite your function:
private static Expression<MechanicalData TransformMechanicalData(MechanicalData sourceMechanicalData, Dictionary<string, MasterSection> masterSectionMappedLibrary)
{
return new MechanicalData()
{
Acoustic = sourceMechanicalData.Acoustic
.Where(a => a != null)
.Select(a => PropertyMapper<LibraryAcoustic, LibraryAcoustic>.Mapper(a, masterSectionMappedLibrary)).ToList(),
}
}
How do I order a bool by null first, then true, then false
return View("Index", db.HolidayRequestForms.ToList().OrderByDescending(e => e.Approved).ThenBy(e => e.RequestID))
I am using a using a custom display template for the bool, I don't know if that matters
You could use a custom comparer
public class ApprovedComparer : IComparer<bool?>
{
public int Compare(bool? x, bool? y)
{
var a = 0;
var b = 0;
if (x.HasValue)
a = x.Value ? 1 : 2;
if (y.HasValue)
b = y.Value ? 1 : 2;
return a - b;
}
}
Usage:
return View("Index", db.HolidayRequestForms.ToList()
.OrderBy(e => e.Approved, new ApprovedComparer())
.ThenBy(e => e.RequestID))
Can be tested in LinqPad (or a normal console app)
public class Thing
{
public string Name { get; set; }
public bool? Approved { get; set; }
}
public class ApprovedComparer : IComparer<bool?>
{
public int Compare(bool? x, bool? y)
{
var a = 0;
var b = 0;
if (x.HasValue)
a = x.Value ? 1 : 2;
if (y.HasValue)
b = y.Value ? 1 : 2;
return a - b;
}
}
void Main()
{
var thing1 = new Thing { Approved = null, Name = "Thing 1" };
var thing2 = new Thing { Approved = true, Name = "Thing 2", };
var thing3 = new Thing { Approved = false, Name = "Thing 3" };
//note the 'incorrect' order
var listOfThings = new[] { thing3, thing2, thing1 };
listOfThings
.OrderBy(x => x.Approved, new ApprovedComparer())
.Select(x => x.Name) //just for outputting the names
.Dump(); //LinqPad specifc
}
Output
As of .net 4.5, you can use Comparer<T>.Create() to create a static comparer, which can be 'inline' - ie, no separate class required.
Personally, I find the separate class a bit cleaner to read. Just my opinion, however.
var comparer = Comparer<bool?>.Create((x, y) =>
{
var a = 0;
var b = 0;
if (x.HasValue)
a = x.Value ? 1 : 2;
if (y.HasValue)
b = y.Value ? 1 : 2;
return a - b;
});
listOfThings
.OrderBy(x => x.Approved, comparer)
.Select(x => x.Name) //just for outputting the names
.Dump(); //LinqPad specifc
You can use this:
myList.OrderBy(v => !v)
I'm trying to perform a check on the database to see if the combination of two properties exists in the database check (pre-Unique constraint check for better UX). Doing the check with a single property is easy, no matter how many you're trying to check. I'm unable to find how to do it with multiple properties in an enumerable.
public class Foo
{
public int Id { get; set; }
public int Bar { get; set; }
public int Bat { get; set; }
public string Name { get; set; }
//...
}
public class FooDupeCheckModel
{
public int Bar { get; set; }
public int Bat { get; set; }
}
public IEnumerable<FooDupeCheckModel> MatchExists(IEnumerable<FooDupeCheckModel> matches)
{
return _db.Foos
.Where(f => matches.Any(m => m.BarId == f.BarId &&
m.BatId == f.BatId))
.Select(f => new FooDupeCheckModel
{
BarId = f.BarId,
BatId = f.BatId
});
}
This unfortunately gives an exception because the complex property matches cannot be converted into a SQL script. Only primitive types can be included into a query.
I also tried converting matches to a multidimensional array before using it within the query, but indexes are not supported within a query. .First() is not allowed to be used their either.
public IEnumerable<FooDupeCheckModel> MatchExists(IEnumerable<FooDupeCheckModel> matches)
{
var matchArray = matches.Select(m => new [] {m.BarId, m.BatId})
.ToArray();
return _db.Foos
.Where(f => matchArray.Any(m => m[0] == f.BarId &&
m[1] == f.BatId))
.Select(f => new FooDupeCheckModel
{
BarId = f.BarId,
BatId = f.BatId
});
}
This may be one of those situations that is such a niche case, or requires a SQL query that is too complex for Entity Framework that it is not possible. If it is possible, then this would be very helpful if someone else runs into the same issue.
I did get around this by looping through and calling the database for each element in matches, but if I could do this in one database call, that'd be quicker.
SQL Server doesn't support comparing tuples. However, you can compare two+ properties by OR-ing together comparisons:
SELECT *
FROM Foo f
WHERE
(
(f.Bar = 1 AND f.Bat = 1)
OR
(f.Bar = 3 AND f.Bat = 2)
)
Unfortunately, there's no easy way to build up an IQueryable<T> involving an OR. You can, however, build it up using Expression tree builders:
var models = new FooDupeCheckModel[]
{
new FooDupeCheckModel() { Bar = 1, Bat = 2 },
new FooDupeCheckModel() { Bar = 1, Bat = 3 }
};
var comparison = getComparison(models);
IQueryable<Foo> foos = new Foo[]
{
new Foo() { Bar = 1, Bat = 1 },
new Foo() { Bar = 1, Bat = 2 },
new Foo() { Bar = 1, Bat = 3 },
new Foo() { Bar = 1, Bat = 4 }
}.AsQueryable();
var results = foos.Where(comparison).ToArray();
...
private static Expression<Func<Foo, bool>> getComparison(IEnumerable<FooDupeCheckModel> models)
{
ParameterExpression pe = Expression.Parameter(typeof(Foo), "f");
var ands = models.Select(m =>
{
// Compare Bars
Expression pBarId = Expression.Property(pe, "Bar");
Expression vBarId = Expression.Constant(m.Bar);
Expression bar = Expression.Equal(pBarId, vBarId);
// Compare Bats
Expression pBatId = Expression.Property(pe, "Bat");
Expression vBatId = Expression.Constant(m.Bat);
Expression bat = Expression.Equal(pBatId, vBatId);
Expression and = Expression.And(bar, bat);
return and;
}).ToArray();
if (ands.Length == 0)
{
return Expression.Lambda<Func<Foo, bool>>(Expression.Constant(true), pe);
}
else
{
Expression ors = ands.First();
foreach (Expression and in ands.Skip(1))
{
ors = Expression.OrElse(ors, and);
}
return Expression.Lambda<Func<Foo, bool>>(ors, pe);
}
}
This works against in-memory data structures. Test it again SQL Server; it should generate the corresponding SQL.
Here is a version that supports an arbitrary number of properties with any names:
public class Foo
{
public int Id { get; set; }
public int Bar { get; set; }
public int Bat { get; set; }
public string Name { get; set; }
}
public class FooDupeCheckModel
{
public int Bar { get; set; }
public int Bat { get; set; }
}
static void Main(string[] args)
{
var models = new FooDupeCheckModel[]
{
new FooDupeCheckModel() { Bar = 1, Bat = 2 },
new FooDupeCheckModel() { Bar = 1, Bat = 3 }
};
var comparison = getComparison<Foo, FooDupeCheckModel>(
models,
compare((Foo f) => f.Bar, (FooDupeCheckModel f) => f.Bar),
compare((Foo f) => f.Bat, (FooDupeCheckModel f) => f.Bat)
);
IQueryable<Foo> foos = new Foo[]
{
new Foo() { Bar = 1, Bat = 1 },
new Foo() { Bar = 1, Bat = 2 },
new Foo() { Bar = 1, Bat = 3 },
new Foo() { Bar = 1, Bat = 4 }
}.AsQueryable();
var query = foos.Where(comparison);
var results = query.ToArray();
}
private class PropertyComparison
{
public PropertyInfo FromProperty { get; set; }
public PropertyInfo ToProperty { get; set; }
}
private static PropertyComparison compare<TFrom, TFromValue, TTo, TToValue>(
Expression<Func<TFrom, TFromValue>> fromAccessor,
Expression<Func<TTo, TToValue>> toAccessor)
{
MemberExpression fromMemberAccessor = (MemberExpression)fromAccessor.Body;
PropertyInfo fromProperty = (PropertyInfo)fromMemberAccessor.Member;
MemberExpression toMemberAccessor = (MemberExpression)toAccessor.Body;
PropertyInfo toProperty = (PropertyInfo)toMemberAccessor.Member;
return new PropertyComparison() { FromProperty = fromProperty, ToProperty = toProperty };
}
private static Expression<Func<TFrom, bool>> getComparison<TFrom, TTo>(
IEnumerable<TTo> models,
params PropertyComparison[] comparisons)
{
ParameterExpression pe = Expression.Parameter(typeof(TFrom), "f");
if (!models.Any() || !comparisons.Any())
{
return Expression.Lambda<Func<TFrom, bool>>(Expression.Constant(true), pe);
}
var ands = models.Select(m =>
{
var equals = comparisons.Select(p =>
{
PropertyInfo fromProperty = p.FromProperty;
PropertyInfo toProperty = p.ToProperty;
object value = toProperty.GetValue(m);
Expression fromValue = Expression.Property(pe, fromProperty);
Expression toValue = Expression.Constant(value);
Expression equal = Expression.Equal(fromValue, toValue);
return equal;
}).ToArray();
var and = equals.First();
foreach (var equal in equals.Skip(1))
{
and = Expression.AndAlso(and, equal);
}
return and;
}).ToArray();
Expression ors = ands.First();
foreach (Expression and in ands.Skip(1))
{
ors = Expression.OrElse(ors, and);
}
return Expression.Lambda<Func<TFrom, bool>>(ors, pe);
}
Since Foo is a type that belongs to the model, you could project your matches to IEnumerable<Foo>, mapping only the two properties of interest, then issue the query. That should make it work.
public IEnumerable<FooDupeCheckModel> MatchExists(IEnumerable<FooDupeCheckModel> matches)
{
//Convert small set to check for dups to objects recognized by the EF
var fooMatches = matches.Select(m => new Foo() { BarId = m.BardId, BatId = m.BatId });
//This should now work
return _db.Foos
.Where(f => fooMatches.Any(m => m.BarId == f.BarId &&
m.BatId == f.BatId))
.Select(f => new FooDupeCheckModel
{
BarId = f.BarId,
BatId = f.BatId
});
}
I have read about the IEqualityComparer interface. Here is my code (which says more then a thousand words)
static void Main(string[] args)
{
var Send = new ObservableCollection<ProdRow>() {
new ProdRow() { Code = "8718607000065", Quantity = 1 },
new ProdRow() { Code = "8718607000911", Quantity = 10 }
};
var WouldSend = new ObservableCollection<ProdRow>() {
new ProdRow() { Code = "8718607000065", Quantity = 1 },
new ProdRow() { Code = "8718607000072", Quantity = 1 },
new ProdRow() { Code = "8718607000256", Quantity = 1 },
new ProdRow() { Code = "8718607000485", Quantity = 1 },
new ProdRow() { Code = "8718607000737", Quantity = 1 },
new ProdRow() { Code = "8718607000911", Quantity = 20 }
};
//var sendToMuch = Send.Except(WouldSend).ToList();
//var sendToLittle = WouldSend.Except(Send).ToList();
//if (sendToMuch.Any() || sendToLittle.Any())
// var notGood = true;
//else
// var okay = true;
var sendToMuch = Send.ToList();
var sendToLittle = WouldSend.ToList();
foreach (var s in Send) {
var w = WouldSend.FirstOrDefault(d => d.Code.Equals(s.Code));
if (w != null) {
if (w.Quantity == s.Quantity) {
sendToMuch.Remove(s);
sendToLittle.Remove(w);
continue;
}
if (w.Quantity > s.Quantity) {
sendToLittle.Single(l => l.Code == w.Code).Quantity = (w.Quantity - s.Quantity);
sendToMuch.Remove(s);
} else {
sendToMuch.Single(l => l.Code == w.Code).Quantity = (s.Quantity - w.Quantity);
sendToLittle.Remove(s);
}
} else {
sendToMuch.Add(s);
}
}
}
The commented lines where what I would hoped that would work... the stuff below with what I ended up with.
As reference, here is my ProdRow class:
class ProdRow : INotifyPropertyChanged, IEqualityComparer<ProdRow>
{
private string _code;
private int _quantity;
public string Code {
get { return _code; }
set {
_code = value;
OnPropertyChanged("Code");
}
}
public int Quantity {
get { return _quantity; }
set {
_quantity = value;
OnPropertyChanged("Quantity");
}
}
private void OnPropertyChanged(string v) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
public new bool Equals(object x, object y) {
if (((ProdRow)x).Code.Equals(((ProdRow)y).Code) && ((ProdRow)x).Quantity == ((ProdRow)y).Quantity)
return true;
else
return false;
}
public int GetHashCode(object obj) {
return obj.GetHashCode();
}
public bool Equals(ProdRow x, ProdRow y) {
if (x.Code.Equals(y.Code) && x.Quantity == y.Quantity)
return true;
else
return false;
}
public int GetHashCode(ProdRow obj) {
throw new NotImplementedException();
}
public event PropertyChangedEventHandler PropertyChanged;
}
I did not expected the commented part to work, because it cannot know to decrease the int of quantity etc. but I would like to know if there is a more efficient way to do this then the solution I used (below the commented lines). Perhaps flatten the collection like a string[]?
P.S. Sorry for the "PascalCase" of Send and WouldSend
IEqualityComparer<T> is not the right interface to implement for a class whose instances you wish to compare. IEqualityComparer<T> implementations are for creating objects that do comparisons from the outside of the objects being compared, which becomes important when you need to re-define what it means for two objects to be equal without access to the code of these objects, or when you need to use different semantic for equality depending on the context.
The right interface for strongly typed equality comparison is IEquatable<T>. However, in your case all you need is overriding Object's Equals(object) and GetHashCode():
public new bool Equals(object obj) {
if (obj == this) return true;
var other = obj as ProdRow;
if (other == null) return false;
return Code.Equals(other.Code) && Quantity == other.Quantity;
}
public int GetHashCode() {
return 31*Code.GetHashCode() + Quantity;
}
As far as computing quantities goes, you can do it with negative numbers and GroupBy:
var quantityByCode = WouldSend.Select(p => new {p.Code, p.Quantity})
.Concat(Send.Select(p => new {p.Code, Quantity = -p.Quantity}))
.GroupBy(p => p.Code)
.ToDictionary(g => g.Key, g => g.Sum(p => p.Quantity));
var tooLittle = quantityByCode
.Where(p => p.Value > 0)
.Select(p => new ProdRow {Code = p.Key, Quantity = p.Value})
.ToList();
var tooMuch = quantityByCode
.Where(p => p.Value < 0)
.Select(p => new ProdRow {Code = p.Key, Quantity = -p.Value})
.ToList();