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>();