Friday, 8 July 2022

Using MediatR to implement CQRS pattern with EF Core

This article will present a sample solution I have pushed to GitHub. It uses MediatR library to showcase CQRS pattern together with .NET 6 and Entity Framework Core. MediatR takes care of decoupling the sending of messages to handling messages in-process. It can be used in the data layer together with EF Core for example to dispatch (send) messages which will either handle (execute) a 'command' (defined in CQRS as a 'write operation') or process and return a 'query' (defined in CQRS as a 'read' operation). What we gain from CQRS is the ability to offload the sending of these messages (commands and queries) to the correct handler for given message handler (executing either a 'command' or return the results of a 'query') and this makes it easier to create scalable code in the data layer. It gives thinner Asp.net core (web api) controller actions in the sample demo. First off, clone the solution :
git clone https://github.com/toreaurstadboss/CqrsDemoWebApi.git
The following Nuget packages are relevant for EF Core and MediatR.
 
 
    <PackageReference Include="Mediatr" Version="10.0.1" />
    <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1"> 
 
 
 
The Wiki of MediatR contains some example code here: https://github.com/jbogard/MediatR/wiki We will first look into the Request /Reponse messages, dispatched to a single handler. These are either comands or queries in this sample. Let us first consider a query that returns a list of books. Here is how it is implemented :
 
 
using CqrsDemoWebApi.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;

namespace CqrsDemoWebApi.Features.Books.Query
{
    public class GetBooks
    {

        public class Query : IRequest<IEnumerable<Book>>
        {

        }

        public class QueryHandler : IRequestHandler<Query, IEnumerable<Book>>
        {
            private readonly BooksDbContext _db;

            public QueryHandler(BooksDbContext db)
            {
                _db = db;
            }

            public async Task<IEnumerable<Book>> Handle(Query request, CancellationToken cancellationToken)
            {
                return await _db.Books.ToListAsync(cancellationToken); 
            }
        }
    }
}
 
 
We see that we are supposed to handle the query async since we must return a Task of TResponse (although we could use Task.FromResult here if we wanted to return it synchronously..) where the TRequest is of type IRequest. Note that Query only implements IRequest of the return type and has no properties (i.e. 'request parameters') since we return all books here and need no such parameters. We inject the DbContext here via dependency injection. In Program.cs of the .NET 6 solution we add MediatR using the builder.Services.AddMediatR() method:
 
  Program.cs (.NET 6 Web Api project)

using CqrsDemoWebApi.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddMediatR(Assembly.GetExecutingAssembly()); //adding MediatR support here. 
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var connectionString = new ConnectionString(builder.Configuration.GetConnectionString("CqrsDemoBooksDb")); 
builder.Services.AddSingleton(connectionString);

builder.Services.AddDbContext<BooksDbContext>(options =>
options.UseSqlServer(connectionString.Value)); 

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
 
Nothing special here, just basic setup of a standard web api in Net 6 here, using the AddMediatR method pointing to the assembly where our contracts reside (commands and queries and their respective handlers). The BookController web api controller looks like this :
 

using CqrsDemoWebApi.Database;
using CqrsDemoWebApi.Features.Books;
using CqrsDemoWebApi.Features.Books.Command;
using CqrsDemoWebApi.Features.Books.Query;
using MediatR;
using Microsoft.AspNetCore.Mvc;

namespace CqrsDemoWebApi.Controllers
{

    [Route("api/[controller]")]
    public class BookController : ControllerBase
    {
        private readonly IMediator _mediator;

        public BookController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        public async Task<IEnumerable<Book>> GetBooks() 
        {
            var books = await _mediator.Send(new GetBooks.Query());
            return books; 
        }

        [HttpGet("{id}")]
        public async Task<Book> GetBook(int id)
        {
            var book = await _mediator.Send(new GetBooksById.Query { Id = id });
            return book; 
        }

        [HttpPost]
        public async Task<ActionResult> AddBook([FromBody] AddNewBook.Command command)
        {
            var createdBookId = await _mediator.Send(command);
            return CreatedAtAction(nameof(GetBook), new { id = createdBookId }, null); 
        }

        [HttpDelete("{id}")]
        public async Task<ActionResult> DeleteBook(int id)
        {
            await _mediator.Send(new DeleteCommand.Command { Id = id });
            return NoContent(); 
        }

    }
}

 
 
As we see, we inject the IMediator here and then use the Dependency Injected field to send the command of query instance. Note the use of [FromBody] in Post requests to send the POST body into the handler for the Command. Each query or command is implemented as nested classes where Query and QueryHandler is nested inside, implement IRequest of T and IRequestHandler of T (and return type U back again if we want a return result). If we do not
want a return result 'void' - we use the special type Unit. This signals that we do not return any results from the command or the query. We can return an int value for the Command after we have inserted a row. It is okay in CQRS to return a resource identifier. CQRS allows also return results from command as long as it is a resource identifier to use to look up the data via a query. (it could be Guid etc too if that is suitable). Here is a example of a command that creates a book:
 
 
 using CqrsDemoWebApi.Database;
using MediatR;

namespace CqrsDemoWebApi.Features.Books.Command
{
    public class AddNewBook
    {
        public class Command : IRequest<int>
        {
            public string? Title { get; set; }
            public string? Author { get; set; }
            public int Year { get; set; }
            public string? ImageLink { get; set; }
            public int Pages { get; set; }
            public string? Country { get; set; }
            public string? Language { get; set; }
            public string? Link { get; set; }
        }

        public class CommandHandler : IRequestHandler<Command, int>
        {
            private readonly BooksDbContext _booksDbContext;

            public CommandHandler(BooksDbContext booksDbContext)
            {
                _booksDbContext = booksDbContext;
            }

            public async Task<int> Handle(Command request, CancellationToken cancellationToken)
            {
                var book = new Book
                {
                    Title = request.Title,
                    Author = request.Author,    
                    Year = request.Year,
                    ImageLink = request.ImageLink,
                    Pages = request.Pages,
                    Country = request.Country,
                    Language = request.Language,
                    Link = request.Link
                };

                await _booksDbContext.Books.AddAsync(book, cancellationToken);
                _booksDbContext.SaveChanges();
                return book.BookId;                          
            }
        }

    }
}
 
 
A sample json body for the POST is then:
 
 
 {
   "title": "Mythical Man Month",
   "author": "Frederick Brooks",
   "year": 1975,
   "country": "USA",
   "imagelink": "",
   "pages": 222
  }
 
 

We return the BookId of the new book as we impement IRequestHandler<int> . It is the auto generated id after saving the new book. This database is seeded by the way via a json file like this :
 
 
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;

namespace CqrsDemoWebApi.Database.Seed
{
    public static class ModelBuilderExtensions
    {
        public static void SeedBooks(this ModelBuilder modelBuilder, string contentRootPath)
        {
            string jsonInput = File.ReadAllText(Path.Combine(contentRootPath, @"Database/Seed/books.json")); 
            var books = JsonConvert.DeserializeObject<Book[]>(jsonInput);

            int bookId = 0;
            foreach (var book in books!)
            {
                bookId++;
                book.BookId = bookId; 
            }
           
            modelBuilder.Entity<Book>().HasData(books);
            modelBuilder.Entity<Book>().HasKey(b => b.BookId);
            modelBuilder.Entity<Book>().Property(b => b.BookId).ValueGeneratedOnAdd();                 
        }
    }
}
 
 
And we have the DbContext like this:
 
 
 using CqrsDemoWebApi.Database.Seed;
using Microsoft.EntityFrameworkCore;

namespace CqrsDemoWebApi.Database
{
    public class BooksDbContext : DbContext
    {
        private readonly IWebHostEnvironment _hostingEnvironment;

        public BooksDbContext(DbContextOptions<BooksDbContext> options,
            IWebHostEnvironment hostingEnvironment) : base(options) 
        {
           
            _hostingEnvironment = hostingEnvironment;
        }

        public DbSet<Book> Books { get; set; } = null!;  

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            string contentRootPath = _hostingEnvironment.ContentRootPath;
            modelBuilder.SeedBooks(contentRootPath); 
        }
    }
}
 
 
Now, the naming of each nested command/command handler or query/queryhandler is given a generic name. It could be that you want to make this a more specific name, but the wrapping class is a good enough specifier for this. I.e. GetBooks.Query() or AddNewBook.Command (we use [FromBody] here and there do not new up an instance like the Query) are specified enough allowing us to use generic nested class names.. MediatR simplifies the goal of implementing CQRS and following the mediator pattern. There are more possibilities with MediatR. Look at videos on Youtube for this topic for example.

Tuesday, 5 July 2022

UnitOfWork pattern in Entity Framework

This article will present code regarding the 'Unit of Work' pattern in Entity Framework, EF. I will also show updated code for a generic repository pattern of the same code. Note that the code in this article is very general and can be run against your DbContext regardless of how it looks. It is solved general, using generics and some usage of reflection. Note that this code goes against DbContext and is written in a solution that uses Entity Framework 6.4.4. However, the code here should work also in newer EF versions, that is - EF Core versions - such as EF Core 6, as the code is very general and EF Core also got the similar structure at least concerning the code shown here.. First off, the code for the interface IUnitOfWork looks like this:
 
 IUnitOfWork.cs

using SomeAcme.Common.Interfaces;

namespace SomeAcme.Data.EntityFramework.Managers
{
    public interface IUnitOfWork
    {
        UnitOfWork AddRepository<T>() where T : class;
         UnitOfWork AddCustomRepository<T>() where T : class;
        int Complete();
        void Dispose();
        UnitOfWork RemoveRepository<T>() where T : class;
        IRepository<T> Repository<T>() where T : class;
    }
}
 
The method Complete is important, as it will commit the transaction and perform changes to the database which the UnitOfWork implementation will work against. The code for UnitWork implementation looks like this:
 
 UnitOfWork.cs

