Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions net/DevExtreme.AspNet.Data.Tests/DataSourceLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,26 @@ public void Issue246(bool remoteSelect) {
var department = (IDictionary<string, object>)item["Department"];
Assert.Equal("abc", department["Title"]);
}

[Fact]
public void CustomAggregatorWithRemoteGrouping() {
// https://github.com/DevExpress/DevExtreme.AspNet.Data/issues/341

StaticBarrier.Run(delegate {
Aggregation.CustomAggregators.RegisterAggregator("my1", typeof(Object));

var exception = Record.Exception(delegate {
DataSourceLoader.Load(new object[0], new SampleLoadOptions {
RemoteGrouping = true,
TotalSummary = new[] {
new SummaryInfo { Selector = "this", SummaryType = "my1" }
}
});
});

Assert.Contains("custom aggregate 'my1' cannot be translated", exception.Message);
});
}
}

}
21 changes: 21 additions & 0 deletions net/DevExtreme.AspNet.Data.Tests/RemoteGroupingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,27 @@ public void Issue324() {
Assert.Equal(2d, loadResult.summary[0]);
}

[Fact]
public void AggregateTranslationError() {
// https://github.com/DevExpress/DevExtreme.AspNet.Data/issues/331

var data = new[] {
new { P1 = "abc" }
};

var exception = Record.Exception(delegate {
DataSourceLoader.Load(data, new SampleLoadOptions {
RemoteGrouping = true,
TotalSummary = new[] {
new SummaryInfo { Selector = "P1", SummaryType = "avg" }
}
});
});

Assert.Equal("Failed to translate the 'sum' aggregate for the 'P1' member (System.String). See InnerException for details.", exception.Message);
Assert.Contains("No coercion operator", exception.InnerException.Message);
}

}

}
4 changes: 4 additions & 0 deletions net/DevExtreme.AspNet.Data/Aggregation/CustomAggregators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ internal static Aggregator<T> CreateAggregator<T>(string summaryType, IAccessor<
return null;
}

internal static bool IsRegistered(string summaryType) {
return _aggregatorTypes.ContainsKey(summaryType);
}

#if DEBUG
internal static void Clear() {
_aggregatorTypes.Clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,38 +92,44 @@ Expression MakeAggregatingProjection(Expression target, Type groupingType, int g

IEnumerable<Expression> MakeAggregates(Expression aggregateTarget, IEnumerable<SummaryInfo> summary) {
foreach(var s in TransformSummary(summary)) {
var itemParam = CreateItemParam(typeof(T));
var selectorExpr = CompileAccessorExpression(itemParam, s.Selector, liftToNullable: true);
var selectorType = selectorExpr.Type;

var callType = typeof(Enumerable);
var isCountNotNull = s.SummaryType == AggregateName.COUNT_NOT_NULL;

if(isCountNotNull && Utils.CanAssignNull(selectorType)) {
yield return Expression.Call(
callType,
nameof(Enumerable.Sum),
Type.EmptyTypes,
Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Select),
new[] { typeof(T), typeof(int) },
aggregateTarget,
Expression.Lambda(
Expression.Condition(
Expression.NotEqual(selectorExpr, Expression.Constant(null, selectorType)),
Expression.Constant(1),
Expression.Constant(0)
),
itemParam
)
yield return MakeAggregate(aggregateTarget, s);
}
}

Expression MakeAggregate(Expression aggregateTarget, SummaryInfo s) {
var itemParam = CreateItemParam(typeof(T));
var selectorExpr = CompileAccessorExpression(itemParam, s.Selector, liftToNullable: true);
var selectorType = selectorExpr.Type;

var callType = typeof(Enumerable);
var isCountNotNull = s.SummaryType == AggregateName.COUNT_NOT_NULL;

if(isCountNotNull && Utils.CanAssignNull(selectorType)) {
return Expression.Call(
callType,
nameof(Enumerable.Sum),
Type.EmptyTypes,
Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Select),
new[] { typeof(T), typeof(int) },
aggregateTarget,
Expression.Lambda(
Expression.Condition(
Expression.NotEqual(selectorExpr, Expression.Constant(null, selectorType)),
Expression.Constant(1),
Expression.Constant(0)
),
itemParam
)
);
} else {
var callMethod = GetPreAggregateMethodName(s.SummaryType);
var callMethodTypeParams = new List<Type> { typeof(T) };
var callArgs = new List<Expression> { aggregateTarget };
)
);
} else {
var callMethod = GetPreAggregateMethodName(s.SummaryType);
var callMethodTypeParams = new List<Type> { typeof(T) };
var callArgs = new List<Expression> { aggregateTarget };

try {
if(!IsWellKnownAggregateDataType(selectorType)) {
if(s.SummaryType == AggregateName.MIN || s.SummaryType == AggregateName.MAX) {
callMethodTypeParams.Add(selectorType);
Expand All @@ -140,7 +146,10 @@ IEnumerable<Expression> MakeAggregates(Expression aggregateTarget, IEnumerable<S
if(!isCountNotNull)
callArgs.Add(Expression.Lambda(selectorExpr, itemParam));

yield return Expression.Call(callType, callMethod, callMethodTypeParams.ToArray(), callArgs.ToArray());
return Expression.Call(callType, callMethod, callMethodTypeParams.ToArray(), callArgs.ToArray());
} catch(Exception x) {
var message = $"Failed to translate the '{s.SummaryType}' aggregate for the '{s.Selector}' member ({selectorExpr.Type}). See InnerException for details.";
throw new Exception(message, x);
}
}
}
Expand Down Expand Up @@ -184,6 +193,12 @@ static string GetPreAggregateMethodName(string summaryType) {
return nameof(Enumerable.Count);
}

if(CustomAggregators.IsRegistered(summaryType)) {
var message = $"The custom aggregate '{summaryType}' cannot be translated to a LINQ expression."
+ $" Set {nameof(DataSourceLoadOptionsBase)}.{nameof(DataSourceLoadOptionsBase.RemoteGrouping)} to False to enable in-memory aggregate calculation.";
throw new InvalidOperationException(message);
}

throw new NotSupportedException();
}

Expand Down