using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.Extensions.DependencyInjection;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using EFCoreSecondLevelCacheInterceptor;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Linq.Expressions;
public class JsonbConvertor<T> : ValueConverter<T, string>
private static readonly Expression<Func<T, string>> _convertToProviderExpression = x => Serialize(x);
private static readonly Expression<Func<string, T>> _convertFromProviderExpression = x => Deserialize(x);
public JsonbConvertor(ConverterMappingHints mappingHints = null)
: base(_convertToProviderExpression, _convertFromProviderExpression, mappingHints)
private static string Serialize(T x)
return JsonSerializer.Serialize(x);
private static T Deserialize(string x)
return JsonSerializer.Deserialize<T>(x);
public class DBNullFormatter : IMessagePackFormatter<DBNull>
public static DBNullFormatter Instance = new();
private DBNullFormatter()
public void Serialize(ref MessagePackWriter writer, DBNull value, MessagePackSerializerOptions options)
public DBNull Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
public Guid Id { get; set; } = Guid.NewGuid();
[Column(TypeName = "jsonb")] public List<BlogOption> OptionDefinitions { get; set; } = new List<BlogOption>();
public bool IsActive { get; set; }
public bool IsRequired { get; set; }
public int SortOrder { get; set; }
public string Name { get; set; }
public DateOnly? BirthDate { get; set; }
public bool IsActive { get; set; }
public int NumberOfTimesUsed { get; set; }
public int SortOrder { get; set; }
public string Name { get; set; }
public class BloggingContext : DbContext
public BloggingContext() { }
public BloggingContext(DbContextOptions<BloggingContext> options)
public DbSet<Blog> Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
base.OnModelCreating(builder);
foreach (var property in builder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.GetColumnType() == "jsonb"))
var converterType = typeof(JsonbConvertor<>).MakeGenericType(property.ClrType);
var converter = (ValueConverter)Activator.CreateInstance(converterType, (object)null);
property.SetValueConverter(converter);
foreach (var property in builder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(DateOnly)))
property.SetValueConverter(
new ValueConverter<DateOnly, DateTime>(
convertToProviderExpression: dateOnly => dateOnly.ToDateTime(new TimeOnly(0, 0)),
convertFromProviderExpression: dateTime => DateOnly.FromDateTime(dateTime)
foreach (var property in builder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(DateOnly?)))
property.SetValueConverter(
new ValueConverter<DateOnly?, DateTime>(
convertToProviderExpression: dateOnly => dateOnly.Value.ToDateTime(new TimeOnly(0, 0)),
convertFromProviderExpression: dateTime => DateOnly.FromDateTime(dateTime)
private BloggingContext _context;
public BlogService(BloggingContext context)
static void Main(string[] args)
var context = getServiceProvider().GetRequiredService<BloggingContext>();
BirthDate = DateOnly.Parse("01/01/2010")
var posts = context.Blogs.Cacheable().ToList();
Console.WriteLine($"Title From DB: {posts.First().Name}");
posts = context.Blogs.Cacheable().ToList();
Console.WriteLine($"Title From Cache: {posts.First().Name}");
private static IServiceProvider getServiceProvider()
var services = new ServiceCollection();
services.AddLogging(config =>
const string providerName = "memoryCache";
services.AddEasyCaching(o =>
cfg.EnableLogging = true;
cfg.SerializerName = "Pack";
so.EnableCustomResolver = true;
so.CustomResolvers = CompositeResolver.Create(
new IMessagePackFormatter[]
NativeDateTimeResolver.Instance,
ContractlessStandardResolver.Instance,
StandardResolverAllowPrivate.Instance,
TypelessContractlessStandardResolver.Instance,
services.AddEFSecondLevelCache(o =>
o.UseEasyCachingCoreProvider(providerName, isHybridCache: false).DisableLogging(false);
o.CacheAllQueries(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(10));
o.UseCacheKeyPrefix("EF_");
var remoteConnectionString = $"Host=db-postgresql-sfo2-76371-do-user-10529433-0.b.db.ondigitalocean.com;Port=25060;Username=EFTest;Password=VStW3x816PCGtTVV;Database=EFTesting;SSL Mode=Require;Trust Server Certificate=true;";
services.AddDbContext<BloggingContext>((serviceProvider, options) =>
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>())
.UseNpgsql(remoteConnectionString, options =>
options.UseAdminDatabase("defaultdb");
options.EnableRetryOnFailure();
.LogTo(sql => Console.WriteLine(sql));
return services.BuildServiceProvider();