using SomeAcme.Common.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SomeAcme.Data.EntityFramework.Managers
{

    public class UnitOfWork : IUnitOfWork, IDisposable
    {

        private readonly System.Data.Entity.DbContext _dbContext;

        public UnitOfWork(System.Data.Entity.DbContext dbContext)
        {
            _dbContext = dbContext;
            _repositories = new Dictionary<Type, object>();
        }

        public UnitOfWork AddRepository<T>() where T : class
        {
            if (!_repositories.ContainsKey(typeof(T)))
            {
                var repoObj = Activator.CreateInstance(typeof(Repository<T>), _dbContext);
                Repository<T> repo = repoObj as Repository<T>;
                if (repo == null)
                {
                    throw new ArgumentNullException($"Could not instantiate repository of type {typeof(T).Name}");
                }
                _repositories[typeof(T)] = repo;
            }
            return this;
        }

        public UnitOfWork AddCustomRepository<T>() where T : class
        {
            if (!_repositories.ContainsKey(typeof(T)))
            {
                bool checkImpementationPassesGenericInterfaceCheck = typeof(T).GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRepository<>));
                if (!checkImpementationPassesGenericInterfaceCheck)
                {
                    throw new ArgumentException($"The type {typeof(T).Name} must implement IRepository<T> to be added as a custom repository");
                }
                var repoObj = Activator.CreateInstance(typeof(T), _dbContext);

                if (repoObj == null)
                {
                    throw new ArgumentNullException($"Could not instantiate repository of type {typeof(T).Name}");
                }
                _repositories[typeof(T)] = repoObj;
            }
            return this;
        }

        public UnitOfWork RemoveRepository<T>() where T : class
        {
            if (_repositories.ContainsKey(typeof(T)))
            {
                _repositories.Remove(typeof(T));
            }
            return this;
        }

        public IRepository<T> Repository<T>() where T : class
        {
            //find suitable repo - possibly a custom repo ..
            IRepository<T> repoFound = null; 

            foreach (var item in _repositories)
            {
                if (item.Key == typeof(T))
                {
                    repoFound = _repositories[typeof(T)] as IRepository<T>;
                    break; 
                }
                bool checkImpementationPassesGenericInterfaceCheck = item.Key.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRepository<>));
                if (checkImpementationPassesGenericInterfaceCheck)
                {
                    //the repo implements IRepository<T> - this is the one to use
                    repoFound = item.Value as IRepository<T>;
                    break;
                }
            }
            if (repoFound == null)
            {
                throw new ArgumentNullException($"Could not retrieve repositiory defined inside the UnitOfWork for entity of type: {typeof(T).Name}. Is it registered into the UnitOfWork. Use method 'AddRepository'");
            }
            return repoFound; 
        }

        private readonly IDictionary<Type, object> _repositories;

        public int Complete()
        {
            if (_dbContext == null)
            {
                throw new ArgumentNullException($"The db context object of the UnitOfWork class is null, cannot complete the UnitOfWork as the db context is not initialized! No changes was performed in DB !"); 
            }
            int numStateEntriesWritten = _dbContext.SaveChanges();
            return numStateEntriesWritten;
        }

        public void Dispose()
        {
            _dbContext?.Dispose(); //dispose the passed in _shared_ db context instead of 
            //disposing the db context inside each repository to dispose only once.. 
            GC.SuppressFinalize(this);
        }

    }
}

 
Note - this implementation focuses on being able to define repositories to the UnitOfWork before running the unit work in case you want to be able to specify which tables / entities got repositories in the db context. Many other implementations will specify the repositories to expose in the UnitOfWork via property getters for example. This will make it easier to implement such a UnitOfWork implementation without this ability to expanding which repositories the UnitOfWork is supporting. Also note that in case you want to add a custom repository that implements IRepository<T> you need to cast into that custom repository when you call the 'Repository' method. A closed abstraction instead, where you do not add repositories like in this code may be more desirable. But perhaps there are scenarios where you want to add repositories that can take part in the UnitOfWork dynamically as shown here. The downside is that you need to initialize the UnitOfWork in addition to performing the database 'steps' before calling the 'Complete' method. What is the most practical UnitOfWork implementation in many cases most probably will be a closed abstraction where you specify which repositories the UnitOfWork supports and avoid having to add repositories like shown here. I did this as an academic exercise to see if such an implementation was possible. The unit tests passes and it looks okay to implement. Note though that you should probably have some default initialization here, i.e. in the constructor of UnitOfWork specify some default repositories and consider if you want to allow adding or removing repositories in the UnitOfWork class. Also, removing repositories could be considered an anti-pattern, so you could disallow this - only allowing adding custom repositories implementating the IRepository<T> in addition to listing up some implemented repositories. Important - always just pass in ONE db context. Each repository must use the same db context instance so the change tracking works as expected. Also note - the UnitOfWork and repositories implement IDisposable. When UnitOfWork goes out of scope, the db context is disposed. In case you want to add the UnitOfWork as a service in a DI container, remember to set up a scoped instance so it will be disposed. In case you use Singleton - this will cause the db connection to hang around.. the Dispose method is exposed as a public method anyways and can be called to dispose on-demand.. Finally, here are some tests that passes testing out the UnitOfWork together with the generic repository pattern !

 
        [Test] 
        public void UnitOfWorkPerformsExpected()
        {
            var dbContext = GetContext();
            var unitOfWork = new UnitOfWork(dbContext);
     
            unitOfWork.AddRepository<OperationExternalEquipment>()
                .AddRepository<OperationDiagnoseCode>();

            var operationExternalEquipment = new OperationExternalEquipment
            {
                OperationId = 10296,
                EquipmentText = "Stent graft type ABC-123",
                OrderedDate = DateTime.Today
            };

            var operationDiagnoseCode = new OperationDiagnoseCode
            {
                OperationId = 10296,
                DiagnoseCodeId = "A09.9",
                IsCodePreFabricated = true                
            };
            
            //act 
            unitOfWork.Repository<OperationExternalEquipment>().Add(operationExternalEquipment);
            unitOfWork.Repository<OperationDiagnoseCode>().Add(operationDiagnoseCode);
            int savedResult = unitOfWork.Complete();

            savedResult.Should().Be(2); 

            //assert 
            var savedOperationEquipmentsForOperation = unitOfWork.Repository<OperationExternalEquipment>().Find(x => x.OperationId == 10296).ToList(); 
            savedOperationEquipmentsForOperation.Any(x => x.EquipmentText == "Stent graft type ABC-123").Should().BeTrue();

            var savedOperationDiagnoseCodesForOperation = unitOfWork.Repository<OperationDiagnoseCode>().Find(x => x.OperationId == 10296).ToList();
            savedOperationDiagnoseCodesForOperation.Any(x => x?.DiagnoseCodeId == "A09.9").Should().BeTrue();

            //cleanup
            unitOfWork.Repository<OperationExternalEquipment>().Remove(operationExternalEquipment);
            unitOfWork.Repository<OperationDiagnoseCode>().Remove(operationDiagnoseCode);
            savedResult = unitOfWork.Complete();

            savedResult.Should().Be(2);

        }

        [Test]
        public void UnitOfWorkCustomRepoPerformsExpected()
        {
            var dbContext = GetContext();
            var unitOfWork = new UnitOfWork(dbContext);
            unitOfWork.AddCustomRepository<OperationExternalEquipmentCustomRepo>();
            var operationExternalEquipment = new OperationExternalEquipment
            {
                OperationId = 10296,
                EquipmentText = "Stent graft type DEF-456",
                OrderedDate = DateTime.Today
            };         

            //act 
            unitOfWork.Repository<OperationExternalEquipment>().Add(operationExternalEquipment);
       
            int savedResult = unitOfWork.Complete();

            savedResult.Should().Be(1);

            //assert 
            var savedOperationEquipmentsForOperation = unitOfWork.Repository<OperationExternalEquipment>().Find(x => x.OperationId == 10296).ToList(); 
            savedOperationEquipmentsForOperation.Any(x => x.EquipmentText == "Stent graft type DEF-456").Should().BeTrue();

            //check if we can use a custom repo method !
            var equipmentRepo = unitOfWork.Repository<OperationExternalEquipment>() as OperationExternalEquipmentCustomRepo;
            var equipmentTexts = string.Join(",", equipmentRepo.GetEquipmentTexts(10296));
            bool foundText = equipmentTexts.Contains("Stent graft type DEF-456"); 
            foundText.Should().BeTrue();

            //cleanup
            unitOfWork.Repository<OperationExternalEquipment>().Remove(operationExternalEquipment);
            savedResult = unitOfWork.Complete();

            savedResult.Should().Be(1);

        }
 
 
The adjusted implementation of Repository now looks like this - I have renamed many of the methods to be more standard compared to other implementations of the repository pattern demonstrated online in different videos on Youtube for example. The updated interface for repository now looks like this:
 
  IRepository.cs
  
  using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace SomeAcme.Common.Interfaces
{

    /// <summary>
    /// Generic implementation of repository pattern for (should maybe have been implemented a decade ago to save some development time .. :-) to save some code in DAL-layer Data.EntityFramework ) 
    /// </summary>
    /// <typeparam name="T">Entity T (POCO for table)</typeparam>
    public interface IRepository<T> where T : class
    {

        /// <summary>
        /// Performs an insert of an entity
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        /// <param name="keyValues">If set, these key values are used to locate entity in db after the insertion has been performed if specifed by other param for saveImmediate</param>
        /// <param name="saveImmediate">Save immediately in db after adding the entity</param>
        T Add(T entity, bool saveImmediate = false);

        /// <summary>
        /// Performs an insert of multiple entities
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        /// <param name="saveImmediate">Save immediately after adding item in db</param>
        IEnumerable<T> AddRange(IEnumerable<T> entity, bool saveImmediate = false);

        /// <summary>
        /// Saves changes. Commits the data to the database.
        /// </summary>
        /// <param name="dbContext">Db context</param>
        void SaveChanges(object dbContext);

        /// <summary>
        /// Delete an entity specified by <paramref name="keyValues"/> to look up entity
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        T Remove(bool saveImmediate, params object[] keyValues);

        /// <summary>
        /// Deletes an entity specified by <paramref name="entity"/> from the database
        /// </summary>
        /// <param name="entity"></param>
        void Remove(T entity, bool saveImmediate = false);

        /// <summary>
        /// Removes entities specified by <paramref name="entities"/> from the database
        /// </summary>
        /// <param name="entities"></param>
        void RemoveRange(IEnumerable<T> entities, bool saveImmediate = false);

        /// <summary>
        /// Update <paramref name="entity"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        /// <param name="saveImmediate">Save immediate if set to true</param>
        /// <returns></returns>
        T Update(T entity, bool saveImmediate, params object[] keyValues);

        /// <summary>
        /// Equivalent to a 'GetById' method, but tailored for generic use. 
        /// Retrieves <paramref name="idSelector"/> specified by <paramref name="idValue"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="keyValues">Key values to use to find entity</param>
        T Get(bool asNoTracking = true, params object[] keyValues);

        /// <summary>
        /// Retrieves entities of type <typeparamref name="T"/> via predicate <paramref name="condition"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="condition"></param>
        /// <returns></returns>
        /// <param name="asNoTracking">If true, does not track items (less chance of db locks due to turning off change tracking) </param>
        IEnumerable<T> Find(Expression<Func<T, bool>> condition, bool asNoTracking = true);

        /// <summary>
        /// Retrieves an entity of type <typeparamref name="T"/> via predicate <paramref name="condition"/>.
        /// If not found, null is returned.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="condition"></param>
        /// <returns></returns>
        T GetByCondition(Expression<Func<T, bool>> condition);

        /// <summary>
        /// Retrieve all the entities specified by <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <param name="asNoTracking">If true, does not track items (less chance of db locks due to turning off change tracking) </param>
        IEnumerable<T> GetAll(bool asNoTracking = true); 

    }
}

 

