Saturday, 7 January 2023

List patterns in C# 11 - And getting a compiler error on code that should work

I have tested out list patterns in .NET 7 and C#. I am using Linqpad 7 and .NET 7.0.1. List patterns are useful I guess to compared sequences and fun to test out. The '_' discard here means to ignore the number at a given position and the '..' range here is to match anything between a given set of value and one index and then a given value at a higher index with arbitrary values between. But in one of the samples, it says you can capture variables inside list patterns. I cannot make it work, I get a compiler error. I am getting a CS0165 'Use of unassigned local variable' error when I try to access the variable(s) captured. I tried checking the crashing code also inside VsCode, still getting the error, however if I debug inside Linqpad I can see the variables that are captured got values at least.

    var someOddNumbers = new int[] { 1, 3, 5, 7, 9, 11 };
    bool resultX = someOddNumbers is [1, 3, _, _, _, 11];
    resultX.Dump("The 'someOddNumbers' equals a sequence of numbers 1,3,then three arbitrary numbers, then 11?");

    bool isOdd = someOddNumbers is [1, .., 9, 11];
    isOdd.Dump("The 'someOddNumbers' equals a sequence of numbers 1, some arbitrary numbers, then ending with 9 and 11?");
    
    result = input is [var firstOddNumber,.. var lastOddNumber];

    if (result)
    {
        Console.WriteLine($"The captured variables are: {firstOddNumber} and {lastOddNumber}"); //this lines gives the CS0165 error
    }

If I comment out the if block I can run the code sample, and in the debugger I can see firstOddNumber and lastOddNumber being set to a value at runtime. But the C# 11 compiler seems to think this is illeagal code since it is using an unintialized variable. I expected to not get a compiler error and be able to also capture the variables defined in the list pattern. I cannot understand the usage of such variables if I cannot use them. I understand that these variables might not be captured if the list pattern does not match, but even when checking if a match was present, I got the compilation error. I can however run the code, just not access the variables.

Sunday, 11 December 2022

Presentation in Norwegian presenting demo repository with GraphQL and Hotchocolate/Strawberryshake

