Written by 13:49 Basics of C#, Languages & Coding • 3 Comments

Functional C#

C# is a multi-paradigm programming language. Recently, the course has been set towards new functional constructions in C#. We can go further and add other extension methods that allow writing less code without ‘climbing’ in the F# territory.

PipeTo

As Pipe Operator is not going to be included in the upcoming next release, we can write the code using the method.

public static TResult PipeTo<TSource, TResult>(
    this TSource source, Func<TSource, TResult> func)
    => func(source);

Imperative variant

public IActionResult Get()
{
    var someData = query
        .Where(x => x.IsActive)
        .OrderBy(x => x.Id)
        .ToArray();
    return Ok(someData);
}

With PipeTo

public IActionResult Get() =>  query
    .Where(x => x.IsActive)
    .OrderBy(x => x.Id)
    .ToArray()
    .PipeTo(Ok);

As you can see, in the first variant, I needed to cast a look back at a the variable declaration and then proceed to Ok, while with PipeTo, the execution flow is running strictly left to right, top to bottom.

Either

In the real world, there are more branching algorithms, rather than the linear ones:

public IActionResult Get(int id) =>  query
    .Where(x => x.Id == id)
    .SingleOrDefault()
    .PipeTo(x => x != null ? Ok(x) : new NotFoundResult(“Not Found”));

It does not look so good anymore. Let’s fix it using the Either method:

public static TOutput Either<TInput, TOutput>(this TInput o, Func<TInput, bool> condition,
    Func<TInput, TOutput> ifTrue, Func<TInput, TOutput> ifFalse)
    => condition(o) ? ifTrue(o) : ifFalse(o);

public IActionResult Get(int id) =>  query
    .Where(x => x.Id == id)
    .SingleOrDefault()
    .Either(x => x != null, Ok, _ => (IActionResult)new NotFoundResult("Not Found"));

Add a null check overload:

public static TOutput Either<TInput, TOutput>(this TInput o, Func<TInput, TOutput> ifTrue,
    Func<TInput, TOutput> ifFalse)
    => o.Either(x => x != null, ifTrue, ifFalse);

public IActionResult Get(int id) =>  query
    .Where(x => x.Id == id)
    .SingleOrDefault()
    .Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));

Unfortunately, the type inference in C# is not perfect. That’s why I had to add an explicit cast to IActionResult.

Do

Get methods of controllers are not supposed to create side effects. However, sometimes they are really needed.

public static T Do<T>(this T obj, Action<T> action)
{
    if (obj != null)
    {
        action(obj);
    }

    return obj;
}

public IActionResult Get(int id) =>  query
    .Where(x => x.Id == id)
    .Do(x => ViewBag.Title = x.Name)
    .SingleOrDefault()
    .Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));

With such code organization, it will be impossible to miss this effect with Do during code review. Though, using DO is a point at issue.

ById

Do not you think that constantly repeating q.Where(x => x.Id == id).SingleOrDefault() is very annoying?

public static TEntity ById<TKey, TEntity>(this IQueryable<TEntity> queryable, TKey id)
    where TEntity : class, IHasId<TKey> where TKey : IComparable, IComparable<TKey>, IEquatable<TKey>
    => queryable.SingleOrDefault(x => x.Id.Equals(id));

public IActionResult Get(int id) =>  query
    .ById(id)
    .Do(x => ViewBag.Title = x.Name)
    .Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));

What if I do not want to get the whole entity and I need projection:

public static TProjection ById<TKey, TEntity, TProjection>(this IQueryable<TEntity> queryable, TKey id,
Expression<Func<TEntity, TProjection>> projectionExpression)
    where TKey : IComparable, IComparable<TKey>, IEquatable<TKey>
    where TEntity : class, IHasId<TKey>
    where TProjection : class, IHasId<TKey>
    => queryable.Select(projectionExpression).SingleOrDefault(x => x.Id.Equals(id));   

public IActionResult Get(int id) =>  query
    .ById(id, x => new {Id = x.Id, Name = x.Name, Data = x.Data})
    .Do(x => ViewBag.Title = x.Name)
    .Either(Ok, _ => (IActionResult)new NotFoundResult("Not Found"));

I think that by now the (IActionResult)new NotFoundResult(“Not Found”)) method is overused. So, you can easily write the OkOrNotFound method by yourself.

Paginate

Perhaps, there are no applications that work with data without paging.

Instead of:

.Skip((paging.Page - 1) * paging.Take)
.Take(paging.Take);

You may write the code in the following way:

public interface IPagedEnumerable<out T> : IEnumerable<T>
{
    long TotalCount { get; }
}

public static IQueryable<T> Paginate<T>(this IOrderedQueryable<T> queryable, IPaging  paging) 
=> queryable
    .Skip((paging.Page - 1) * paging.Take)
    .Take(paging.Take);

public static IPagedEnumerable<T> ToPagedEnumerable<T>(this IOrderedQueryable<T> queryable,
    IPaging paging)
    where T : class
    => From(queryable.Paginate(paging).ToArray(), queryable.Count());

public static IPagedEnumerable<T> From<T>(IEnumerable<T> inner, int totalCount)
    =>  new PagedEnumerable<T>(inner, totalCount);

public IActionResult Get(IPaging paging) =>  query
    .Where(x => x.IsActive)
    .OrderBy(x => x.Id)
    .ToPagedEnumerable(paging)
    .PipeTo(Ok);

IQueryableFilter

As it has been discussed, I suggested a different way to group Where and OrderBy in LINQ statements, which might be interesting for you:

public class MyNiceSpec : AutoSpec<MyNiceEntity>
{
    public int? Id { get; set; }

    public string Name { get; set; }

    public string Code { get; set; }

    public string Description { get; set; }
}

public IActionResult Get(MyNiceSpec spec) =>  query
    .Where(spec)
    .OrderBy(spec)
    .ToPagedEnumerable(paging)
    .PipeTo(Ok);

It makes sense to apply Where before the Select clause, and sometimes – after.

Add the MaybeWhere method, which works both with IQueryableSpecification, and Expression<Func<T, bool>>

public static IQueryable<T> MaybeWhere<T>(this IQueryable<T> source, object spec)
    where T : class
{
    var specification = spec as IQueryableSpecification<T>;
    if (specification != null)
    {
        source = specification.Apply(source);
    }

    var expr = spec as Expression<Func<T, bool>>;
    if (expr != null)
    {
        source = source.Where(expr);
    }

    return source;
}

Now, we can write a method that takes into account different variants:

public static IPagedEnumerable<TDest> Paged<TEntity, TDest>(
this IQueryableProvider queryableProvider, IPaging spec ,
Expression<Func<TEntity, TDest>> projectionExpression)
    where TEntity : class, IHasId
    where TDest : class, IHasId
    => queryableProvider
        .Query<TEntity>()
        .MaybeWhere(spec)
        .Select(projectionExpression)
        .MaybeWhere(spec)
        .MaybeOrderBy(spec)
        .OrderByIdIfNotOrdered()
        .ToPagedEnumerable(spec);

or you can use Queryable Extensions AutoMapper:

public static IPagedEnumerable<TDest> Paged<TEntity, TDest>(this IQueryableProvider queryableProvider,
    IPaging spec)
    where TEntity : class, IHasId
    where TDest : class, IHasId => queryableProvider
        .Query<TEntity>()
        .MaybeWhere(spec)
        .ProjectTo<TDest>()
        .MaybeWhere(spec)
        .MaybeOrderBy(spec)
        .OrderByIdIfNotOrdered()
        .ToPagedEnumerable(spec);

If you do not like to use IPaging, IQueryableSpecififcation and IQueryableOrderBy simultaneously for one object, you can write the following code:

public static IPagedEnumerable<TDest> Paged<TEntity, TDest>(this IQueryableProvider queryableProvider,
    IPaging paging, IQueryableOrderBy<TDest> queryableOrderBy,
IQueryableSpecification<TEntity> entitySpec = null, IQueryableSpecification<TDest> destSpec = null)
    where TEntity : class, IHasId where TDest : class
    => queryableProvider
        .Query<TEntity>()
        .EitherOrSelf(entitySpec, x => x.Where(entitySpec))
        .ProjectTo<TDest>()
        .EitherOrSelf(destSpec, x => x.Where(destSpec))
        .OrderBy(queryableOrderBy)
        .ToPagedEnumerable(paging);

As a result, we get three rows of code for the method that filters, sorts and provides paging for any data sources that support LINQ.

public IActionResult Get(MyNiceSpec spec) =>  query
    .Paged<int, MyNiceEntity, MyNiceDto>(spec)
    .PipeTo(Ok);

Unfortunately, signatures of methods in C# look humongous due to the load of generics. Luckily, we may omit method parameters in the application code. The same is with the signatures of LINQ extensions. How often do you specify the type that the Select clause returns? With var, we can avoid this torture.

Also Read:

Functional F# that slowly appears in C#

Tags: , , Last modified: September 23, 2021
Close