And the implementation looks like this:
 
 
 using SomeAcme.Common.Interfaces;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace SomeAcme.Data.EntityFramework.Managers
{
    public class Repository<T> : IRepository<T>, IDisposable where T : class
    {
        private readonly System.Data.Entity.DbContext _dbContext;

        public Repository(System.Data.Entity.DbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public T Add(T entity, bool saveImmediate = false)
        {
            return ExecuteQuery((T obj, System.Data.Entity.DbContext dbContext) =>
            {
                var entityDb = dbContext.Set<T>().Add(entity);                
                if (saveImmediate)
                    SaveChanges(dbContext);
                return entityDb;

            }, entity);
        }

        public T Add(T entity, bool saveImmediate = false, params object[] keys)
        {
            return ExecuteQuery((T obj, System.Data.Entity.DbContext dbContext) =>
            {
                dbContext.Entry(obj).State = EntityState.Added;
                if (saveImmediate)
                    SaveChanges(dbContext);
                var entityInDb = dbContext.Set<T>().Find(keys);
                return entityInDb;
            }, entity);
        }

        public IEnumerable<T> AddRange(IEnumerable<T> entities, bool saveImmediate)
        {
            var entitites = _dbContext.Set<T>().AddRange(entities);
            if (saveImmediate)
                SaveChanges(_dbContext);
            return entitites;
        }

        public T Remove(bool saveImmediate = false, params object[] keyValues)
        {
            var entity = _dbContext.Set<T>().Find(keyValues);
            if (entity == null)
                return null;
            var entry = _dbContext.Entry(entity);
            if (entry == null)
                return null;
            entry.State = EntityState.Deleted;
            if (saveImmediate)
                SaveChanges(_dbContext);
            return entity;
        }

        public void Remove(T entity, bool saveImmediate = false)
        {
            _dbContext.Set<T>().Remove(entity);
            if (saveImmediate)
                SaveChanges(_dbContext);
        }

        public void RemoveRange(IEnumerable<T> entities, bool saveImmediate = false)
        {
            _dbContext.Set<T>().RemoveRange(entities);
            if (saveImmediate)
                SaveChanges(_dbContext);
        }

        /// <summary>
        /// Note - requiring here that we have defined primary key(s) on the target tables ! 
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        public T Get(params object[] keyValues)
        {
            var entity = _dbContext.Set<T>().Find(keyValues);
            _dbContext.Entry(entity).State = EntityState.Detached;
            return entity;
        }

        public IEnumerable<T> GetAll(bool asNoTracking = true)
        {
            return asNoTracking ? _dbContext.Set<T>().AsNoTracking() : _dbContext.Set<T>();
        }

        public IEnumerable<T> Find(Expression<Func<T, bool>> condition, bool asNoTracking = true)
        {
            IQueryable<T> query = asNoTracking ? _dbContext.Set<T>().AsNoTracking() : _dbContext.Set<T>();
            var entities = query.Where(condition);
            return entities;
        }

        public T GetByCondition(Expression<Func<T, bool>> condition)
        {
            IQueryable<T> query = _dbContext.Set<T>().AsNoTracking();
            var entities = query.Where(condition);
            return entities.FirstOrDefault();
        }

        public bool ExistsByCondition(Expression<Func<T, bool>> condition)
        {
            IQueryable<T> query = _dbContext.Set<T>().AsNoTracking();
            return query.Any(condition);

        }

        public T Get(bool asNoTracking, params object[] keyValues)
        {
            var entity = asNoTracking ? _dbContext.Set<T>().AsNoTracking().FirstOrDefault() : _dbContext.Set<T>().Find(keyValues);
            return entity;
        }

        public void SaveChanges(object context)
        {
            var dbContext = context as System.Data.Entity.DbContext;
            if (dbContext == null)
            {
                throw new ArgumentException($"dbContext object inside save method : Must be of type System.Data.Entity.DbContext", nameof(context));
            }
            dbContext.SaveChanges(); 
        }

        public T Update(T entity, bool saveImmediate = false, params object[] keyValues)
        {
            return ExecuteQuery((T obj, System.Data.Entity.DbContext dbContext) =>
            {
                var entityInDb = dbContext.Set<T>().Find(keyValues);
                if (entityInDb == null)
                    return null; 
                dbContext.Entry(entityInDb).CurrentValues.SetValues(obj);                
                if (saveImmediate)
                {
                    SaveChanges(dbContext);
                }
                return obj;
            }, entity);            
        }

        private T ExecuteQuery(Func<T, System.Data.Entity.DbContext, T> query, T entity)
        {
            T result = query(entity, _dbContext);
            return result;
        }

        public void Dispose()
        {
            Dispose(true); 
        }

        private void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                _dbContext?.Dispose();
                GC.SuppressFinalize(this);
            }
        }

    }
}

 
 
Totally, the code of the unit of work and repository pattern is about 300 lines of code combined. It should match a lot of Data Access Layer (DAL) implementations of Entity Framework, possible we could reduce a lot of code here in many projects by following these patterns which are accepted data access patterns defined by the 'Gang of four' way back many decades ago. If I would adjust this code next I would do these modifications :
  • UnitOfWork should only allow adding custom repos in addition to some predefines repos. This should give an overall simplification of UnitOfWork
  • Specific transaction handling should be added, like setting the transaction isolation scope. Also speifically doing rollback in case anything crashes in UnitOfWork
  • Possible add some more shared utility methods inside Repository class if such should be added

Saturday, 25 June 2022

Generic Singleton that allows initing a type T once

I have looked into a simple design pattern today and decided to have a go with a generic implementation of this. The class is sealed with a private constructor. It will for a generic pattern of type T instantiate a 'Singleton', i.e. same objects will be returned via the static Instance property for a given type T. It is also possible to 'Init' to a custom object in case you want to set the Singleton object to a custom inited object of type T. If you skip initing call - this will just use the parameterless
constructor. But you can only do a call to Init once (i.e. call the Init method) , so do this initing only in the application startup code for example. Of course, we could instead create a more distilled version, disallowing 'Initing' singleton objects not allowing sending in an adjusted instance of type T as the inited singleton - and also Singleton is considered a pattern which should be implemented per class and not in a generic manner like this. Also, it violates the Single responsibility principle
as the object of type T has not control of how in instantiates itself. But Singleton pattern is an accepted creational pattern.
 

 public sealed class Singleton<T> where T : class, new()
    {
        private static Lazy<T> InstanceProxy
        {
            get
            {
                if (_instanceObj?.IsValueCreated != true)
                {
                    _instanceObj = new Lazy<T>(() => new T());
                }
                return _instanceObj;
            }
        }

        private static Lazy<T>? _instanceObj;


        public static T Instance { get { return InstanceProxy.Value; } } 

        public static void Init(Lazy<T> instance)
        {
            if (_instanceObj?.IsValueCreated == true)
            {
                throw new ArgumentException($"A Singleton for the type <T> is already set"); 
            }
            _instanceObj = instance ?? throw new ArgumentNullException(nameof(instance)); 
        }

        private Singleton()
        {   
        }
    }
 
You can use it like this : Some model class :
 

public class Aeroplane
{
    public string? Model { get; set; }
    public string? Manufacturer { get; set; }
    public int YearBuilt { get; set; }
    public int PassengerCount { get; set; }
}
 
Usage sample :


var aeroplane = new Aeroplane
{
    Manufacturer = "Boeing",
    Model = "747",
    PassengerCount = 350,
    YearBuilt = 2005
};

var aeroPlane3 = Singleton<Aeroplane>.Instance;
var aeroPlane4 = Singleton<Aeroplane>.Instance;

Console.WriteLine($"Aeroplane3 and aeroplane4 is same object? {Object.ReferenceEquals(aeroPlane3, aeroPlane4)}");
 
Outputs 'true'. Trying to re-init type T Singleton to another object fails :
 
var aeroplane2 = new Aeroplane
{
    Manufacturer = "Sopwith Aviation Company",
    Model = "Sophwith Camel",
    PassengerCount = 1,
    YearBuilt = 1917
};

Singleton<Aeroplane>.Init(new Lazy<Aeroplane>(aeroplane2));
 
You can of course just access the Singleton with initing it - as mentioned it will call the default public constructor of type T and set an this 'default' instance of T as the singleton for type T. Possible you could have a way of setting a custom constructor here instead of passing an object as a sort of improved 'factory pattern'. We could for example in the Init method specify which constructor method
to call in the initing and pass in parameters for example.
 
var aeroplaneDefaultInstantiated = Singleton<Aeroplane>.Instance; 
 
