Reputation: 12530
I have a large database where we do a lot of TimeZone conversions. I am slowly converting the database to use DateTimeOffSet datatypes, but that's something I can't do accross the board. Too much change.
I can however change the code, and since I know that all my dates are stored in UTC in the database, I would like to just use the DateTimeOffSet object in .NET.
How can I get EF to do the conversion on the fly for me?
I have tried this:
modelBuilder.Properties<DateTimeOffset>()
.Configure( c => c.HasColumnType( "datetime" ) );
But I'm getting the error
(37,12) : error 2019: Member Mapping specified is not valid. The type 'Edm.DateTimeOffset[Nullable=True,DefaultValue=,Precision=]' of member 'ModifyDate' in type '{ObjectType}' is not compatible with 'SqlServer.datetime[Nullable=True,DefaultValue=,Precision=3]' of member 'ModifyDate' in type 'CodeFirstDatabaseSchema.{ObjectType}'.
Upvotes: 3
Views: 2485
Reputation: 241420
Here is one approach you might consider:
First, define this following attribute:
[AttributeUsage(AttributeTargets.Property)]
public class DateTimeKindAttribute : Attribute
{
private readonly DateTimeKind _kind;
public DateTimeKindAttribute(DateTimeKind kind)
{
_kind = kind;
}
public DateTimeKind Kind
{
get { return _kind; }
}
public static void Apply(object entity)
{
if (entity == null)
return;
var properties = entity.GetType().GetProperties()
.Where(x => x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?));
foreach (var property in properties)
{
var attr = property.GetCustomAttribute<DateTimeKindAttribute>();
if (attr == null)
continue;
var dt = property.PropertyType == typeof(DateTime?)
? (DateTime?) property.GetValue(entity)
: (DateTime) property.GetValue(entity);
if (dt == null)
continue;
property.SetValue(entity, DateTime.SpecifyKind(dt.Value, attr.Kind));
}
}
}
Now hook that attribute up to your EF context:
public class MyContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public MyContext()
{
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
(sender, e) => DateTimeKindAttribute.Apply(e.Entity);
}
}
Now on any DateTime
or DateTime?
properties, you can apply this attribute:
public class Foo
{
public int Id { get; set; }
[DateTimeKind(DateTimeKind.Utc)]
public DateTime Bar { get; set; }
}
With this in place, whenever Entity Framework loads an entity from the database, it will set the DateTimeKind
that you specify, such as UTC.
Now, you said you want to start switching over to DateTimeOffset
types. You can take advantage of the fact that DateTime
has a one-way implicit conversion to DateTimeOffset
that will take the .Kind
into account. In other words, you can do this:
DateTimeOffset BarDTO = foo.Bar;
Even though foo.Bar
is a DateTime
, this will work. Because the kind is set to UTC, the offset will be set to zero in the DateTimeOffset
.
Of course, you could always do this in your model with something like this:
[NotMapped]
public DateTimeOffset BarDTO
{
get { return Bar; }
set { Bar = value.UtcDateTime; }
}
I'm sure you can come up with variations on this to suit your needs. The main thing is that whatever property is mapped to the field, the type must match.
Upvotes: 6