Skip to content

Commit 16f6955

Browse files
first commit
0 parents  commit 16f6955

13 files changed

+1560
-0
lines changed

.gitignore

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# .NET build output
2+
bin/
3+
obj/
4+
[Dd]ebug/
5+
[Rr]elease/
6+
x64/
7+
x86/
8+
[Ww][Ii][Nn]32/
9+
[Aa][Rr][Mm]/
10+
[Aa][Rr][Mm]64/
11+
bld/
12+
[Bb]in/
13+
[Oo]bj/
14+
[Ll]og/
15+
[Ll]ogs/
16+
17+
# .NET Core
18+
project.lock.json
19+
project.fragment.lock.json
20+
artifacts/
21+
22+
# Visual Studio files
23+
.vs/
24+
*.user
25+
*.userosscache
26+
*.suo
27+
*.userprefs
28+
*.dbmdl
29+
*.dbproj.schemaview
30+
*.pfx
31+
*.publishsettings
32+
orleans.codegen.cs
33+
*.psess
34+
*.vsp
35+
*.vspx
36+
*.sap
37+
38+
# Visual Studio Code
39+
.vscode/*
40+
!.vscode/settings.json
41+
!.vscode/tasks.json
42+
!.vscode/launch.json
43+
!.vscode/extensions.json
44+
*.code-workspace
45+
46+
# Rider
47+
.idea/
48+
*.sln.iml
49+
50+
# Test Results
51+
[Tt]est[Rr]esult*/
52+
[Bb]uild[Ll]og.*
53+
*.VisualState.xml
54+
TestResult.xml
55+
nunit-*.xml
56+
TestResults/
57+
58+
# NuGet
59+
*.nupkg
60+
*.snupkg
61+
**/[Pp]ackages/*
62+
!**/[Pp]ackages/build/
63+
*.nuget.props
64+
*.nuget.targets
65+
66+
# Code coverage
67+
*.coverage
68+
*.coveragexml
69+
coverage*[.json, .xml, .info]
70+
71+
# Windows
72+
Thumbs.db
73+
ehthumbs.db
74+
Desktop.ini
75+
76+
# macOS
77+
.DS_Store
78+
._*
79+
80+
# User-specific files
81+
*.rsuser
82+
83+
# ReSharper
84+
_ReSharper*/
85+
*.[Rr]e[Ss]harper
86+
*.DotSettings.user
87+
88+
# Fody
89+
FodyWeavers.xsd
90+
91+
# VS Code directories
92+
.history/
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>
+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System.Linq.Expressions;
2+
using System.Reflection;
3+
4+
namespace Aggregation.Lib;
5+
6+
/// <summary>
7+
/// Defines the available aggregation methods for numeric properties
8+
/// </summary>
9+
public enum AggregationMethod
10+
{
11+
Sum,
12+
Min,
13+
Max,
14+
Average,
15+
First,
16+
Last,
17+
Count,
18+
Custom
19+
}
20+
21+
/// <summary>
22+
/// Configuration for a property's aggregation method
23+
/// </summary>
24+
public class PropertyAggregationConfig
25+
{
26+
public string PropertyName { get; set; } = string.Empty;
27+
public AggregationMethod Method { get; set; } = AggregationMethod.Sum;
28+
public Delegate? CustomAggregator { get; set; }
29+
}
30+
31+
/// <summary>
32+
/// Fluent configuration builder for the aggregator
33+
/// </summary>
34+
/// <typeparam name="T">The type of object being aggregated</typeparam>
35+
public class AggregationConfiguration<T> where T : class, new()
36+
{
37+
private readonly Dictionary<string, PropertyAggregationConfig> _configurations = new();
38+
private readonly HashSet<string> _excludedProperties = new();
39+
private string? _primaryKeyPropertyName;
40+
41+
/// <summary>
42+
/// Specifies which property should be treated as the primary key
43+
/// </summary>
44+
public AggregationConfiguration<T> PrimaryKey<TProp>(Expression<Func<T, TProp>> propertySelector)
45+
{
46+
var propertyName = ExtractPropertyName(propertySelector);
47+
_primaryKeyPropertyName = propertyName;
48+
return this;
49+
}
50+
51+
/// <summary>
52+
/// Configure a property with a specific aggregation method
53+
/// </summary>
54+
public AggregationConfiguration<T> Property<TProp>(
55+
Expression<Func<T, TProp>> propertySelector,
56+
AggregationMethod method)
57+
{
58+
var propertyName = ExtractPropertyName(propertySelector);
59+
_configurations[propertyName] = new PropertyAggregationConfig
60+
{
61+
PropertyName = propertyName,
62+
Method = method
63+
};
64+
return this;
65+
}
66+
67+
/// <summary>
68+
/// Configure a property with a custom aggregation function
69+
/// </summary>
70+
public AggregationConfiguration<T> Property<TProp>(
71+
Expression<Func<T, TProp>> propertySelector,
72+
Func<IEnumerable<TProp>, TProp> customAggregator)
73+
{
74+
var propertyName = ExtractPropertyName(propertySelector);
75+
_configurations[propertyName] = new PropertyAggregationConfig
76+
{
77+
PropertyName = propertyName,
78+
Method = AggregationMethod.Custom,
79+
CustomAggregator = customAggregator
80+
};
81+
return this;
82+
}
83+
84+
/// <summary>
85+
/// Configure multiple properties to use the same aggregation method
86+
/// </summary>
87+
public AggregationConfiguration<T> Properties(
88+
AggregationMethod method,
89+
params Expression<Func<T, object>>[] propertySelectors)
90+
{
91+
foreach (var selector in propertySelectors)
92+
{
93+
var propertyName = ExtractPropertyName(selector);
94+
_configurations[propertyName] = new PropertyAggregationConfig
95+
{
96+
PropertyName = propertyName,
97+
Method = method
98+
};
99+
}
100+
return this;
101+
}
102+
103+
/// <summary>
104+
/// Configure all numeric properties to use the same aggregation method
105+
/// </summary>
106+
public AggregationConfiguration<T> AllNumericProperties(AggregationMethod method)
107+
{
108+
var numericProperties = typeof(T).GetProperties()
109+
.Where(p => IsNumericType(p.PropertyType));
110+
111+
foreach (var prop in numericProperties)
112+
{
113+
_configurations[prop.Name] = new PropertyAggregationConfig
114+
{
115+
PropertyName = prop.Name,
116+
Method = method
117+
};
118+
}
119+
return this;
120+
}
121+
122+
/// <summary>
123+
/// Exclude specific properties from aggregation
124+
/// </summary>
125+
public AggregationConfiguration<T> Exclude<TProp>(
126+
params Expression<Func<T, TProp>>[] propertySelectors)
127+
{
128+
foreach (var selector in propertySelectors)
129+
{
130+
var propertyName = ExtractPropertyName(selector);
131+
if (_configurations.ContainsKey(propertyName))
132+
{
133+
_configurations.Remove(propertyName);
134+
}
135+
_excludedProperties.Add(propertyName);
136+
}
137+
return this;
138+
}
139+
140+
// Helper methods for internal use
141+
internal AggregationMethod GetMethodForProperty(string propertyName)
142+
{
143+
return _configurations.TryGetValue(propertyName, out var config)
144+
? config.Method
145+
: AggregationMethod.Sum; // Default to Sum
146+
}
147+
148+
internal Delegate? GetCustomAggregatorForProperty(string propertyName)
149+
{
150+
return _configurations.TryGetValue(propertyName, out var config)
151+
? config.CustomAggregator
152+
: null;
153+
}
154+
155+
internal bool HasConfigurationForProperty(string propertyName)
156+
{
157+
return _configurations.ContainsKey(propertyName);
158+
}
159+
160+
internal bool IsPropertyExcluded(string propertyName)
161+
{
162+
return _excludedProperties.Contains(propertyName);
163+
}
164+
165+
/// <summary>
166+
/// Gets the name of the property configured as the primary key, if any
167+
/// </summary>
168+
internal string? GetPrimaryKeyPropertyName()
169+
{
170+
return _primaryKeyPropertyName;
171+
}
172+
173+
private string ExtractPropertyName<TProp>(Expression<Func<T, TProp>> propertySelector)
174+
{
175+
if (propertySelector.Body is MemberExpression memberExpression)
176+
{
177+
return memberExpression.Member.Name;
178+
}
179+
else if (propertySelector.Body is UnaryExpression unaryExpression &&
180+
unaryExpression.NodeType == ExpressionType.Convert &&
181+
unaryExpression.Operand is MemberExpression operandMemberExpression)
182+
{
183+
// Handle boxing conversion (e.g., when a value type is implicitly converted to object)
184+
return operandMemberExpression.Member.Name;
185+
}
186+
187+
throw new ArgumentException("Expression is not a property access expression", nameof(propertySelector));
188+
}
189+
190+
private bool IsNumericType(Type type)
191+
{
192+
return type == typeof(int) ||
193+
type == typeof(long) ||
194+
type == typeof(float) ||
195+
type == typeof(double) ||
196+
type == typeof(decimal);
197+
}
198+
}

Aggregation.Lib/AggregationPeriod.cs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Aggregation.Lib;
2+
3+
/// <summary>
4+
/// Represents different aggregation periods
5+
/// </summary>
6+
public enum AggregationPeriod
7+
{
8+
Daily,
9+
Weekly,
10+
Monthly,
11+
Yearly
12+
}

0 commit comments

Comments
 (0)