Note : Default instantiation - calls the parameterless public constructor of type T. So you must do the initialization inside the public parameterless constructor if you skip the Init method. We can also allow sending in a custom constructor to the Singleton class by offering another init method. Here, we can send in parameters of given types and therefore identify the constructor to call. This of course demands that such a constructor exists. It offers another way of setting a singleton object. We now can either set the singleton object for type T via :
  • An instance that calls the default parameterless constructor and sets this object as the singleton for type T
  • A customized instance in case you want to have more fine tuned initalized object to be set as the singleton for type T
  • An instance that calls a specified constructor of the type T and sets the created instance as the singleton for type T
Sample I tested this out with (dump method calls below are done inside Linqpad 7 - so paste this code into there) :
 
void Main()
{
	Singleton<Aeroplane>.Init(new object[] { "Nieuport IV", 1911 });
	//Singleton<Aeroplane>.Init(new object[] { "Nieuport V", 1914 });

	var aeroplaneTwo = Singleton<Aeroplane>.Instance;
	var aeroplaneThree = Singleton<Aeroplane>.Instance;
	Object.ReferenceEquals(aeroplaneTwo, aeroplaneThree).Dump();
	aeroplaneTwo.Dump();
	aeroplaneThree.Dump();
}

public class Aeroplane
{
	public string? Model { get; set; }
	public string? Manufacturer { get; set; }
	public int YearBuilt { get; set; }
	public int PassengerCount { get; set; }
	
	public Aeroplane()
	{
			
	}
	
	public Aeroplane(string model, int yearBuilt)
	{
		Model = model; 
		YearBuilt = yearBuilt; 
		if (YearBuilt < 1913) {
			PassengerCount = 1; 
		}
	}
}


public sealed class Singleton<T> where T : class, new()
{
	private static Lazy<T> InstanceProxy
	{
		get
		{
			if (_instanceObj?.IsValueCreated != true)
			{
				_instanceObj = new Lazy<T>(() => new T());
			}
			return _instanceObj;
		}
	}

	private static Lazy<T>? _instanceObj;

	public static void Init(object[] constructorParams)
	{
		if (_instanceObj?.IsValueCreated == true)
		{
			throw new ArgumentException($"A Singleton for the type <{typeof(T).Name}> is already set");
		}
		var constructor = typeof(T).GetConstructor(constructorParams.Select(p => p.GetType()).ToArray());
		if (constructor == null)
		{
			string typenamesParams = string.Join(",", constructorParams.Select(p => p.GetType()));
			throw new ArgumentException($"Could not find a constructor of type {typeof(T).Name} with the parameter types {typenamesParams}"); 
		}
		var instanceObj = constructor.Invoke(constructorParams); 
		_instanceObj = new Lazy<T>(instanceObj as T); 

	}


	public static T Instance { get { return InstanceProxy.Value; } }

	public static void Init(Lazy<T> instance)
	{
		if (_instanceObj?.IsValueCreated == true)
		{
			throw new ArgumentException($"A Singleton for the type <T> is already set");
		}
		_instanceObj = instance ?? throw new ArgumentNullException(nameof(instance));
	}

	private Singleton()
	{
	}
}

 
Once more, we disallow calling the Init method many times, here we call a specific constructor to init as the Singleton object.

Monday, 6 June 2022

Repository pattern in Entity Framework Core

I have been looking into repository pattern for EF Core today. This is what I got :
 
 
using SomeAcme.Interfaces;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace SomeAcme.DAL.Pattern
{
    public class Repository<T> : IRepository<T> where T : class
    {

        private System.Data.Entity.DbContext GetContext()
        {
            return new SomeAcmeDbContext as System.Data.Entity.DbContext;
        }

        private T ExecuteQuery(Func<T, System.Data.Entity.DbContext, T> query, T entity)
        {
            using (var context = GetContext())
            {
                T result = query(entity, context);
                return result;
            }
        }

        public T Add(T entity, bool saveImmediate = false)
        {
            return ExecuteQuery((T obj, System.Data.Entity.DbContext dbContext) =>
            {
                var entityDb = dbContext.Set<T>().Add(entity);                
                if (saveImmediate)
                    SaveChanges(dbContext);
                return entityDb;

            }, entity);
        }

        public T Add(T entity, bool saveImmediate = false, params object[] keys)
        {
            return ExecuteQuery((T obj, System.Data.Entity.DbContext dbContext) =>
            {
                dbContext.Entry(obj).State = EntityState.Added;
                if (saveImmediate)
                    SaveChanges(dbContext);
                var entityInDb = dbContext.Set<T>().Find(keys);
                return entityInDb;
            }, entity);
        }

        public IEnumerable<T> AddRange(IEnumerable<T> entities, bool saveImmediate)
        {
            using (var dbContext = GetContext())
            {
                var entitites = dbContext.Set<T>().AddRange(entities);
                if (saveImmediate)
                    SaveChanges(dbContext);
                return entitites;
            }
        }

        public T Delete(bool saveImmediate = false, params object[] keyValues)
        {
            using (var dbContext = GetContext())
            {
                var entity = dbContext.Set<T>().Find(keyValues);
                if (entity == null)
                    return null;
                var entry = dbContext.Entry(entity);
                if (entry == null)
                    return null; 
                entry.State = EntityState.Deleted;
                if (saveImmediate)
                    SaveChanges(dbContext); 
                return entity; 
            }        
        }

        /// <summary>
        /// Note - requiring here that we have defined primary key(s) on the target tables ! 
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        public T Get(params object[] keyValues)
        {
            using (var dbContext = GetContext())
            {
                var entity = dbContext.Set<T>().Find(keyValues);
                dbContext.Entry(entity).State = EntityState.Detached; 
                return entity; 
            }      
        }

        public IList<T> GetAll(bool asNoTracking = true)
        {
            using (var dbContext = GetContext())
            {
                return asNoTracking ? dbContext.Set<T>().AsNoTracking().ToList() : dbContext.Set<T>().ToList();
            }           
        }

        public IList<T> GetAllByCondition(Expression<Func<T, bool>> condition, bool asNoTracking = true)
        {
            using (var dbContext = GetContext())
            {
                IQueryable<T> query = asNoTracking ? dbContext.Set<T>().AsNoTracking() : dbContext.Set<T>();
                var entities = query.Where(condition);
                return entities.ToList();
            }          
        }

        public T GetFirstByCondition(Expression<Func<T, bool>> condition)
        {
            return GetAllByCondition(condition).FirstOrDefault(); 
        }

        public T GetByKeyValues(bool asNoTracking, params object[] keyValues)
        {
            using (var dbContext = GetContext())
            {
                var entity = asNoTracking ? dbContext.Set<T>().AsNoTracking().FirstOrDefault() : dbContext.Set<T>().Find(keyValues);
                return entity;
            }
        }

        public void SaveChanges(object context)
        {
            var dbContext = context as System.Data.Entity.DbContext;
            if (dbContext == null)
            {
                throw new ArgumentException("Must be of type System.Data.Entity.DbContext", nameof(context));
            }
            dbContext.SaveChanges(); 
        }

        public T Update(T entity, bool saveImmediate = false, params object[] keyValues)
        {
            return ExecuteQuery((T obj, System.Data.Entity.DbContext dbContext) =>
            {
                var entityInDb = dbContext.Set<T>().Find(keyValues);
                if (entityInDb == null)
                    return null; 
                dbContext.Entry(entityInDb).CurrentValues.SetValues(obj);                
                  if (saveImmediate)
                {
                    SaveChanges(dbContext);
                }
                return obj;
            }, entity);            
        }

    }
}

 
 
 
And here are some unit tests against a database of mine (containing some integration tests)
 
using AutoMapper;
using FluentAssertions;
using SomeAcme.Common;
using SomeAcme.Common.DataContract;
using SomeAcme.Data.EntityFramework.Managers;
using SomeAcme.Data.EntityFramework.Models;
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;

namespace SomeAcme.Service.Implementation.Test.Pattern
{

    [TestFixture]
    public class RepositoryTest
    {
        private IMapper _mapper; 

