-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from davewalker5/FR-24-Bug-Fixes
FR-24 Bug-Fixes and Improvements
- Loading branch information
Showing
29 changed files
with
485 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
src/FlightRecorder.BusinessLogic/FlightRecorder.BusinessLogic.sln
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.5.002.0 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlightRecorder.BusinessLogic", "FlightRecorder.BusinessLogic.csproj", "{A666D73E-C9A8-4F30-863B-DED16DA1884F}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{A666D73E-C9A8-4F30-863B-DED16DA1884F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{A666D73E-C9A8-4F30-863B-DED16DA1884F}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{A666D73E-C9A8-4F30-863B-DED16DA1884F}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{A666D73E-C9A8-4F30-863B-DED16DA1884F}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {28A1C7E4-5D14-478A-A674-1CE34D499690} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using FlightRecorder.Data; | ||
using FlightRecorder.Entities.Interfaces; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Threading.Tasks; | ||
|
||
namespace FlightRecorder.BusinessLogic.Logic | ||
{ | ||
[ExcludeFromCodeCoverage] | ||
internal class DateBasedReport<T> : ReportManagerBase, IDateBasedReport<T> where T : class | ||
{ | ||
private const string DateFormat = "yyyy-MM-dd"; | ||
private const string FromDatePlaceHolder = "$from"; | ||
private const string ToDatePlaceHolder = "$to"; | ||
|
||
internal DateBasedReport(FlightRecorderDbContext context) : base(context) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Generate a datebased report for reporting entity type T | ||
/// </summary> | ||
/// <param name="from"></param> | ||
/// <param name="to"></param> | ||
/// <param name="pageNumber"></param> | ||
/// <param name="pageSize"></param> | ||
/// <returns></returns> | ||
public async Task<IEnumerable<T>> GenerateReport(DateTime? from, DateTime? to, int pageNumber, int pageSize) | ||
{ | ||
// SQL report files are named after the keyless entity type they map to with a .sql extension | ||
var sqlFile = $"{typeof(T).Name}.sql"; | ||
|
||
// Load the SQL file and perform date range place-holder replacements | ||
var query = ReadDateBasedSqlReportResource(sqlFile, from, to); | ||
|
||
// Run the query and return the results | ||
var results = await GenerateReport<T>(query, pageNumber, pageSize); | ||
return results; | ||
} | ||
|
||
/// <summary> | ||
/// Read the SQL report file for a sightings-based report with a date range in it | ||
/// </summary> | ||
/// <param name="reportFile"></param> | ||
/// <param name="from"></param> | ||
/// <param name="to"></param> | ||
/// <returns></returns> | ||
private static string ReadDateBasedSqlReportResource(string reportFile, DateTime? from, DateTime? to) | ||
{ | ||
// Get non-NULL versions of the from and to dates | ||
var nonNullFromDate = (from ?? DateTime.MinValue).ToString(DateFormat); | ||
var nonNullToDate = (to ?? DateTime.MaxValue).ToString(DateFormat); | ||
|
||
// Read and return the query, replacing the date range parameters | ||
var query = ReadSqlResource(reportFile, new Dictionary<string, string> | ||
{ | ||
{ FromDatePlaceHolder, nonNullFromDate }, | ||
{ ToDatePlaceHolder, nonNullToDate } | ||
|
||
}); | ||
|
||
return query; | ||
} | ||
|
||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
src/FlightRecorder.BusinessLogic/Logic/ReportManagerBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using FlightRecorder.Data; | ||
using Microsoft.EntityFrameworkCore; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
|
||
namespace FlightRecorder.BusinessLogic.Logic | ||
{ | ||
[ExcludeFromCodeCoverage] | ||
internal abstract class ReportManagerBase | ||
{ | ||
private readonly FlightRecorderDbContext _context; | ||
|
||
protected ReportManagerBase(FlightRecorderDbContext context) | ||
{ | ||
_context = context; | ||
} | ||
|
||
/// <summary> | ||
/// Generate the report for entity type T given a query that returns rows mapping to that | ||
/// type | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
/// <param name="query"></param> | ||
/// <returns></returns> | ||
protected async Task<IEnumerable<T>> GenerateReport<T>(string query, int pageNumber, int pageSize) where T : class | ||
{ | ||
// Pagination using Skip and Take causes the database query to fail with FromSqlRaw, possible | ||
// dependent on the DBS. To avoid this, the results are queried in two steps: | ||
// | ||
// 1) Query the database for all the report results and convert to a list | ||
// 2) Extract the required page from the in-memory list | ||
var all = await _context.Set<T>().FromSqlRaw(query).ToListAsync(); | ||
var results = all.Skip((pageNumber - 1) * pageSize).Take(pageSize); | ||
return results; | ||
} | ||
|
||
/// <summary> | ||
/// Get the full path to a file held in the SQL sub-folder | ||
/// </summary> | ||
/// <param name="fileName"></param> | ||
/// <returns></returns> | ||
protected static string GetSqlFilePath(string fileName) | ||
{ | ||
var assembly = Assembly.GetExecutingAssembly(); | ||
var assemblyFolder = Path.GetDirectoryName(assembly.Location); | ||
var sqlFilePath = Path.Combine(assemblyFolder, "sql", fileName); | ||
return sqlFilePath; | ||
} | ||
|
||
/// <summary> | ||
/// Read a SQL file and perform placeholder value replacement | ||
/// </summary> | ||
/// <param name="file"></param> | ||
/// <param name="placeHolderValues"></param> | ||
protected static string ReadSqlFile(string file, Dictionary<string, string> placeHolderValues) | ||
{ | ||
string content = ""; | ||
|
||
// Open a stream reader to read the file content | ||
using (var reader = new StreamReader(file)) | ||
{ | ||
// Read the file content | ||
content = reader.ReadToEnd(); | ||
|
||
// Perform place holder replacement | ||
content = ReplacePlaceHolders(content, placeHolderValues); | ||
} | ||
|
||
return content; | ||
} | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="file"></param> | ||
/// <param name="placeHolderValues"></param> | ||
/// <returns></returns> | ||
protected static string ReadSqlResource(string file, Dictionary<string, string> placeHolderValues) | ||
{ | ||
string content = ""; | ||
|
||
// Get the name of the resource and a resource stream for reading it | ||
var assembly = Assembly.GetExecutingAssembly(); | ||
var sqlResourceName = $"FlightRecorder.BusinessLogic.Sql.{file}"; | ||
var resourceStream = assembly.GetManifestResourceStream(sqlResourceName); | ||
|
||
// Open a stream reader to read the file content | ||
using (var reader = new StreamReader(resourceStream)) | ||
{ | ||
// Read the file content | ||
content = reader.ReadToEnd(); | ||
|
||
// Perform place holder replacement | ||
content = ReplacePlaceHolders(content, placeHolderValues); | ||
} | ||
|
||
return content; | ||
} | ||
|
||
/// <summary> | ||
/// Perform place holder replacement on content read from an embedded resource or SQL file | ||
/// </summary> | ||
/// <param name="content"></param> | ||
/// <param name="placeHolderValues"></param> | ||
/// <returns></returns> | ||
private static string ReplacePlaceHolders(string content, Dictionary<string, string> placeHolderValues) | ||
{ | ||
foreach (var placeHolder in placeHolderValues.Keys) | ||
{ | ||
content = content.Replace(placeHolder, placeHolderValues[placeHolder]); | ||
} | ||
|
||
return content; | ||
} | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/FlightRecorder.BusinessLogic/Sql/AirlineStatistics.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
SELECT a.Name, | ||
COUNT( DISTINCT s.Id ) AS "Sightings", | ||
COUNT( DISTINCT f.Id ) AS "Flights", | ||
COUNT( DISTINCT l.Id ) AS "Locations", | ||
COUNT( DISTINCT ai.Id ) AS "Aircraft", | ||
COUNT( DISTINCT m.Id ) AS "Models", | ||
COUNT( DISTINCT ma.Id ) AS "Manufacturers" | ||
FROM AIRLINE a | ||
INNER JOIN FLIGHT f ON f.Airline_Id = a.Id | ||
INNER JOIN SIGHTING s ON s.Flight_Id = f.Id | ||
INNER JOIN LOCATION l ON l.Id = s.Location_Id | ||
INNER JOIN AIRCRAFT ai ON ai.Id = s.Aircraft_Id | ||
INNER JOIN MODEL m ON m.Id = ai.Model_Id | ||
INNER JOIN MANUFACTURER ma ON ma.Id = m.Manufacturer_Id | ||
WHERE s.Date BETWEEN '$from' AND '$to' | ||
GROUP BY a.Name | ||
ORDER BY a.Name ASC; |
16 changes: 16 additions & 0 deletions
16
src/FlightRecorder.BusinessLogic/Sql/LocationStatistics.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
SELECT l.Name, | ||
COUNT( DISTINCT s.Id ) AS "Sightings", | ||
COUNT( DISTINCT f.Id ) AS "Flights", | ||
COUNT( DISTINCT ai.Id ) AS "Aircraft", | ||
COUNT( DISTINCT m.Id ) AS "Models", | ||
COUNT( DISTINCT ma.Id ) AS "Manufacturers" | ||
FROM AIRLINE a | ||
INNER JOIN FLIGHT f ON f.Airline_Id = a.Id | ||
INNER JOIN SIGHTING s ON s.Flight_Id = f.Id | ||
INNER JOIN LOCATION l ON l.Id = s.Location_Id | ||
INNER JOIN AIRCRAFT ai ON ai.Id = s.Aircraft_Id | ||
INNER JOIN MODEL m ON m.Id = ai.Model_Id | ||
INNER JOIN MANUFACTURER ma ON ma.Id = m.Manufacturer_Id | ||
WHERE s.Date BETWEEN '$from' AND '$to' | ||
GROUP BY l.Name | ||
ORDER BY l.Name ASC; |
16 changes: 16 additions & 0 deletions
16
src/FlightRecorder.BusinessLogic/Sql/ManufacturerStatistics.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
SELECT ma.Name, | ||
COUNT( DISTINCT s.Id ) AS "Sightings", | ||
COUNT( DISTINCT f.Id ) AS "Flights", | ||
COUNT( DISTINCT l.Id ) AS "Locations", | ||
COUNT( DISTINCT ai.Id ) AS "Aircraft", | ||
COUNT( DISTINCT m.Id ) AS "Models" | ||
FROM AIRLINE a | ||
INNER JOIN FLIGHT f ON f.Airline_Id = a.Id | ||
INNER JOIN SIGHTING s ON s.Flight_Id = f.Id | ||
INNER JOIN LOCATION l ON l.Id = s.Location_Id | ||
INNER JOIN AIRCRAFT ai ON ai.Id = s.Aircraft_Id | ||
INNER JOIN MODEL m ON m.Id = ai.Model_Id | ||
INNER JOIN MANUFACTURER ma ON ma.Id = m.Manufacturer_Id | ||
WHERE s.Date BETWEEN '$from' AND '$to' | ||
GROUP BY ma.Name | ||
ORDER BY ma.Name ASC; |
Oops, something went wrong.