I have written a presentation about GraphQL demo repository of mine here. It is in Norwegian, so it will not be translated to english here. For Norwegian readers : Jeg har skrevet en presentasjon på norsk om GraphQL som går igjennom et demo repository som benytter GraphQL, med HotChocolate i backend, sammen med Entity Framework Core 6 og .net 6 (C# selvsagt) og som i frontend benytter Blazor og StrawberryShake ! Dere kan lese de 43 slidesene vedlagt OneDrive lenken under. I foredraget går jeg igjennom key giveaways om GraphQL, inkludert case insensitive søk, omtale om paginerte data og projisering og gir et overblikk av hva GraphQL går ut på og hvilke fordeler man kan få ut av det. Sentrale fordeler med GraphQL er : * Fleksibilitet - spesifiser hvilke felter du vil ha for å unngå "overfetching" * -Ytelse - færre API kall og unngår waterfall opphenting hvor man må hente opp stadig flere ressurser som i fra et REST API, men får en aggreggert tilpasset struktur av de data man faktisk vil ha tilbake * Ett felles endepunkt /graphql - man slipper å lage controllere som i REST API - som ofte føles som unødvendig. GraphQL er ikke noe som kan løse alle utfordringer i API design, men det kan gi klienter mye mer fleksibilitet og også unngå at API designere må stadig lage flere metoder og som har både "overfetching" eller enda verre - underfetching - som gir flere API kall og dårligere ytelse. Man utnytter båndbredde og serverressurser bedre ved å kun hente ut informasjon man trenger. Og GraphQL er ikke bare orientert rundt spørringer, men også endringer (mutations), pub sub event pattern (Subscriptions) og en hel del annen funksjonalitet som tilhører API design ! Du kan lese Powerpoint presentasjonen her (43 slides, lesetid ca 1 time om du vil studere det nøye, en 15 minutt om du vil skumlese mer). #blazor #hotchocolate #strawberryshake #chillicream #apidesign #csharp #dotnet #codinggrounds The presentation is here :

Powerpoint presentation (Norwegian, 11th december 2022) : https://1drv.ms/u/s!AhGGDxs-tzqJcFrls6Fue8Xnjx4?e=lWYYwU

Friday, 18 November 2022

Case insensitive search in HotChocolate GraphQL

I tested out the contains operator on string fields today in GraphQL. It is actually case sensitive, and this is counter-intuitive since I have connected the GraphQL database to a SQL database, which performs usually a case insensitive search with the 'contains' operator (using 'LIKE' operator
under the hood). The following adjustments need to be made to make it work : First off, define a class inheriting QueryableStringOperationHandler
 

using HotChocolate.Data.Filters;
using HotChocolate.Data.Filters.Expressions;
using HotChocolate.Language;
using System.Linq.Expressions;
using System.Reflection;

namespace AspNetGraphQLDemoV2.Server
{
    public class QueryableStringInvariantContainsHandler : QueryableStringOperationHandler
    {
        private static readonly MethodInfo _contains = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!;

        public QueryableStringInvariantContainsHandler(InputParser inputParser) : base(inputParser)
        {
        }

        protected override int Operation => DefaultFilterOperations.Contains;
        public override Expression HandleOperation(QueryableFilterContext context, IFilterOperationField field, 
            IValueNode value, object? parsedValue)
        {
            Expression property = context.GetInstance();
            if (parsedValue is string str)
            {
                var toLower = Expression.Call(property, typeof(string).GetMethod("ToLower", Type.EmptyTypes)!); //get the ToLower method of string class via reflection. The Type.EmptyTypes will retrieve the method overload of ToLower which accept no arguments.
                var finalExpression = Expression.Call(toLower, _contains, Expression.Constant(str.ToLower()));
                return finalExpression;

            }
            throw new InvalidOperationException();
        }
    }
}

 
We overload the Operation to 'Contains' so we are going to adjust how we treat the expression tree of GraphQL, overriding the HandleOperation. This is similar to the QueryableStringOperationHandler presented here: https://chillicream.com/docs/hotchocolate/api-reference/extending-filtering, in our case we support the contains method instead. The finalExpression 'DebugView' evaluates to :
.Call (.Call ($_s0.OfficialName).ToLower()).Contains("tind") To actually use this adaption of filtering of string properties in HotChocolate, we do the following in program.cs (startup class in .net 6) to not only add filtering support to HotChocolate, but also add a filter convention extension first to make it easier to register and avoid adding cluttering to the startup code in program.cs :

 
 using HotChocolate.Data.Filters;
using HotChocolate.Data.Filters.Expressions;

namespace AspNetGraphQLDemoV2.Server
{
    public class FilterConventionExtensionForInvariantContainsStrings : FilterConventionExtension
    {
        protected override void Configure(IFilterConventionDescriptor descriptor)
        {
            descriptor.AddProviderExtension(new QueryableFilterProviderExtension(
                                    y => y.AddFieldHandler<QueryableStringInvariantContainsHandler>()));
        }    
    }
}

 
You can see an example how I register this case insensitive contains filter in the program.cs example code below. Note both the usage of .AddFiltering() and the .AddConvention() call.
 
 
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("MountainsV2Db");
builder.Services
    .AddDbContext<MountainDbContext>(options =>
    {
        options.UseSqlServer(connectionString);
    })
    .AddCors()
    .AddGraphQLServer()
    .AddProjections()
    .AddFiltering()
    .AddConvention<IFilterConvention, FilterConventionExtensionForInvariantContainsStrings>()
    .AddSorting()
    .RegisterDbContext<MountainDbContext>()
    .AddQueryType<MountainQueries>()
    .AddMutationType<MountainMutations>()
    .AddSubscriptionType<MountainSubscriptions>()
    .AddInMemorySubscriptions();

var app = builder.Build();
 
Now, sample .graphql file (containing a GraphQL query) that shows how the new filtering capability can be used :
 
 query {
  mountains (where: { officialName: {contains: "TiND"}}) {
    officialName
  }
}
 
The backend code retrieves a list of data from a table and I have added the [UseFiltering] attribute to specify for HotChocolate that filtering should be supported to the method.
 
   public class MountainQueries
    {
        [UseFiltering]
        [UseSorting]
        public async Task<List<Mountain>> GetMountains([Service] MountainDbContext mountainDb)
        {
            return await mountainDb.Mountains.ToListAsync();
        }

//..