        [SetUp]
        public void TestInitialize()
        {
            IntegrationTestBootstrapper.Run();
            System.Threading.Thread.CurrentPrincipal = new ConcreteClaimsPrincipal(SomeAcme.Administrator, "107455", "Testutvikler, Ivrig");

            var configuration = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap>PasSystem, PasSystemDataContract>();
            });

#if DEBUG
            // only during development, validate your mappings; remove it before release
            // configuration.AssertConfigurationIsValid();
#endif
            _mapper = configuration.CreateMapper(); 

        }

        [Test]
        public void GetAllReturnsExpected()
        {
            var pasSystemRepository = new Repository>PasSystem>();
            var allPasSystem = pasSystemRepository.GetAll()?.Select(x => _mapper.Map>PasSystemDataContract>(x)).ToList();
            allPasSystem?.Count().Should().BePositive();
        }

        [Test]
        public void GetReturnsExpected()
        {
            var jobTitleRepo = new Repository>Title>();
            var jobTitle = jobTitleRepo.Get(6);
            Assert.IsNotNull(jobTitle);
            jobTitle.Text.Should().Be("Anestesisykepleier"); 
        }

        [Test] 
        public void AddRangeDoesNotThrow()
        {
            var postponementCauseRepo = new Repository>PostponementCause>();
            var postponementCauses = new List>PostponementCause>()
            {
                new PostponementCause{ FreshOrganizationalUnitId = 107455, IsActive = true, Text = "Personel not available"},
                new PostponementCause{ FreshOrganizationalUnitId = 107455, IsActive = true, Text = "Personel already busy"},
            };
            postponementCauseRepo.AddRange(postponementCauses, true);
        }

        [Test]
        public void GetByConditionReturnsNonEmpty()
        {
            var timeMatrixRepository = new Repository>TimeMatrix>();
            var timematrices = timeMatrixRepository.GetAllByCondition(t => t.PreOperation > 80).ToList();
            timematrices.Count().Should().BePositive(); 
        }

        [Test]
        public void AddTimeMatrixToDbViaRepositorySucceeds()
        {
            var timeMatrixRepository = new Repository>TimeMatrix>();

            var timeMatrix = new TimeMatrix
            {
                Code = "T100",
                IsActive = false,
                PostOperation = 11,
                PreOperation = 11,
            };
            var timeMatrixSavedToDb = timeMatrixRepository.Add(timeMatrix, true);

            timeMatrixSavedToDb.TimeMatrixId.Should().BePositive(); 
        }

        [Test]
        public void AddAndUpdateTimeMatrixToDbViaRepositorySucceeds()
        {
            var timeMatrixRepository = new Repository>TimeMatrix>();

            var timeMatrix = new TimeMatrix
            {
                Code = "T100",
                IsActive = false,
                PostOperation = 11,
                PreOperation = 11,
            };
            var timeMatrixSavedToDb = timeMatrixRepository.Add(timeMatrix, true);
            timeMatrixSavedToDb.TimeMatrixId.Should().BePositive();
            timeMatrix.Code = "T200";
            timeMatrix.PreOperation = 11; 
            timeMatrixSavedToDb = timeMatrixRepository.Update(timeMatrix, true, timeMatrixSavedToDb.TimeMatrixId);
            timeMatrixSavedToDb.Code.Should().Be("T200"); 
            timeMatrix.PreOperation.Should().Be(11);    
        }

        [Test]
        public void AddAndUpdateAndDeleteTimeMatrixToDbViaRepositorySucceeds()
        {
            var timeMatrixRepository = new Repository>TimeMatrix>();

            var timeMatrix = new TimeMatrix
            {
                Code = "T100",
                IsActive = false,
                PostOperation = 11,
                PreOperation = 11,
            };
            var timeMatrixSavedToDb = timeMatrixRepository.Add(timeMatrix, true);
            timeMatrixSavedToDb.TimeMatrixId.Should().BePositive();
            timeMatrix.Code = "T300";
            timeMatrix.PreOperation = 11;
            timeMatrixSavedToDb = timeMatrixRepository.Update(timeMatrix, true, timeMatrixSavedToDb.TimeMatrixId);
            timeMatrixSavedToDb.Code.Should().Be("T300");
            timeMatrixSavedToDb.PreOperation.Should().Be(11);
            timeMatrixSavedToDb = timeMatrixRepository.Delete(true, timeMatrixSavedToDb.TimeMatrixId);
            timeMatrixSavedToDb.Should().NotBeNull(); 
        }

        [Test]
        public void AddPostOfficeToDbViaRepositorySucceeds()
        {
            var postOfficeRepository = new Repository>PostOffice>();

            var postOffice = new PostOffice
            {
                PostalPlace = "Steinkjer",
                PostalCode = "7724"
            };
            var postOfficeSavedToDb = postOfficeRepository.Add(postOffice, true);
            postOfficeSavedToDb.Should().NotBeNull();
        }


    }
}

 
Also note the usage of Auto mapper here to automatically map between POCO entity objects to DTO (Data transfer objects, usually data contracts for example). Building a useful repository pattern in EF Core and combining it with Automapper (available on nuget) will probably reduce your Data Access Layer logic a bit .. In many cases maybe an understatement.. Note - this code have passed unit tests, but not been used in production (yet). Methods that demands keyValues to find entities do require your table to have primary keys on the table. This still is 'demo code' and WIP (Work in progress). Works !

Saturday, 4 June 2022

Making use of extension methods to extract data from FHIR bundles

This article shows some extension methods to extract data from FHIR bundles, where FHIR stands for Fast Healthcare Interoperability Resources. The standard is used as a global or country specific standard with its own
variants. It is also a standard that allows for extensibility and its goal is to define interoperability and an information model definining resources which are then comprised of smaller elements which can define different kinds of information.
FHIR also defines API standards and is defined in different formats such as XML and json. We will look into some example extension methods for retrieving data deep inside a FHIR Bundle. A bundle is a top level data which got a lot of components in a hierarchical structure as we define the data in XML or JSON for example, i.e. a tree structure. Let us say that we want to retrieve data like medications a pasient is taking as his or her DDD (Defined Daily Dosage). We want to be able to find a medication statement inside our bundle and then retrieve the medication dosage quantity. We know the unit is measured in micrograms (ug) and the medication (drug) is called Fentanyl. Here is a property getter with logic to retrieve this value.
 
   public int? Fentanyl
   {
            get
            {
                var dosageQuantity = _bundle.SearchMedicationStatements("http://someacme.no/fhir/MedicationStatement/")
                    ?.GetMedicationDosageQuantity("Fentanyl", "ug");
                //value is already a decimal? data type and must be parsed 
                if (int.TryParse(dosageQuantity?.Value?.ToString(), out var dosageQuantityParsed))
                {
                    return dosageQuantityParsed;
                }
                return null;
            }
    }
 

We have these two extension methods to help us with retrieving the data :
 
      
        public static List<MedicationStatement>? SearchMedicationStatements(this Bundle bundle, string resourcePath)
        {
            var medicationStatementsMatching = bundle?.Entry?.Where(e => e.FullUrl.StartsWith(resourcePath))?.Select(m => m.Resource)?.OfType<MedicationStatement>()?.ToList();
            return medicationStatementsMatching;
        }

        public static Dosage? GetMedicationDosageDosage(this List<MedicationStatement> medicationStatements, string displayText)
        {
            //find dosage with given display text 

            foreach (var medicationStatement in medicationStatements)
            {
                var medicationStatementMedication = medicationStatement?.Medication as CodeableConcept; 
                if (medicationStatementMedication == null)
                {
                    continue; 
                }
                var medicationCoding = medicationStatementMedication?.Coding?.FirstOrDefault(med => med.Display?.Equals(displayText, StringComparison.InvariantCultureIgnoreCase) == true);  
                if (medicationCoding != null)
                {
                    var quantity = medicationStatement?.Dosage?.FirstOrDefault();
                    return quantity; 
                }           
            }

            return null;
        }
 
 
