QueryContainer
- Keep your queries static and prebuilt as possible. You will reach better performance.
- To reuse a query use CompareFormat() instead of Compare(), which can be used only once per build. see more...
Static and Prebuilt Queries
- Create a special folder (i.e. Specs) for static specification classes in your infrastructure library.
- Name the specifications according to what they select.
- Do not forget to prebuild the query specification.
//
// Example of Usual Match Join
//
namespace YourProject.Infra
{
public static class D3MatchBaseQuery
{
private static ISpecification<Match>? that = null;
public static ISpecification<Match> GetSingleton(ID3Context d3Context)
{
if (that == null || d3Context.GetType() != QueryContainer.D3ContextType)
{
that = D3Specification.Create<Match>(d3Context).OrderBy(x => x.MatchId) // sort order 0 + 10
.And(D3Specification.Create<MatchSetScore>(d3Context).OrderBy(x => x.SetOrder)) // sort order 0 + 1 (count of sorts) + 10
.And(D3Specification.Create<Team>(d3Context))
.And(D3Specification.Create<Tournament>(d3Context))
.And(D3Specification.Create<TourSerie>(d3Context))
.OrderBy(x => x.TournamentId, true, 1); // sort order 1
D3Specification.PreBuild(that);
}
return that;
}
}
}
//
// Example of Usual Match Join with Team Name Filter
//
namespace YourProject.Infra
{
public static class D3MatchBaseFilterByTeamNameQuery
{
private static ISpecification<Match>? that = null;
public static ISpecification<Match> GetSingleton(ID3Context d3Context)
{
if (that == null || !QueryContainer.initialized)
{
that = D3MatchBaseQuery.GetSingleton(D3Context)
.And(D3Specification.Create<Team>(d3Context).CompareFormat(x => x.Name, ComparisonOp.EQUALS, 0));
D3Specification.PreBuild(that);
}
return that;
}
}
}
IQueryContainer Interface
- Create an interface in your domain library.
- Create interface methods for all the static queries that contains parameters and create interface properties for all the static parameterless queries.
- The interface will serve you for the query reusing as you do not have repeat the long expressions like new D3SpecificationWithParams(...GetSingleton(D3Context) { Parameters = ...} for each query.
- Use types of ISpecification for non-final static queries, which you want to reuse internally in a query container or the Specs namespace.
- Use ISpecificationWithParams for final queries, which you want to use outside a query container, which you can attach the parameters from the function arguments to.
- IQueryContainer can be registered as a scope lifetime service.
namespace YourProject.Domain
{
public interface IQueryContainer
{
public ISpecification<Match> MatchBaseQuery { get; }
public ISpecificationWithParams<Match> MatchBaseFilterByTeamNameQuery(string teamName);
}
}
QueryContainer Class
- Implement the interface in your infrastructure library where you have the static specifications defined.
- You can have more implementations of IQueryContainer.
- We recommend to initialize all the specifications if they have not been initialized after an application (re)start as you can see in this example.
namespace YourProject.Infra
{
public class QueryContainer : IQueryContainer
{
private ID3Context D3Context { get; set; }
private static StringBuilder Locker { get; } = new StringBuilder();
internal static bool initialized = false; // static!!! - Specifications' initialization must be done only once after the application starts.
public ISpecification<Match> MatchBaseQuery { get; } = D3MatchBaseQuery.GetSingleton(D3Context);
public QueryContainer(ID3Context D3Context)
{
this.D3Context = D3Context;
if (!initialized)
{
lock (Locker)
{
InitAllSpecs();
initialized = true;
}
}
}
public void InitAllSpecs()
{
var types = GetType().Assembly.GetExportedTypes();
foreach (var type in types)
{
if (type == null || (!type.FullName?.Contains("Specs") ?? false))
{
continue;
}
if (type.GetMethod("GetSingleton", BindingFlags.Static | BindingFlags.Public) is MethodInfo method)
{
method.Invoke(null, new object[] { D3Context });
}
}
}
public ISpecificationWithParams<Match> MatchBaseFilterByTeamNameQuery(string teamName)
{
return new D3SpecificationWithParams<Match>(D3MatchBaseFilterByTeamNameQuery.GetSingleton(D3Context))
{
Parameters = new Dictionary<int, object?>
{
{ 0, teamName }
}
};
}
}
}
Dependency Injection
...
builder.Services.AddSingleton<ID3ContextFactory, MySQLD3ContextFactory>();
builder.Services.AddScoped<ID3Context>(x => x.GetService<ID3ContextFactory>()!.Create());
...
builder.Services.AddScoped<IQueryContainer, QueryContainer>();