Skip to content

Commit aadc6ce

Browse files
Merge pull request #2322 from maca88/GH2221
Add support for OData group by queries
2 parents 66fa1d4 + 66b9143 commit aadc6ce

File tree

10 files changed

+583
-9
lines changed

10 files changed

+583
-9
lines changed

src/NHibernate.Test/Async/Linq/ByMethod/GroupByTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,48 @@ public async Task GroupByOrderByKeySelectToClassAsync()
910910
.ToListAsync());
911911
}
912912

913+
[Test]
914+
public async Task SelectArrayIndexBeforeGroupByAsync()
915+
{
916+
var result = db.Orders
917+
.SelectMany(o => o.OrderLines.Select(c => c.Id).DefaultIfEmpty().Select(c => new object[] {c, o}))
918+
.GroupBy(g => g[0], g => (Order) g[1])
919+
.Select(g => new[] {g.Key, g.Count(), g.Max(x => x.OrderDate)});
920+
921+
Assert.True(await (result.AnyAsync()));
922+
}
923+
924+
[Test]
925+
public async Task SelectMemberInitBeforeGroupByAsync()
926+
{
927+
var result = await (db.Orders
928+
.Select(o => new OrderGroup {OrderId = o.OrderId, OrderDate = o.OrderDate})
929+
.GroupBy(o => o.OrderId)
930+
.Select(g => new OrderGroup {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)})
931+
.ToListAsync());
932+
933+
Assert.True(result.Any());
934+
}
935+
936+
[Test]
937+
public async Task SelectNewBeforeGroupByAsync()
938+
{
939+
var result = await (db.Orders
940+
.Select(o => new {o.OrderId, o.OrderDate})
941+
.GroupBy(o => o.OrderId)
942+
.Select(g => new {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)})
943+
.ToListAsync());
944+
945+
Assert.True(result.Any());
946+
}
947+
948+
private class OrderGroup
949+
{
950+
public int OrderId { get; set; }
951+
952+
public DateTime? OrderDate { get; set; }
953+
}
954+
913955
private class GroupInfo
914956
{
915957
public object Key { get; set; }
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using Microsoft.AspNet.OData;
15+
using Microsoft.AspNet.OData.Builder;
16+
using Microsoft.AspNet.OData.Extensions;
17+
using Microsoft.AspNet.OData.Query;
18+
using Microsoft.AspNet.OData.Query.Expressions;
19+
using Microsoft.AspNetCore.Http;
20+
using Microsoft.OData.Edm;
21+
using NHibernate.DomainModel.Northwind.Entities;
22+
using NUnit.Framework;
23+
using NHibernate.Linq;
24+
25+
namespace NHibernate.Test.Linq
26+
{
27+
using System.Threading.Tasks;
28+
[TestFixture]
29+
public class ODataTestsAsync : LinqTestCase
30+
{
31+
private IEdmModel _edmModel;
32+
33+
protected override void OnSetUp()
34+
{
35+
base.OnSetUp();
36+
37+
_edmModel = CreatEdmModel();
38+
}
39+
40+
[TestCase("$apply=groupby((Customer/CustomerId))", 89)]
41+
[TestCase("$apply=groupby((Customer/CustomerId))&$orderby=Customer/CustomerId", 89)]
42+
[TestCase("$apply=groupby((Customer/CustomerId, ShippingAddress/PostalCode), aggregate(OrderId with average as Average, Employee/EmployeeId with max as Max))", 89)]
43+
[TestCase("$apply=groupby((Customer/CustomerId), aggregate(OrderId with sum as Total))&$skip=2", 87)]
44+
public async Task OrderGroupByAsync(string queryString, int expectedRows)
45+
{
46+
var query = ApplyFilter(session.Query<Order>(), queryString);
47+
Assert.That(query, Is.AssignableTo<IQueryable<DynamicTypeWrapper>>());
48+
49+
var results = await (((IQueryable<DynamicTypeWrapper>) query).ToListAsync());
50+
Assert.That(results, Has.Count.EqualTo(expectedRows));
51+
}
52+
53+
private IQueryable ApplyFilter<T>(IQueryable<T> query, string queryString)
54+
{
55+
var context = new ODataQueryContext(CreatEdmModel(), typeof(T), null) { };
56+
var dataQuerySettings = new ODataQuerySettings {HandleNullPropagation = HandleNullPropagationOption.False};
57+
var serviceProvider = new ODataServiceProvider(
58+
new Dictionary<System.Type, object>()
59+
{
60+
{typeof(DefaultQuerySettings), new DefaultQuerySettings()},
61+
{typeof(ODataOptions), new ODataOptions()},
62+
{typeof(IEdmModel), _edmModel},
63+
{typeof(ODataQuerySettings), dataQuerySettings},
64+
});
65+
66+
HttpContext httpContext = new DefaultHttpContext();
67+
httpContext.ODataFeature().RequestContainer = serviceProvider;
68+
httpContext.RequestServices = serviceProvider;
69+
var request = httpContext.Request;
70+
Uri requestUri = new Uri($"http://localhost/?{queryString}");
71+
request.Method = HttpMethods.Get;
72+
request.Scheme = requestUri.Scheme;
73+
request.Host = new HostString(requestUri.Host);
74+
request.QueryString = new QueryString(requestUri.Query);
75+
request.Path = new PathString(requestUri.AbsolutePath);
76+
var options = new ODataQueryOptions(context, request);
77+
78+
return options.ApplyTo(query, dataQuerySettings);
79+
}
80+
81+
private static IEdmModel CreatEdmModel()
82+
{
83+
var builder = new ODataConventionModelBuilder();
84+
85+
var adressModel = builder.ComplexType<Address>();
86+
adressModel.Property(o => o.City);
87+
adressModel.Property(o => o.Country);
88+
adressModel.Property(o => o.Fax);
89+
adressModel.Property(o => o.PhoneNumber);
90+
adressModel.Property(o => o.PostalCode);
91+
adressModel.Property(o => o.Region);
92+
adressModel.Property(o => o.Street);
93+
94+
var customerModel = builder.EntitySet<Customer>(nameof(Customer));
95+
customerModel.EntityType.HasKey(o => o.CustomerId);
96+
customerModel.EntityType.Property(o => o.CompanyName);
97+
customerModel.EntityType.Property(o => o.ContactTitle);
98+
customerModel.EntityType.ComplexProperty(o => o.Address);
99+
customerModel.EntityType.HasMany(o => o.Orders);
100+
101+
var orderModel = builder.EntitySet<Order>(nameof(Order));
102+
orderModel.EntityType.HasKey(o => o.OrderId);
103+
orderModel.EntityType.Property(o => o.Freight);
104+
orderModel.EntityType.Property(o => o.OrderDate);
105+
orderModel.EntityType.Property(o => o.RequiredDate);
106+
orderModel.EntityType.Property(o => o.ShippedTo);
107+
orderModel.EntityType.Property(o => o.ShippingDate);
108+
orderModel.EntityType.ComplexProperty(o => o.ShippingAddress);
109+
orderModel.EntityType.HasRequired(o => o.Customer);
110+
orderModel.EntityType.HasOptional(o => o.Employee);
111+
112+
var employeeModel = builder.EntitySet<Employee>(nameof(Employee));
113+
employeeModel.EntityType.HasKey(o => o.EmployeeId);
114+
employeeModel.EntityType.Property(o => o.BirthDate);
115+
employeeModel.EntityType.Property(o => o.Extension);
116+
employeeModel.EntityType.Property(o => o.FirstName);
117+
employeeModel.EntityType.Property(o => o.HireDate);
118+
employeeModel.EntityType.Property(o => o.LastName);
119+
employeeModel.EntityType.Property(o => o.Notes);
120+
employeeModel.EntityType.Property(o => o.Title);
121+
employeeModel.EntityType.HasMany(o => o.Orders);
122+
123+
return builder.GetEdmModel();
124+
}
125+
126+
private class ODataServiceProvider : IServiceProvider
127+
{
128+
private readonly Dictionary<System.Type, object> _singletonObjects = new Dictionary<System.Type, object>();
129+
130+
public ODataServiceProvider(Dictionary<System.Type, object> singletonObjects)
131+
{
132+
_singletonObjects = singletonObjects;
133+
}
134+
135+
public object GetService(System.Type serviceType)
136+
{
137+
if (_singletonObjects.TryGetValue(serviceType, out var service))
138+
{
139+
return service;
140+
}
141+
142+
var ctor = serviceType.GetConstructor(new System.Type[0]);
143+
if (ctor != null)
144+
{
145+
return ctor.Invoke(new object[0]);
146+
}
147+
148+
ctor = serviceType.GetConstructor(new[] { typeof(DefaultQuerySettings) });
149+
if (ctor != null)
150+
{
151+
return ctor.Invoke(new object[] { GetService(typeof(DefaultQuerySettings)) });
152+
}
153+
154+
ctor = serviceType.GetConstructor(new[] { typeof(IServiceProvider) });
155+
if (ctor != null)
156+
{
157+
return ctor.Invoke(new object[] { this });
158+
}
159+
160+
return null;
161+
}
162+
}
163+
}
164+
}

src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,48 @@ public void FetchBeforeGroupBy()
912912
Assert.True(result.Any());
913913
}
914914

915+
[Test]
916+
public void SelectArrayIndexBeforeGroupBy()
917+
{
918+
var result = db.Orders
919+
.SelectMany(o => o.OrderLines.Select(c => c.Id).DefaultIfEmpty().Select(c => new object[] {c, o}))
920+
.GroupBy(g => g[0], g => (Order) g[1])
921+
.Select(g => new[] {g.Key, g.Count(), g.Max(x => x.OrderDate)});
922+
923+
Assert.True(result.Any());
924+
}
925+
926+
[Test]
927+
public void SelectMemberInitBeforeGroupBy()
928+
{
929+
var result = db.Orders
930+
.Select(o => new OrderGroup {OrderId = o.OrderId, OrderDate = o.OrderDate})
931+
.GroupBy(o => o.OrderId)
932+
.Select(g => new OrderGroup {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)})
933+
.ToList();
934+
935+
Assert.True(result.Any());
936+
}
937+
938+
[Test]
939+
public void SelectNewBeforeGroupBy()
940+
{
941+
var result = db.Orders
942+
.Select(o => new {o.OrderId, o.OrderDate})
943+
.GroupBy(o => o.OrderId)
944+
.Select(g => new {OrderId = g.Key, OrderDate = g.Max(o => o.OrderDate)})
945+
.ToList();
946+
947+
Assert.True(result.Any());
948+
}
949+
950+
private class OrderGroup
951+
{
952+
public int OrderId { get; set; }
953+
954+
public DateTime? OrderDate { get; set; }
955+
}
956+
915957
private class GroupInfo
916958
{
917959
public object Key { get; set; }

0 commit comments

Comments
 (0)