And our unit test will then be very simply with some static Fhir bundle data like this :
 
 
    [TestFixture]
    public class SomeAcmeManagerTests
    {
        private Bundle? _bundle = new();
        private SomeAcmeColoscopyDomainModel? _domainModel;

        [SetUp]
        public void TestInitialize()
        {
            _bundle = new FhirJsonParser().Parse<Bundle>(File.ReadAllText(@"TestData/JSON_someacme.json"));
            _domainModel = new SomeAcmeColoscopyDomainModel(_bundle, _metadataVersionId);
        }
 
        [Test]
        public void Map_Fhir_Bundle_To_Property_Fentanyl()
        {
            _domainModel?.Fentanyl.Should().Be(2);
        }
 
Now, we have used some classes here as you have seen called FhirJsonParser and MedicationStatements. These classes and functionality is available in some selected nuget packages :
 
  <PackageReference Include="Hl7.Fhir.R4" Version="4.0.0" />
  <PackageReference Include="Hl7.Fhir.Serialization" Version="4.0.0" />
  <PackageReference Include="Hl7.Fhir.Support" Version="4.0.0" />
  <PackageReference Include="Hl7.Fhir.Support.Poco" Version="4.0.0" />
 
This packages are licensed under BSD-3 license and are 'free' as long as you include the copyright notice. See this url for more info - it is the HL7 FHIR SDK for Microsoft .net platform. As you see we have additional nuget packages for (de)serialization and Poco objects. We also have common interfaces and classes in the HL7.Fhir.Support and HL7.Fhir.Poco nuget packages. This makes it way easier to work with a large bundle of Fhir data. Now, about sample data - this is often given in a .json file by the for example another organization, that you want to integrate with. FHIR is about interoperability and a common understanding of different health information systems via a common standard. The sample data starts with these data at the top - a bundle and a diagnosticreport. Of course, FHIR is a very large standard and which data you work against will vary a lot.
 
 {
  "resourceType": "Bundle",
  "meta": {
    "profile": [
      "http://someacmeregistries.no/fhir/StructureDefinition/colonoscopyreport-bundle-someacme"
    ]
  },
  "identifier": {
    "system": "http://someacmeotherorg.no/fhir/NamingSystem/colonoscopy-report-id",
    "value": "IdPlaceholder"
  },
  "type": "collection",
  "timestamp": "2022-05-10T10:26:40.6221425+02:00",
  "entry": [
    {
      "fullUrl": "http://somethirdacme.no/fhir/DiagnosticReport/c83b2f53-2d01-46e7-aed4-703396d5433f",
      "resource": {
        "resourceType": "DiagnosticReport",
        "meta": {
          "profile": [
            "http://someacmeregistries.no/fhir/StructureDefinition/colonoscopyreport-diagnosticreport-gastronet"
          ]
        },
        "status": "final",
        "code": {
          "coding": [
            {
              "system": "http://snomed.info/sct",
              "code": "73761001",
              "display": "Koloskopi"
            }
          ]
        },
        "subject": {
          "reference": "http://someacme.no/fhir/Patient/84c51e6c-020c-469b-b6c8-a6e9b2db6ff6"
        },
 
 
As you can see we also have something called Snomed SCT codes in our data. The meaning of these codes can be looked up online. International edition is here: https://browser.ihtsdotools.org/? Our medication drug statment for Fentanyl is defined further into the FHIR json bundle.


    {
      "fullUrl": "http://someacme.no/fhir/MedicationStatement/ae48d3bf-c289-4290-b7ed-78ef7bb6f1b5",
      "resource": {
        "resourceType": "MedicationStatement",
        "meta": {
          "profile": [
            "http://someame.no/fhir/StructureDefinition/colonoscopyreport-medicationstatement-gastronet"
          ]
        },
        "partOf": [
          {
            "reference": "http://someacme.no/fhir/Procedure/14f9f2e3-0d53-46c9-936f-3f0f85dd8cce"
          }
        ],
        "status": "active",
        "medicationCodeableConcept": {
          "coding": [
            {
              "system": "http://snomed.info/sct",
              "code": "373492002",
              "display": "Fentanyl"
            }
          ]
        },
        "subject": {
          "reference": "http://someacme.no/fhir/Patient/84c51e6c-020c-469b-b6c8-a6e9b2db6ff6"
        },
        "dosage": [
          {
            "doseAndRate": [
              {
                "doseQuantity": {
                  "value": 2,
                  "unit": "ug",
                  "system": "http://unitsofmeasure.org",
                  "code": "ug"
                }
              }
            ]
          }
        ]
      }
    },



As you can see, a FHIR json bundle will be quite lengthy and my sample file, which is a sample diagnostic report for coloscopy is right above 1000 lines and 33 kilobytes. To retrieve the medication dosage we need to go deep into the FHIR json structure sometimes and sometimes look at sibling nodes or further down. What helped me a lot while creating the extension methods I will mention next, was debugging and looking into the immediate window and inspect which kind of Entry it is. An Entry is a generic term which describes that our component is a general term in FHIR which can be many different types, such as a MedicationStatment and contain a Codable concept which ultimately will contain the dose and quantity of our medication (drug). What I did was the following to make working with the FHIR bundle in a more code friendly manner :

GENERIC APPROACH - Implement a mapping from a FHIR bundle into a DOMAIN MODEL which then can be used in your SYSTEM

  • Use the debugger and unit tests and explore in the immediate window which kind of Entry each component in the FHIR bundle is. Identity if we can cast an Entry into a correct subtype, such as a MedicationStatement. These POCO objects are available in the noted nuget packages above.
  • After finding a way to retrieve the data - generalize the retrival into extension methods, which can be chained and then make use of these extension methods into property getter logic.
  • Use a TDD approach to retrieve the data. Each property was found in the sample doc for me (about 100 properties) and I was trying to find generic ways to find these property values
  • Sometimes you need fine tuned logic too to find data. FHIR contains some extensions and different FHIR bundles, although it is a standard, may vary some.
Okay, here is all the extension methods I made for this case. I have masked the real organization names here. My approach can be used in many different scenarios for retrieving FHIR bundle data.
 
 
 
using Hl7.Fhir.Model;
using static Hl7.Fhir.Model.Observation;

namespace SomeAcme.FhirFacade.SomeProduct.HelperMethods
{

    /// <summary>
    /// Helper methods for Fhir bundle. Generic use helper methods.  
    /// </summary>
    public static class FhirHelperExtensions
    {


        public static string? SearchForPractitioner(this Bundle bundle, string procedurePath, string functionRole)
        {
            var performer = bundle.SearchForProcedure(procedurePath)?
                .Performer?.FirstOrDefault(p => p?.Function?.Coding?.FirstOrDefault()?.Display?.Equals(functionRole, StringComparison.InvariantCultureIgnoreCase) == true);
            return performer?.Function?.Coding?.FirstOrDefault()?.Code;
        }

        /// <summary>
        /// Looks up an identifier value (e.g. hpr number or similar) of practitioner
        /// </summary>
        /// <param name="practitioner"></param>
        /// <returns></returns>
        public static string? GetPractitionerIdentifierValue(this Practitioner practitioner)
        {
            return practitioner?.Identifier?.FirstOrDefault()?.Value; 
        }

        public static Organization? SearchForOrganization(this Bundle bundle, string resourcePath, bool startsWith = true)
        {
            if (startsWith)
            {
                var organizationMatching = bundle?.Entry.FirstOrDefault(e => e.FullUrl.StartsWith(resourcePath))?.Resource as Organization;
                return organizationMatching;
            }

            var organization = bundle?.FindEntry(resourcePath).FirstOrDefault()?.Resource as Organization;
            return organization; 
        }

        public static MedicationStatement? SearchMedicationStatement(this Bundle bundle, string resourcePath, bool startsWith = true)
        {
            if (startsWith)
            {
                var medicationStatementMatching = bundle?.Entry.FirstOrDefault(e => e.FullUrl.StartsWith(resourcePath))?.Resource as MedicationStatement;
                return medicationStatementMatching;
            }

            var medicationStatement = bundle?.FindEntry(resourcePath).FirstOrDefault()?.Resource as MedicationStatement;
            return medicationStatement;
        }

        public static List<MedicationStatement>? SearchMedicationStatements(this Bundle bundle, string resourcePath)
        {
            var medicationStatementsMatching = bundle?.Entry?.Where(e => e.FullUrl.StartsWith(resourcePath))?.Select(m => m.Resource)?.OfType<MedicationStatement>()?.ToList();
            return medicationStatementsMatching;
        }

        public static Dosage? GetMedicationDosageDosage(this List<MedicationStatement> medicationStatements, string displayText)
        {
            //find dosage with given display text 

            foreach (var medicationStatement in medicationStatements)
            {
                var medicationStatementMedication = medicationStatement?.Medication as CodeableConcept; 
                if (medicationStatementMedication == null)
                {
                    continue; 
                }
                var medicationCoding = medicationStatementMedication?.Coding?.FirstOrDefault(med => med.Display?.Equals(displayText, StringComparison.InvariantCultureIgnoreCase) == true);  
                if (medicationCoding != null)
                {
                    var quantity = medicationStatement?.Dosage?.FirstOrDefault();
                    return quantity; 
                }           
            }

            return null;
        }

        public static Quantity? GetMedicationDosageQuantity(this List<MedicationStatement> medicationStatements, string displayText, string? expectedUnitname = null)
        {
            //find quantity for dosage with given display text 

            foreach (var medicationStatement in medicationStatements)
            {
                var medicationStatementMedication = medicationStatement?.Medication as CodeableConcept;
                if (medicationStatementMedication == null)
                {
                    continue;
                }
                var medicationCoding = medicationStatementMedication?.Coding?.FirstOrDefault(med => med.Display?.Equals(displayText, StringComparison.InvariantCultureIgnoreCase) == true);
                if (medicationCoding != null)
                {
                    if (medicationStatement?.Dosage?.FirstOrDefault()?.DoseAndRate?.FirstOrDefault()?.Dose is Quantity quantity)
                    {
                        if (!string.IsNullOrWhiteSpace(expectedUnitname) && expectedUnitname.Equals(expectedUnitname, StringComparison.InvariantCultureIgnoreCase))
                        {
                            return quantity; 
                        }
                        return null; //found the right dosage - but the unit name does not agree 

                    }
                }
            }
            return null; 
        }


        public static string? GetOrganizationIdentifierValue(this Organization organization)
        {
            return organization?.Identifier?.FirstOrDefault()?.Value;   
        }

        public static Observation? SearchForObservation(this Bundle bundle, string resourcePath, bool startsWith = true)
        {
            if (startsWith)
            {
                var observationMatching = bundle?.Entry?.FirstOrDefault(e => e.FullUrl.StartsWith(resourcePath))?.Resource as Observation;
                return observationMatching;
            }

            var observation = bundle?.FindEntry(resourcePath).FirstOrDefault()?.Resource as Observation;
            return observation;
        }
     
        public static ComponentComponent? GetObservationComponent(this Observation observation, string observationComponentDisplayText)
        {
            //TODO : these observations is not the same as the observation in SomeProduct and must be additionally mapped (enums does not agree) 
            foreach (var observationComponent in observation.Component)
            {
                if (observationComponent?.Code?.Coding?.Any() != true)
                {
                    continue;
                }
                foreach (var observationEntry in observationComponent.Code.Coding)
                {
                    if (observationEntry?.Display.Contains(observationComponentDisplayText, StringComparison.InvariantCultureIgnoreCase) == true)
                    {
                        return observationComponent;
                    }
                }
            }
            return null;
        }

        public static string? GetObservationComponentCodeValue(this Observation observation, string observationComponentDisplayText)
        {
            //TODO : these observations is not the same as the observation in Gastronet and must be additionally mapped (enums does not agree) 
            foreach (var observationComponent in observation.Component)
            {
                if (observationComponent?.Code?.Coding?.Any() != true)
                {
                    continue; 
                }
                foreach (var observationEntry in observationComponent.Code.Coding)
                {
                    if (observationEntry?.Display.Contains(observationComponentDisplayText, StringComparison.InvariantCultureIgnoreCase) == true)
                    {
                        return observationEntry?.Code; 
                    }
                }                
            }

            return null; 
        }

        public static Patient? SearchForPatient(this Bundle bundle, string resourcePath, bool startsWith = true)
        {
            if (startsWith)
            {
                var patientMatching = bundle?.Entry?.FirstOrDefault(e => e.FullUrl.StartsWith(resourcePath))?.Resource as Patient;
                return patientMatching;
            }
            var patient = bundle?.FindEntry(resourcePath).FirstOrDefault()?.Resource as Patient;
            return patient;
        }

        public static CodeableConcept? SearchForProcedureReason(this Procedure procedure, string code)
        {
            return procedure?.ReasonCode?.FirstOrDefault(c => c?.Coding?.FirstOrDefault()?.Code?.Equals(code, StringComparison.InvariantCultureIgnoreCase) == true);
        }

        public static CodeableConcept? SearchForProcedureReasonViaDisplay(this Procedure procedure, string display)
        {
            return procedure?.ReasonCode?.FirstOrDefault(c => c?.Coding?.FirstOrDefault()?.Display?.Equals(display, StringComparison.InvariantCultureIgnoreCase) == true);
        }

        public static Procedure? SearchForProcedure(this Bundle bundle, string resourcePath, bool startsWithMatching = true)
        {
            if (startsWithMatching)
            {
                var procedureMatching = bundle?.Entry.FirstOrDefault(e => e.FullUrl.StartsWith(resourcePath))?.Resource as Procedure;
                return procedureMatching;
            }
            var procedure = bundle?.FindEntry(resourcePath).FirstOrDefault()?.Resource as Procedure;
            return procedure;
        }

        public static bool SearchForProcedureComplication(this Bundle bundle, string resourcePath, string displayText)
        {
            var procedure = SearchForProcedure(bundle, resourcePath); 
            if (procedure?.Complication?.Any() == true)
            {
                var complications = procedure.Complication.ToList();
                var complicationMatching = complications.FirstOrDefault(x => x.Coding?.FirstOrDefault()?.Display?.ToLower() == displayText);
                //TODO : consider complicaitonCode here or just mere precense ? Ask Kreftreg ? string complicationCode = complicationMatching?.Coding?.FirstOrDefault()?.Code;
                //Other developer confirmed checking mere precense is okay. going for this then.
                return complicationMatching != null;
            }
            return false; 
        }

        public static Quantity? GetObservationQuantity(this Observation observation)
        {
            var quantity = observation?.Value as Quantity;
            return quantity;             
        }

        public static DiagnosticReport? SearchForDiagnosis(this Bundle bundle, string resourcePath, bool startsWithMatching = true)
        {
            if (startsWithMatching)
            {
                var diagnosisMatching = bundle?.Entry.FirstOrDefault(e => e.FullUrl.StartsWith(resourcePath))?.Resource as DiagnosticReport;
                return diagnosisMatching;

            }
            var diagnosticReport = bundle?.FindEntry(resourcePath).FirstOrDefault()?.Resource as DiagnosticReport;
            return diagnosticReport;
        }

        public static string? GetDiagnosticProcedureCode(this DiagnosticReport diagnosticReport, int nthProcedureCode)
        {
            var codes = diagnosticReport?.Code?.Coding?.ToList();
            if (codes == null || !codes.Any() || codes.Count < nthProcedureCode+1)
            {
                return null;
            }
            var coding = codes.ElementAt(nthProcedureCode) as Coding;
            return coding.Display;
        }

        public static string? GetDiagnosisCode(this DiagnosticReport diagnostic, int position)
        {
            var diagnoses = diagnostic?.ConclusionCode?.ToList();
            if (diagnoses == null)
            {
                return null; 
            }
            if (diagnoses?.Count -1 >= position)
            {
                try
                {
                    return $"{diagnoses![position]?.Coding?.First().Code} {diagnoses[position]?.Coding.First().Display}";
                }
                catch { return null; }
            }
            return null;         
        }

        public static Extension? SearchForExtensionInsideProcedure(this Procedure procedure, string extensionUrl)
        {
            var extension = procedure?.Extension?.FirstOrDefault(e => e?.Url == extensionUrl) as Extension;
            return extension; 
        }

        public static List<Extension>? SearchForExtensionsInsideProcedure(this Procedure procedure, string extensionUrl)
        {
            var extensions = procedure?.Extension?.Where(e => e?.Url == extensionUrl)?.ToList();
            return extensions;
        }

        public static CodeableConcept? GetCodeableConceptInsideExtension(this Extension extension)
        {
            if (extension?.Value == null)
            {
                return null; 
            }
            var codeableConcept = extension?.Value as CodeableConcept;
            return codeableConcept;            
        }

        public static Extension? SearchForSubExtensionInsideExtension(this Extension extension, string extensionUrl)
        {
            var subExtension = extension?.Extension?.FirstOrDefault(e => e?.Url == extensionUrl);
            return subExtension;
        }

        public static Duration? GetExtensionDuration(this Extension extension)
        {
            var duration = extension?.Value as Duration;
            return duration; 
        }

        public static FhirBoolean? GetExtensionBoolean(this Extension extension)
        {
            return extension?.Value as FhirBoolean;
        }

        public static Coding? GetExtensionCodeValue(this Extension extension, string url, string system)
        {
            var subExtension = extension?.Extension?.FirstOrDefault(e => e?.Url == url) as Extension;
            var codeContainer = subExtension?.Value as CodeableConcept;
            return codeContainer?.Coding?.FirstOrDefault(c => c.System == system); 
        }      

        public static string? GetPatientIdentifier(this Patient patient)
        {
            return patient?.Identifier?.FirstOrDefault()?.Value;
        }


        public static string GetPatientName(this Patient patient)
        {

            var firstName = (patient?.Name?.FirstOrDefault())?.Given?.FirstOrDefault();
            var lastName = patient?.Name?.FirstOrDefault()?.Family;
            var middleName = (patient?.Name?.FirstOrDefault()?.Extension?.FirstOrDefault())?.Value?.ToString();
            return $"{firstName}{(!string.IsNullOrWhiteSpace(middleName) ? " " + middleName + " " : " ")}{lastName}";

        }

    }


}

 
 

I hope you found this article helpful in case you need to extract data from a FHIR bundle document. I am not specializing into working with FHIR, I just worked 1-2 weeks on such a FHIR bundle document and found my approach to maybe be of general interest and use. At least I found my approach
scalable for mapping each fields. Also note that I made a domain model as a model object where I put logic into the getters of a property and this corresponds to a field in the FHIR JSON bundle we want to retrieve. So I would then repeat the approach by extending the list of steps to successfully map a FHIR bundle into a domain model which THEN can be used as a better prepared model for INPUT to YOUR system. i.e. we go via a domain model that then can be input to your system where mapping will be trivial inside your system, e.g. save a domain model field into a database or similar if you want to input a FHIR json bundle and create a new POCO entity into your system and store it to a database.

GENERIC APPROACH - Implement a mapping from a FHIR bundle into a DOMAIN MODEL which then can be used in your SYSTEM

  • Use the debugger and unit tests and explore in the immediate window which kind of Entry each component in the FHIR bundle is. Identity if we can cast an Entry into a correct subtype, such as a MedicationStatement. These POCO objects are available in the noted nuget packages above.
  • After finding a way to retrieve the data - generalize the retrival into extension methods, which can be chained and then make use of these extension methods into property getter logic. These properties reside in the DOMAIN MODEL object. For example ColoscopyDomainModel .
  • Use a TDD approach to retrieve the data. Each property was found in the sample doc for me (about 100 properties) and I was trying to find generic ways to find these property values
  • Sometimes you need fine tuned logic too to find data. FHIR contains some extensions and different FHIR bundles, although it is a standard, may vary some.
  • + REMEMBER - then utilize the DOMAIN MODEL which is mapped into YOUR SYSTEM and then save the fields to a database or other storage for example. Or maybe you only want to use the domain model as is without it doing anything else than represent its data from the FHIR bundle.
Of course you will need some more infrastructure around handling FHIR documents, such as a REST API for example, but this article focused on FHIR bundle parsing. And finally, FHIR supports also the formats XML and RDF. The web site of FHIR can explain more, if you want to delve into details. I found it most helpful just to get started coding here with the GENERIC APPROACH mentioned above. HL 7 FHIR web site: http://hl7.org/fhir/

Saturday, 28 May 2022

Using expression trees to build up loops - Gauss Summation

I tested out Expression trees today in more depth and played around with Gauss summation. The Gauss summation is a well-known theorem in Calculus. It states that for a plain arithmetic sequence of numbers with a distance of 1 (i.e. 1,2,3,4,5,6...) from 1..n the sum of these numbers are equal to the formula : Sum(n) = (n*(n+1)) / 2 Johan Karl Friendrich Gauss is renowned to have come up with this as a very young student when a school teacher asked the class to sum the numbers from 1 to 100 and give him the answer. Gauss almost instantly replied '5050', which was also the correct answer. This may or may not have been the case. The formula itself can anyways be theorized by summing the largest and
smallest number and then approaching the middle of the sequence. You can add 1 and 100 to get 101, 2 and 99 to get 101 and so on. The sum is always 101 (n+1) and there are a hundred such 'pairs' (n). But we want to only sum the numbers once, so we divide by 2 => we have the Gauss summation formula ! Let's look at how to do such a summation using Expression trees in C#. But I have only created a loop algorithm here, we calculate the same answer but we instead use expression trees. In demonstrates how
we can get started with expression trees in C# using loops (it is while loop which is created here) and parameter expressions and other components, such as 'labels' used in 'gotos'. This is actually needed in
expression trees to get the looping and breaking to work. The 'SumRange' method looks like this :

public static Expression SumRange(ParameterExpression value)
{
    LabelTarget label = Expression.Label(typeof(int));

    ParameterExpression result = Expression.Variable(typeof(int), "result");
    var initializeResult = Expression.Assign(result, Expression.Constant(0));

    var innerLogicBlock = Expression.Block(
        Expression.Assign(result,
            Expression.Add(result, value)),
        Expression.PostDecrementAssign(value)
    );

    BlockExpression body = Expression.Block(
       new[] { result },
       initializeResult,
       Expression.Loop(
           Expression.IfThenElse(
            Expression.GreaterThanOrEqual(value, Expression.Constant(1)),
            innerLogicBlock,
            Expression.Break(label, result)
            ),
            label
         )
    );
    return body;
}

We pass in a parameter expression. We then declare a 'label' which is used in a 'goto' execution flow when we want to break out of our loop, created by Expression.Loop. The initializeResult is listed here inside Expression block as we want to assign the result variable to the initial value (the expression constant '0'). We then have an 'outer logic' where we have a If-Then-Else condition where we check if value is greater than or equal to 1 and then
we perform the 'inner logicl block' assigned earlier, where we assign result to itself and the value variable passed in as a parameterexpression to this method. Note, we will do some type checking via Expression.Lambda which call this SumRange method explained further below. Note the use of 'PostDecrementAssign' expression which decrements the 'value' and ensures we can exit out of the loop. It can be of course hard to follow along such expression trees without some tooling. I use the ReadableExpressions:Visualizer plugin for VS 2022 here : https://marketplace.visualstudio.com/items?itemName=vs-publisher-1232914.ReadableExpressionsVisualizers You can use it to preview expressions as shown in the below screen shot :
And our unit test passes with expected result.
 

        [Fact]
        public void SumRange()
        {
            var value = Expression.Parameter(typeof(int));
            var result = ScriptingEngine.SumRange(value);
            var expr = Expression.Lambda<Func<int, int>>(result, value);
            var func = expr.Compile(); 
             Assert.Equal(5050, func(100)); 
        }

 
As you can see, even for a simple method, we need to type a lot of code to build up an expression tree. There are helper libraries such as AgileObjects.ReadableExpressions and System.Linq.Dynamic.Core which
can help out a lot when using expression trees. Add these to your package references (.csproj) for example :

  <ItemGroup>
    <PackageReference Include="AgileObjects.ReadableExpressions" Version="3.3.0" />
    <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.18" />
  </ItemGroup>

The first package of these got a handy method ToReadableString and the last one got a handy helper class called DynamicExpressionParser, which in tandem can create pretty complex expression trees. When will you use such logic ? Most often when wanting to build up custom logic query filters. You should not allow end users to arbitrarily build up all kinds of query filters, but may offer them a set of user controls to build and combine query filters so they can retrieve data via rather complex rules. The code libs mentioned here is supported in .NET Framework 3.5 or later (and .NET Standard 1.0), so most target frameworks are supported then.

Get properties of a given type in C#

This article shows how we can find all properties with a given property type. The provided code also can find private properties or look for nullable of the property type. E.g. find all DateTime properties and also include all properties which are Nullable of DateTime, Nullable. An extension method for this looks like the following (put the method into a static class as it is an extension method) :
  

  
          /// <summary>
        /// Retrieves a list of properties (property info) with given type <paramref name="propertyType"/> in a nested object
        /// </summary>
        /// <param name="rootObject"></param>
        /// <param name="propertyType"></param>
        /// <param name="includePrivateProperties">If set to true, includes private properties</param>
        /// <param name="includeNullableVariant">If set to true, return also propertie which are the nullable variant of the <paramref name="propertyType"/>.</param>
        /// <returns>A list of properties with given <paramref name="propertyType"/>, possibly including also non-nullable variant of the type and both public and private properties set with the parameters <paramref name="includePrivateProperties"/> and <paramref name="includeNullableVariant"/></returns>
        public static IEnumerable<PropertyInfo> GetPropertiesOfType(this object rootObject, Type propertyType,
            bool includePrivateProperties = false, bool includeNullableVariant = false)
        {
            if (rootObject == null)
            {
                yield return null;
            }
            var bindingFlagsFilter = !includePrivateProperties ? BindingFlags.Public | BindingFlags.Instance : BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
            var propertiesOfType = rootObject.GetType().GetProperties(bindingFlagsFilter)
                .Where(p => p.PropertyType == propertyType || (includeNullableVariant && propertyType == Nullable.GetUnderlyingType(p.PropertyType)))
                .ToList();
            foreach (var prop in propertiesOfType)
            {
                yield return prop;
            }
            var nestableProperties = rootObject.GetType().GetProperties(bindingFlagsFilter)
              .Where(p => p.PropertyType.IsClass && p.PropertyType != typeof(string))
              .ToList(); //ignoring properties of type strings as they are not nested, though a class
            foreach (var prop in nestableProperties)
            {
                if (prop.GetIndexParameters().Length > 0)
                {
                    continue; //skip indexer properties 
                }
                var rootObjectLevel = prop.GetValue(rootObject, null);
                if (rootObjectLevel == null)
                {
                    continue;
                }
                foreach (var propertyAtLevel in GetPropertiesOfType(rootObjectLevel, propertyType, includePrivateProperties, includeNullableVariant))
                {
                    yield return propertyAtLevel;
                }
            }
        }

We find the properties matching the property type and then recursively fetch such properties at nested levels too if the property is a class and therefore can contain sub properties. We end up with all the properties of a given type. As we see, we adjust the binding flags to include private properties too or not. And we use the Nullable.GetUnderlyingType method to match the underlying type in case we want to look for DateTime and DateTime? properties. This method is fairly fast, in the order of a few milliseconds (1-5 when I tested for an ordinary two level nested object.) But we are using reflection here and the method could be faster if we made use of some other techniques, perhaps with
IL 'magic'. I have not found a way to do this yet though.. Here is another utility method (extension method) for finding 'property paths'. This is handly if you want to craft an SQL select statement for example as we need the fully qualified path perhaps if our tooling creates fields in the database similar to POCO object
and the nested object is similar to table structure. Maybe your nested properties are mapped to SomeInnerTable1_SomeField1 and so on. Anyways, it is handly to have 'property paths' to the properties to get a fast overview of where the properties are located in the nested structure of your (possibly complex) object.



         /// <summary>
        /// This method looks for properties of given type in a nested object (e.g. a form data contract) 
        /// </summary>
        /// <param name="rootObject"></param>
        /// <param name="propertyType"></param>
        /// <returns></returns>
        private IEnumerable<string> GetPropertyPathsForType(object rootObject, Type propertyType, string prefixAtLevel = "")
        {
            if (rootObject == null)
            {
                yield return string.Empty;
            }
            var propertiesOfType = rootObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => p.PropertyType == propertyType)
                .ToList();

            foreach (var prop in propertiesOfType)
            {
                if (string.IsNullOrWhiteSpace(prefixAtLevel))
                {
                    yield return prop.Name; //root properties have no prefix 
                }
                else
                {
                    yield return prefixAtLevel.TrimStart('.') + "." + prop.Name;
                }
            }

            var nestableProperties = rootObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
              .Where(p => p.PropertyType.IsClass && p.PropertyType != typeof(string))
              .ToList(); //ignoring properties of type strings as they are not nested, though a class

            foreach (var prop in nestableProperties)
            {
                if (prop.GetIndexParameters().Length > 0)
                {
                    continue; //skip indexer properties - this is identified as required 
                }
                var rootObjectLevel = prop.GetValue(rootObject, null);
                if (rootObjectLevel == null)
                {
                    continue;
                }
                foreach (var propertyAtLevel in GetPropertyPathsForType(rootObjectLevel, propertyType, prefixAtLevel + "." + prop.Name))
                {
                    yield return propertyAtLevel.TrimStart('.').TrimEnd('.');
                }
            }
        }


The code above could use the first method more to support including public and private properties, but I leave it out 'as an exercise to the reader' as text books often states. So this code is very handy if you for example at work need to find 'all the datetime properties in a domain object' and similar cases. Maybe you want to deny all datetimes have a future datetime in case it is a report for a patient treatment report being performed yesterday and so on, and for
that particular model, there will be no future date time values.

Wednesday, 6 April 2022

Abstract factory - registering factory methods

Today, instead of relying on factories we often resort to dependency injection. However, you can 'blueprint' objects by offering a helper class to register both interfaces with concrete classes and default parameters if you want to instantiate a type of object with given parameters using an abstract factory class The following code shows how we can start building such a registration approach, supporting also unregistering factories and creating objects which are registered. You will have to add thread safety and better checking if a given type T and parameters P1..PN are registered, here is though a simple Abstract factory. Paste the code into Linqpad 6 or Linqpad 7 to test it out !
 
 
 void Main()
{

 var factory = new AbstractFactory(); 
 
 factory.Register<IDrive>(() => new FoulwheeledDrive());
 factory.Register<Car, string, string, IDrive>((string make, string model, IDrive drive) => new Car(make, model, drive)); 
 
 var car = factory.Create<Car, string, string, IDrive>("BMW", "M5", factory.Create<IDrive>());
 
 car.ToString().Dump();
 
 //can also unregister a factory method
 //factory.Unregister<Car, string, string, IDrive>();
 //var car2 = factory.Create<Car, string, string, IDrive>("BMW", "M5", factory.Create<IDrive>());
 
	
}

public interface IVehicle {
 string Model { get; set; }
 string Make { get; set; }
}

public interface IDrive {
 string Drive();
}

public class FoulwheeledDrive : IDrive {
 public string Drive() => "Car driving with four wheeled motion";
}

public class Car  {

  public string Make { get; set; }  
  public string Model { get; set; }
  
  public IDrive Drive { get; set; }
 
  public Car(string model, string make, IDrive drive){
    Model = model; 
 	Make = make;
	Drive = drive;
  }
  
  public override string ToString() => $"{Model} {Make} : {Drive.Drive()}";
 
}

public class AbstractFactory {

 private readonly Dictionary<Type, Delegate> _factories = new (); 
 
 
 public void Register<T>(Func<T> factory) => _factories[typeof(Func<T>)] = factory;
 public void Register<T,P>(Func<P,T> factory) => _factories[typeof(Func<P,T>)] = factory;
 public void Register<T,P1, P2>(Func<P1, P2, T> factory) => _factories[typeof(Func<P1, P2, T>)] = factory;
 public void Register<T, P1, P2, P3>(Func<P1, P2, P3, T> factory) => _factories[typeof(Func<P1, P2, P3, T>)] = factory;
 public void Register<T, P1, P2, P3, P4>(Func<T> factory) => _factories[typeof(Func<P1, P2, P3, P4, T>)] = factory;
 public void Register<T, P1, P2, P3, P4, P5>(Func<T> factory) => _factories[typeof(Func<P1, P2, P3, P4, P5, T>)] = factory;
 
 public void Unregister<T>() => _factories.Remove(typeof(Func<T>)); 
 public void Unregister<T,P>() => _factories.Remove(typeof(Func<P,T>));
 public void Unregister<T,P1, P2>() => _factories.Remove(typeof(Func<P1, P2, T>));
 public void Unregister<T,P1, P2, P3>() => _factories.Remove(typeof(Func<P1, P2, P3, T>)); 
 public void Unregister<T,P1, P2, P3, P4>() => _factories.Remove(typeof(Func<P1, P2, P3, P4, T>));
 public void Unregister<T,P1, P2, P3, P4, P5>() => _factories.Remove(typeof(Func<P1, P2, P3, P4, P5, T>));
 
 public T Create<T>() => ((Func<T>)_factories[typeof(Func<T>)])();
 public T Create<T, P>(P p) => ((Func<P,T>)_factories[typeof(Func<P,T>)])(p);
 public T Create<T, P1, P2>(P1 p1, P2 p2) => ((Func<P1, P2,T>)_factories[typeof(Func<P1, P2,T>)])(p1, p2);
 public T Create<T, P1, P2, P3>(P1 p1, P2 p2, P3 p3) => ((Func<P1, P2, P3, T>)_factories[typeof(Func<P1, P2, P3, T>)])(p1, p2, p3);
 public T Create<T, P1, P2, P3, P4>(P1 p1, P2 p2, P3 p3, P4 p4) => ((Func<P1, P2, P3, P4, T>)_factories[typeof(Func<P1, P2, P3, P4, T>)])(p1, p2, p3, p4);
 public T Create<T, P1, P2, P3, P4, P5>(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) => ((Func<P1, P2, P3, P4, P5, T>)_factories[typeof(Func<P1, P2, P3, T>)])(p1, p2, p3, p4, p5);
 
}


 
 

The registration of the factories then instantiate the instance. Note that we do not use the new operator when instantiating the object. We avoid using Activator.CreateInstance and reflection using this approach. Why use this pattern when we want to create 'blueprint' objects and want to have standard ways of instantiating them and for example avoid using concrete types. Most IoC frameworks allow you to create compound classes and supply parameters, so the abstract factory pattern seems a bit 'crude', but it is used many places still.