Skip to content

Instantly share code, notes, and snippets.

@werwolfby
Last active March 7, 2023 13:18
Show Gist options
  • Save werwolfby/7f04558bc21c8114e209d5727fb2e9f8 to your computer and use it in GitHub Desktop.
Save werwolfby/7f04558bc21c8114e209d5727fb2e9f8 to your computer and use it in GitHub Desktop.
EF Core DateTime Utc kind
public class CustomDbContext : DbContext
{
// ...
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
// Replace default materializer source to custom, to convert DateTimes
options.ReplaceService<IEntityMaterializerSource, DateTimeKindEntityMaterializerSource>();
base.OnConfiguring(options);
}
}
public class DateTimeKindEntityMaterializerSource : EntityMaterializerSource
{
private static readonly MethodInfo _normalizeMethod =
typeof(DateTimeKindMapper).GetTypeInfo().GetMethod(nameof(DateTimeKindMapper.Normalize));
private static readonly MethodInfo _normalizeNullableMethod =
typeof(DateTimeKindMapper).GetTypeInfo().GetMethod(nameof(DateTimeKindMapper.NormalizeNullable));
private static readonly MethodInfo _normalizeObjectMethod =
typeof(DateTimeKindMapper).GetTypeInfo().GetMethod(nameof(DateTimeKindMapper.NormalizeObject));
public override Expression CreateReadValueExpression(Expression valueBuffer, Type type, int index, IProperty property = null)
{
if (type == typeof(DateTime))
{
return Expression.Call(
_normalizeMethod,
base.CreateReadValueExpression(valueBuffer, type, index, property));
}
if (type == typeof(DateTime?))
{
return Expression.Call(
_normalizeNullableMethod,
base.CreateReadValueExpression(valueBuffer, type, index, property));
}
return base.CreateReadValueExpression(valueBuffer, type, index, property);
}
public override Expression CreateReadValueCallExpression(Expression valueBuffer, int index)
{
var readValueCallExpression = base.CreateReadValueCallExpression(valueBuffer, index);
if (readValueCallExpression.Type == typeof(DateTime))
{
return Expression.Call(
_normalizeMethod,
readValueCallExpression);
}
if (readValueCallExpression.Type == typeof(DateTime?))
{
return Expression.Call(
_normalizeNullableMethod,
readValueCallExpression);
}
if (readValueCallExpression.Type == typeof(object))
{
return Expression.Call(
_normalizeObjectMethod,
readValueCallExpression);
}
return readValueCallExpression;
}
}
public class DateTimeKindMapper
{
public static DateTime Normalize(DateTime value)
=> DateTime.SpecifyKind(value, DateTimeKind.Utc);
public static DateTime? NormalizeNullable(DateTime? value)
=> value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : (DateTime?)null;
public static object NormalizeObject(object value)
=> value is DateTime dateTime ? Normalize(dateTime) : value;
}
@natelowry
Copy link

seems CreateReadValueExpression changed the type of the last param to IPropertyBase and CreateReadValueCallExpression was marked Obsolete. the version in my fork seems to work fine with those changes.

thanks for this!

@natelowry
Copy link

also note, if you're using UseInMemoryDatabase for testing, it'll barf on this.

My solution was to move the ReplaceService call to inside the AddDbContext options in my services setup for my web project like so:

services.AddDbContext<MyDatabaseContext>(options =>
{
    options.UseSqlServer(connectionString);
    options.ReplaceService<IEntityMaterializerSource, DateTimeKindEntityMaterializerSource>();
});

@danielGentus
Copy link

Can you make an example using .Net 6?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment