multi group fix.
This commit is contained in:
parent
d64c9c5d49
commit
ee7afe49d2
@ -109,12 +109,10 @@ namespace PoweredSoft.DynamicQuery.Cli
|
|||||||
|
|
||||||
criteria.Groups = new List<IGroup>()
|
criteria.Groups = new List<IGroup>()
|
||||||
{
|
{
|
||||||
new Group { Path = "LastName" }
|
new Group { Path = "LastName" },
|
||||||
//, new Group { Path = "Sexe" }
|
new Group { Path = "Sexe" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
criteria.Aggregates = new List<IAggregate>()
|
criteria.Aggregates = new List<IAggregate>()
|
||||||
{
|
{
|
||||||
new Aggregate { Type = AggregateType.Count },
|
new Aggregate { Type = AggregateType.Count },
|
||||||
|
@ -47,5 +47,37 @@ namespace PoweredSoft.DynamicQuery.Test
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GroupComplex()
|
||||||
|
{
|
||||||
|
MockContextFactory.SeedAndTestContextFor("GroupTests_Complex", TestSeeders.SeedTicketScenario, ctx =>
|
||||||
|
{
|
||||||
|
var criteria = new QueryCriteria()
|
||||||
|
{
|
||||||
|
Groups = new List<IGroup>()
|
||||||
|
{
|
||||||
|
new Group { Path = "TicketType" },
|
||||||
|
new Group { Path = "Priority" }
|
||||||
|
},
|
||||||
|
Aggregates = new List<IAggregate>()
|
||||||
|
{
|
||||||
|
new Aggregate { Type = AggregateType.Count }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var queryHandler = new QueryHandler();
|
||||||
|
var result = queryHandler.Execute(ctx.Tickets, criteria);
|
||||||
|
|
||||||
|
var firstGroup = result.Data[0] as IGroupQueryResult;
|
||||||
|
Assert.NotNull(firstGroup);
|
||||||
|
var secondGroup = result.Data[1] as IGroupQueryResult;
|
||||||
|
Assert.NotNull(secondGroup);
|
||||||
|
|
||||||
|
var expected = ctx.Tickets.Select(t => t.TicketType).Distinct().Count();
|
||||||
|
var c = result.Data.Cast<IGroupQueryResult>().Select(t => t.GroupValue).Count();
|
||||||
|
Assert.Equal(expected, c);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ namespace PoweredSoft.DynamicQuery.Test.Mock
|
|||||||
public virtual DbSet<Item> Items { get; set; }
|
public virtual DbSet<Item> Items { get; set; }
|
||||||
public virtual DbSet<Order> Orders { get; set; }
|
public virtual DbSet<Order> Orders { get; set; }
|
||||||
public virtual DbSet<OrderItem> OrderItems { get; set; }
|
public virtual DbSet<OrderItem> OrderItems { get; set; }
|
||||||
|
public virtual DbSet<Ticket> Tickets { get; set; }
|
||||||
|
|
||||||
public MockContext()
|
public MockContext()
|
||||||
{
|
{
|
||||||
|
@ -92,5 +92,48 @@ namespace PoweredSoft.DynamicQuery.Test.Mock
|
|||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void SeedTicketScenario(string testName)
|
||||||
|
{
|
||||||
|
MockContextFactory.TestContextFor(testName, ctx =>
|
||||||
|
{
|
||||||
|
var faker = new Bogus.Faker<Ticket>()
|
||||||
|
.RuleFor(t => t.TicketType, (f, u) => f.PickRandom("new", "open", "refused", "closed"))
|
||||||
|
.RuleFor(t => t.Title, (f, u) => f.Lorem.Sentence())
|
||||||
|
.RuleFor(t => t.Details, (f, u) => f.Lorem.Paragraph())
|
||||||
|
.RuleFor(t => t.IsHtml, (f, u) => false)
|
||||||
|
.RuleFor(t => t.TagList, (f, u) => string.Join(",", f.Commerce.Categories(3)))
|
||||||
|
.RuleFor(t => t.CreatedDate, (f, u) => f.Date.Recent(100))
|
||||||
|
.RuleFor(t => t.Owner, (f, u) => f.Person.FullName)
|
||||||
|
.RuleFor(t => t.AssignedTo, (f, u) => f.Person.FullName)
|
||||||
|
.RuleFor(t => t.TicketStatus, (f, u) => f.PickRandom(1, 2, 3))
|
||||||
|
.RuleFor(t => t.LastUpdateBy, (f, u) => f.Person.FullName)
|
||||||
|
.RuleFor(t => t.LastUpdateDate, (f, u) => f.Date.Soon(5))
|
||||||
|
.RuleFor(t => t.Priority, (f, u) => f.PickRandom("low", "medium", "high", "critical"))
|
||||||
|
.RuleFor(t => t.AffectedCustomer, (f, u) => f.PickRandom(true, false))
|
||||||
|
.RuleFor(t => t.Version, (f, u) => f.PickRandom("1.0.0", "1.1.0", "2.0.0"))
|
||||||
|
.RuleFor(t => t.ProjectId, (f, u) => f.Random.Number(100))
|
||||||
|
.RuleFor(t => t.DueDate, (f, u) => f.Date.Soon(5))
|
||||||
|
.RuleFor(t => t.EstimatedDuration, (f, u) => f.Random.Number(20))
|
||||||
|
.RuleFor(t => t.ActualDuration, (f, u) => f.Random.Number(20))
|
||||||
|
.RuleFor(t => t.TargetDate, (f, u) => f.Date.Soon(5))
|
||||||
|
.RuleFor(t => t.ResolutionDate, (f, u) => f.Date.Soon(5))
|
||||||
|
.RuleFor(t => t.Type, (f, u) => f.PickRandom(1, 2, 3))
|
||||||
|
.RuleFor(t => t.ParentId, () => 0)
|
||||||
|
.RuleFor(t => t.PreferredLanguage, (f, u) => f.PickRandom("fr", "en", "es"))
|
||||||
|
;
|
||||||
|
|
||||||
|
var fakeModels = new List<Ticket>();
|
||||||
|
for (var i = 0; i < 500; i++)
|
||||||
|
{
|
||||||
|
var t = faker.Generate();
|
||||||
|
t.TicketId = i + 1;
|
||||||
|
fakeModels.Add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.AddRange(fakeModels);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
PoweredSoft.DynamicQuery.Test/Mock/Ticket.cs
Normal file
36
PoweredSoft.DynamicQuery.Test/Mock/Ticket.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace PoweredSoft.DynamicQuery.Test.Mock
|
||||||
|
{
|
||||||
|
public class Ticket
|
||||||
|
{
|
||||||
|
public int TicketId { get; set; }
|
||||||
|
public string TicketType { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Details { get; set; }
|
||||||
|
public bool IsHtml { get; set; }
|
||||||
|
public string TagList { get; set; }
|
||||||
|
public DateTimeOffset CreatedDate { get; set; }
|
||||||
|
public string Owner { get; set; }
|
||||||
|
public string AssignedTo { get; set; }
|
||||||
|
public int TicketStatus { get; set; }
|
||||||
|
public DateTimeOffset CurrentStatusDate { get; set; }
|
||||||
|
public string CurrentStatusSetBy { get; set; }
|
||||||
|
public string LastUpdateBy { get; set; }
|
||||||
|
public DateTimeOffset LastUpdateDate { get; set; }
|
||||||
|
public string Priority { get; set; }
|
||||||
|
public bool AffectedCustomer { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
public int ProjectId { get; set; }
|
||||||
|
public DateTimeOffset DueDate { get; set; }
|
||||||
|
public decimal EstimatedDuration { get; set; }
|
||||||
|
public decimal ActualDuration { get; set; }
|
||||||
|
public DateTimeOffset TargetDate { get; set; }
|
||||||
|
public DateTimeOffset ResolutionDate { get; set; }
|
||||||
|
public int Type { get; set; }
|
||||||
|
public int ParentId { get; set; }
|
||||||
|
public string PreferredLanguage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bogus" Version="24.3.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.4" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.1.4" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.1.4" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -53,8 +54,14 @@ namespace PoweredSoft.DynamicQuery
|
|||||||
sb.ToList("Records");
|
sb.ToList("Records");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// loop through the grouped records.
|
// loop through the grouped records.
|
||||||
var groupRecords = CurrentQueryable.ToDynamicClassList();
|
var groupRecords = CurrentQueryable.ToDynamicClassList();
|
||||||
|
|
||||||
|
// now join them into logical collections
|
||||||
|
result.Data = RecursiveRegroup<T>(groupRecords, aggregateResults, Criteria.Groups.First());
|
||||||
|
|
||||||
|
/*
|
||||||
result.Data = groupRecords.Select((groupRecord, groupRecordIndex) =>
|
result.Data = groupRecords.Select((groupRecord, groupRecordIndex) =>
|
||||||
{
|
{
|
||||||
var groupRecordResult = new GroupQueryResult();
|
var groupRecordResult = new GroupQueryResult();
|
||||||
@ -105,12 +112,73 @@ namespace PoweredSoft.DynamicQuery
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (object)groupRecordResult;
|
return (object)groupRecordResult;
|
||||||
}).ToList();
|
}).ToList();*/
|
||||||
|
|
||||||
result.Aggregates = CalculateTotalAggregate<T>(queryableAfterFilters);
|
result.Aggregates = CalculateTotalAggregate<T>(queryableAfterFilters);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual List<object> RecursiveRegroup<T>(List<DynamicClass> groupRecords, List<List<DynamicClass>> aggregateResults, IGroup group, List<IGroupQueryResult> parentGroupResults = null)
|
||||||
|
{
|
||||||
|
var groupIndex = Criteria.Groups.IndexOf(group);
|
||||||
|
var isLast = Criteria.Groups.Last() == group;
|
||||||
|
var groups = Criteria.Groups.Take(groupIndex + 1).ToList();
|
||||||
|
var hasAggregates = Criteria.Aggregates.Any();
|
||||||
|
|
||||||
|
var ret = groupRecords
|
||||||
|
.GroupBy(gk => gk.GetDynamicPropertyValue($"Key_{groupIndex}"))
|
||||||
|
.Select(t =>
|
||||||
|
{
|
||||||
|
var groupResult = new GroupQueryResult();
|
||||||
|
|
||||||
|
// group results.
|
||||||
|
|
||||||
|
List<IGroupQueryResult> groupResults;
|
||||||
|
if (parentGroupResults == null)
|
||||||
|
groupResults = new List<IGroupQueryResult> { groupResult };
|
||||||
|
else
|
||||||
|
groupResults = parentGroupResults.Union(new[] { groupResult }).ToList();
|
||||||
|
|
||||||
|
groupResult.GroupPath = group.Path;
|
||||||
|
groupResult.GroupValue = t.Key;
|
||||||
|
|
||||||
|
if (hasAggregates)
|
||||||
|
{
|
||||||
|
var matchingAggregate = FindMatchingAggregateResult(aggregateResults, groups, groupResults);
|
||||||
|
if (matchingAggregate == null)
|
||||||
|
Debugger.Break();
|
||||||
|
|
||||||
|
groupResult.Aggregates = new List<IAggregateResult>();
|
||||||
|
Criteria.Aggregates.ForEach((a, ai) =>
|
||||||
|
{
|
||||||
|
var key = $"Agg_{ai}";
|
||||||
|
var aggregateResult = new AggregateResult
|
||||||
|
{
|
||||||
|
Path = a.Path,
|
||||||
|
Type = a.Type,
|
||||||
|
Value = matchingAggregate.GetDynamicPropertyValue(key)
|
||||||
|
};
|
||||||
|
groupResult.Aggregates.Add(aggregateResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLast)
|
||||||
|
{
|
||||||
|
var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue<List<T>>("Records")).ToList();
|
||||||
|
groupResult.Data = InterceptConvertTo<T>(entities);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
groupResult.Data = RecursiveRegroup<T>(t.ToList(), aggregateResults, Criteria.Groups[groupIndex+1], groupResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupResult;
|
||||||
|
})
|
||||||
|
.AsEnumerable<object>()
|
||||||
|
.ToList();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual List<IAggregateResult> CalculateTotalAggregate<T>(IQueryable queryableAfterFilters)
|
protected virtual List<IAggregateResult> CalculateTotalAggregate<T>(IQueryable queryableAfterFilters)
|
||||||
{
|
{
|
||||||
if (!Criteria.Aggregates.Any())
|
if (!Criteria.Aggregates.Any())
|
||||||
@ -127,7 +195,7 @@ namespace PoweredSoft.DynamicQuery
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var aggregateResult = selectExpression.ToDynamicClassList().First();
|
var aggregateResult = selectExpression.ToDynamicClassList().FirstOrDefault();
|
||||||
var ret = new List<IAggregateResult>();
|
var ret = new List<IAggregateResult>();
|
||||||
Criteria.Aggregates.ForEach((a, index) =>
|
Criteria.Aggregates.ForEach((a, index) =>
|
||||||
{
|
{
|
||||||
@ -135,13 +203,13 @@ namespace PoweredSoft.DynamicQuery
|
|||||||
{
|
{
|
||||||
Path = a.Path,
|
Path = a.Path,
|
||||||
Type = a.Type,
|
Type = a.Type,
|
||||||
Value = aggregateResult.GetDynamicPropertyValue($"Agg_{index}")
|
Value = aggregateResult?.GetDynamicPropertyValue($"Agg_{index}")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DynamicClass FindMatchingAggregateResult(List<List<DynamicClass>> aggregateResults, List<IGroup> groups, List<GroupQueryResult> groupResults)
|
private DynamicClass FindMatchingAggregateResult(List<List<DynamicClass>> aggregateResults, List<IGroup> groups, List<IGroupQueryResult> groupResults)
|
||||||
{
|
{
|
||||||
var groupIndex = groupResults.Count - 1;
|
var groupIndex = groupResults.Count - 1;
|
||||||
var aggregateLevel = aggregateResults[groupIndex];
|
var aggregateLevel = aggregateResults[groupIndex];
|
||||||
|
Loading…
Reference in New Issue
Block a user