Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying a default DateTimeKind #12994

Closed
poke opened this issue Aug 14, 2018 · 7 comments
Closed

Allow specifying a default DateTimeKind #12994

poke opened this issue Aug 14, 2018 · 7 comments

Comments

@poke
Copy link

poke commented Aug 14, 2018

This is related to #4711 and #10784, basically a follow-up issue based on a comment by @rmja:

A slight variation of this is that it would be nice if one could configure the default DateTimeKind. For example, I know that all dates in my database are Utc, so it would be nice if this was specified for all fetched dates in the DbContext, or maybe configurable per entity property?


Usually, when you are designing a database, you have the full control over how dates might be stored inside of it. So regardless of whether your database supports storing the timeszone with the DateTime or not, it’s not uncommon to decide on a fixed timezone for all dates inside of a database. Usually, and that is also the general recommendation on this topic, this would be UTC.

It would be really nice to be able to set up a default handling for the DateTimeKind. For example, a good and flexible rule could be this: If the kind is unspecified, interpret it as a UTC DateTime; otherwise convert it into a UTC DateTime, taking its kind into account.

I’m currently doing this with a value converter like this:

Expression<Func<DateTime, DateTime>> normalizeDate = d => d.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(d, DateTimeKind.Utc) : d.ToUniversalTime();
var dateTimeNormalizer = new ValueConverter<DateTime, DateTime>(normalizeDate, normalizeDate);

// and then for every single DateTime property on every entity
entity.Property(e => e.EverySingleDateTimeProperty).HasConversion(dateTimeNormalizer);

This works pretty well but having to add this on every single DateTime property is really tedious. Of course, this particular solution will become obsolete when configuring a value converter globally for a property type becomes possible with (maybe) 3.0. But regardless of that, I think it would be worth to consider adding support for date time kinds in general, that is independent of value converters.

What do you think? Am I ignoring missing use cases that would break such a default, or would implementing this separately from default value converters be too complicated?

@h0wXD
Copy link

h0wXD commented Sep 29, 2019

Following other peoples suggestions (ModelBuilderExtension etc) in both #4711 and #10784, I decided to go ahead with:

public static class ModelBuilderExtension
{
	public static ModelBuilder UseValueConverterForType<T>(this ModelBuilder modelBuilder, ValueConverter converter)
	{
		return modelBuilder.UseValueConverterForType(typeof(T), converter);
	}

	public static ModelBuilder UseValueConverterForType(this ModelBuilder modelBuilder, Type type, ValueConverter converter)
	{
		foreach (var entityType in modelBuilder.Model.GetEntityTypes())
		{
			// note that entityType.GetProperties() will throw an exception, so we have to use reflection
			var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type);

			foreach (var property in properties)
			{
				modelBuilder.Entity(entityType.Name).Property(property.Name)
					.HasConversion(converter);
			}
		}

		return modelBuilder;
	}

	public class DateTimeKindValueConverter : ValueConverter<DateTime, DateTime>
	{
		public DateTimeKindValueConverter(DateTimeKind kind, ConverterMappingHints mappingHints = null)
			: base(
				v => v.ToUniversalTime(),
				v => DateTime.SpecifyKind(v, kind),
				mappingHints)
		{
		}
	}

	public static void SetDefaultDateTimeKind(this ModelBuilder modelBuilder, DateTimeKind kind)
	{
		modelBuilder.UseValueConverterForType<DateTime>(new DateTimeKindValueConverter(kind));
		modelBuilder.UseValueConverterForType<DateTime?>(new DateTimeKindValueConverter(kind));
	}
}

Allows you to specify the default in the DbContext:

protected override void OnModelCreating(ModelBuilder builder)
{
	builder.SetDefaultDateTimeKind(DateTimeKind.Utc);
	base.OnModelCreating(builder);
}

@roji
Copy link
Member

roji commented Sep 29, 2019

Duplicate of #10784?

@haraldkofler
Copy link

Following other peoples suggestions (ModelBuilderExtension etc) in both #4711 and #10784, I decided to go ahead with:

public static class ModelBuilderExtension
{
	public static ModelBuilder UseValueConverterForType<T>(this ModelBuilder modelBuilder, ValueConverter converter)
	{
		return modelBuilder.UseValueConverterForType(typeof(T), converter);
	}

	public static ModelBuilder UseValueConverterForType(this ModelBuilder modelBuilder, Type type, ValueConverter converter)
	{
		foreach (var entityType in modelBuilder.Model.GetEntityTypes())
		{
			// note that entityType.GetProperties() will throw an exception, so we have to use reflection
			var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type);

			foreach (var property in properties)
			{
				modelBuilder.Entity(entityType.Name).Property(property.Name)
					.HasConversion(converter);
			}
		}

		return modelBuilder;
	}

	public class DateTimeKindValueConverter : ValueConverter<DateTime, DateTime>
	{
		public DateTimeKindValueConverter(DateTimeKind kind, ConverterMappingHints mappingHints = null)
			: base(
				v => v.ToUniversalTime(),
				v => DateTime.SpecifyKind(v, kind),
				mappingHints)
		{
		}
	}

	public static void SetDefaultDateTimeKind(this ModelBuilder modelBuilder, DateTimeKind kind)
	{
		modelBuilder.UseValueConverterForType<DateTime>(new DateTimeKindValueConverter(kind));
		modelBuilder.UseValueConverterForType<DateTime?>(new DateTimeKindValueConverter(kind));
	}
}

Allows you to specify the default in the DbContext:

protected override void OnModelCreating(ModelBuilder builder)
{
	builder.SetDefaultDateTimeKind(DateTimeKind.Utc);
	base.OnModelCreating(builder);
}

This solution works very well, except if you have a not mapped property on a data model.
By adding a check if a a setModel function is present this problem can be addressed too:

`// note that entityType.GetProperties() will throw an exception, so we have to use reflection
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type && p.SetMethod != null);

foreach (var property in properties)`

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
@rjk

This comment was marked as resolved.

@roji

This comment was marked as resolved.

@rjk

This comment was marked as resolved.

@roji

This comment was marked as resolved.

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

No branches or pull requests

7 participants