Linql.Server 1.0.0-beta.5035

This is a prerelease version of Linql.Server.
dotnet add package Linql.Server --version 1.0.0-beta.5035
                    
NuGet\Install-Package Linql.Server -Version 1.0.0-beta.5035
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Linql.Server" Version="1.0.0-beta.5035" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Linql.Server" Version="1.0.0-beta.5035" />
                    
Directory.Packages.props
<PackageReference Include="Linql.Server" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Linql.Server --version 1.0.0-beta.5035
                    
#r "nuget: Linql.Server, 1.0.0-beta.5035"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Linql.Server@1.0.0-beta.5035
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Linql.Server&version=1.0.0-beta.5035&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Linql.Server&version=1.0.0-beta.5035&prerelease
                    
Install as a Cake Tool

Linql.Server

A C# Server for the Linql Language. Allows your api to be used as if it were an IQueryable.

//Some LinqlSearch string
string json = "SomeLinqlSearchJson";

//Set the assemblies that are valid inside the LinqlContext
HashSet<Assembly> assemblies = new HashSet<Assembly>()
{
    typeof(Boolean).Assembly,
    typeof(Enumerable).Assembly,
    typeof(Queryable).Assembly
};

//Create a LinqlCompiler with the assemblies we care about
LinqlCompiler Compiler = new LinqlCompiler(assemblies);

//Turn the json into a generic LinqlSearch
LinqlSearch? search = JsonSerializer.Deserialize<LinqlSearch>(json);

//Execute the LinqlSearch either with a concrete type, or generically
IEnumerable<DataModel> typedData = this.Compiler.Execute<IEnumerable<DataModel>>(search, this.Data);

object genericData = this.Compiler.Execute(search, this.Data);

Linql Overview

Installation

dotnet add package Linql.Server -v 1.0.0-alpha1

WebApi Usage

Program.cs
var builder = WebApplication.CreateBuilder(args);
...
builder.Services.AddSingleton<LinqlCompiler, CustomLinqlCompiler>();

CustomLinqlCompiler.cs
 public class CustomLinqlCompiler : LinqlCompiler
{
    public CustomLinqlCompiler() : base() 
    { 
        //Loads .Net System types, as well as NetTopologySuite and Linq Assemblies
        this.ValidAssemblies = new HashSet<Assembly>()
        {
            typeof(Boolean).Assembly,
            typeof(Enumerable).Assembly,
            typeof(Queryable).Assembly,
            typeof(Geometry).Assembly,
            typeof(State).Assembly,
        };

        //Add deserializer to Linql deserializer chain
        var geoJsonConverterFactory = new GeoJsonConverterFactory();
        this.JsonOptions.Converters.Add(geoJsonConverterFactory);
    }
}

StateController.cs
//Example Controller that returns StateData
[ApiController]
[Route("[controller]")]
public class StateController : ControllerBase
{
    private readonly ILogger<StateController> _logger;

    protected DataService DataService { get; set; }

    protected LinqlCompiler Compiler { get; set; }

    public StateController(ILogger<StateController> logger, DataService DataService, LinqlCompiler Compiler)
    {
        _logger = logger;
        this.DataService = DataService;
        this.Compiler = Compiler;

    }

    [HttpPost]
    public async Task<object> Linql(LinqlSearch Search)
    {
        object result = await this.Compiler.ExecuteAsync(Search, this.DataService.StateData.AsQueryable());
        return result;
    }

    //A Batching Method Example.
    [HttpPost("/Batch")]
    public async Task<List<object>> Batch(List<LinqlSearch> Searches)
    {
        List<Task<object>> tasks = Searches.Select(r =>
        {
            LinqlCompiler compiler = new CustomLinqlCompiler();
            Task<object> result = compiler.ExecuteAsync(r, this.DataService.StateData.AsQueryable());
            return result;
        }).ToList();

        var taskResults = await Task.WhenAll(tasks);
        List<object> results = taskResults.ToList();

        return results;
    }
}

Full Example

Checkout our full example here.

Library Support

EntityFramework 6

Linql is compatible with EntityFramework 6. There are tests here.

EntityFramework Core

Linql should be compatible with EntityFramework Core as well. I do not have tests for this yet, but will be working on them soon. Conceptually, there should be no issue.

Advanced Concepts

Batching

In our full example, as well as in the above sample, you can see the "Batching" technique. Batching can significantly reduce the overhead of your application, by bundling requests together into one Http Request. The server then multiplexes the results.

Batching is mostly always desired, but requires that the server implement a Generic Controller interface.

Generic Controllers

It's obviously advantageous to use Linql over your entire data model. To do so, a generic controller interface is preferrable. An example of this will be provided at a later date.

Information Extraction

Find functionality built into the library allows application developers to search for patterns within linql searches in order to implement more advanced logic.

Find has two options, Exact and Similar.

Exact will only return results if the statements exactly match, while Similar will do its best to try and find things that relatively match. Either method will recursively try to find the expression anywhere in the expression tree.

Only the first expression in the comparison search is used for matching.

A common usecase is to limit the max number of objects returned for types.

EnforceLimit.cs
LinqlSearch someLinqlSearch;

//Set some limit
int limit = 500;

//Create a search that will look for the Take method
IQueryable<DataModel> takeSearch = new LinqlSearch<DataModel>();
takeSearch = takeSearch.Take(500);
LinqlSearch takeCompiled = takeSearch.ToLinqlSearch();

//Look for an expression that is similar ot the take method
List<LinqlExpression> findResults = someLinqlSearch.Find(takeCompiled, LinqlFindOption.Similar);

//If not method is found, splice the take expression into the user supplied search
if(findResults.Count() == 0)
{
    takeSearch.Expression.LastOrDefault()?.Next = takeSearch.Expressions.FirstOrDefault();
}
else
{
    //Go through the results, compare the limits, and override if they exceeded the limit.  
    //Realisitcally, we'd only expect 1 item in findResults, but we loop through for consistency.
    findResults.ForEach(r =>
    {
        if (r is LinqlFunction fun)
        {
            fun.Arguments.ForEach(arg =>
            {
                if (arg is LinqlConstant constant && constant.Value > limit)
                {
                    constant.Value = limit;
                }
            });
        }
    });
}

Compiler Hooks

The first level of Linql searches provide a before and after lifecycle hook method. These can be used to provide advanced logic before and after expressions are executed.

In example, suppose I want to disallow the selection of a particular property. I can accomplish this like so:

DisablePropertySelectionHook.cs
//Create the linql hook
LinqlCompilerHook disableSelectHook = new LinqlBeforeExecutionHook((fun, input, inputType, method, args) =>
{
    MemberInfo prop = typeof(DataModel).GetMember(nameof(DataModel.Decimal)).FirstOrDefault();

    if(fun.FunctionName == nameof(Queryable.Select))
    {
        LambdaExpression lam = args.Where(r => r is LambdaExpression).Cast<LambdaExpression>().FirstOrDefault();

        if(lam != null && lam.Body is MemberExpression member && member.Member == prop)
        {
            throw new Exception($"Not allowed to select into property {nameof(DataModel.Decimal)} on type {nameof(DataModel)}");
        }
    }

    return Task.CompletedTask;
});

...

//Add the hook into the linql compiler.  There is a corresponding RemoveHook method as well. 
this.Compiler.AddHook(disableSelectHook);

AfterExecutionHookExample.cs
//Example prototype of after execution hooks
LinqlCompilerHook afterExecutionHook = new LinqlAfterExecutionHook((fun, input, inputType, method, args, object) =>
{
   ...
});

...

//Add the hook into the linql compiler.  There is a corresponding RemoveHook method as well. 
this.Compiler.AddHook(afterExecutionHook);

ORM Row Level Permissioning

If using an ORM, and row level access control can be achieved by backing the data model generated for a user to point to views that enforce row level permissions.

In conjunction with Linql's Find functionality and lifecycle hooks, this strategy provides an "open" experience for consumers while allowing developers to enforce data access controls at scale.

Development

  • Visual Studio 2022 17.4
  • .Net 7 to run tests and example projects.

Future Enhancements

  • Better Find/Hook support
  • Allow linql queries to continue after materialization
  • Support multi-line statements
  • Support multiple LinqlSearches in the same context, and allow interaction between them (Join)
  • Anonymous Types (which would then allow specific select statements)
  • Performance Tests
  • More test cases

Testing

Unit tests are located here.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-beta.5035 174 8/7/2025
1.0.0-beta.5027 180 8/7/2025
1.0.0-beta.5025 182 8/7/2025
1.0.0-beta.5024 179 8/6/2025
1.0.0-beta.5023 180 8/6/2025
1.0.0-beta.5018 188 8/6/2025
1.0.0-beta.3922 333 4/14/2025
1.0.0-beta.3877 142 4/9/2025
1.0.0-beta.3867 133 4/9/2025
1.0.0-beta.2293 170 9/27/2024
1.0.0-beta.2287 111 9/26/2024
1.0.0-beta.1537 875 7/10/2024
1.0.0-beta.292 442 2/1/2024
1.0.0-alpha8 539 12/28/2023
1.0.0-alpha356 160 11/1/2023
1.0.0-alpha312 96 10/3/2023
1.0.0-alpha292 157 9/28/2023
1.0.0-alpha284 76 2/1/2024
1.0.0-alpha256 112 9/10/2023
1.0.0-alpha255 107 9/10/2023
1.0.0-alpha2 138 8/13/2023
1.0.0-alpha1 144 7/16/2023