Dapper - how to apply a transformation to selected object fields? - c#

The problem I am trying to solve is pretty simple: I need to make sure my timestamps are stored in the database as UTC. I use DateTime in my model objects, which maps to timespamp column in Postgres DB.
I'd like to do 2 things:
When a value is saved to the database, I want to check the DateTime.Kind field and throw up if it is not UTC. This will help me to capture errors if I try to feed a non-UTC time to a timestamp field. I do not want to do any transformations, just assert.
When loading from DB, I need those DateTime instances come out with DateTime.Kind set to UTC, so the rest of my application can recognize they are UTC and convert to local or whatever, as needed.
I only need to apply this logic to selected timestamp fields, not to all DateTime-s, because I have other DateTime values in my model, which need to be stored as they are.
I've tried this:
public class UtcTimestampMapper: SqlMapper.TypeHandler<DateTime>
{
public override void SetValue(IDbDataParameter parameter, DateTime dt)
{
if (dt.Kind == DateTimeKind.Utc)
parameter.Value = dt;
else
throw new InvalidOperationException("UTC timestamp expected");
}
public override DateTime Parse(object value)
{
return DateTime.SpecifyKind((DateTime)value, DateTimeKind.Utc);
}
}
This solution has 2 issues:
It applies to all DateTime values. With SetValue I can do the trick checking the column name in IDbDataParameter, maybe... But Parse method just gets a naked value, there is nothing I can do to determine where it goes.
The SetValue method won't be called at all, unless I remove the standard handler for DateTime. See this SO answer for details: Dapper TypeHandler.SetValue() not being called Again, if I replace the handler, I replace it globally, there is no way to choose where it applies.
So, is there a way to solve my problem with the facilities Dapper provides to customize mappings?

Related

How can I turn UTC Timezone off for MongoDB

I'm using the extension below for MongoDB date fields because of MongoDB stores times in UTC by default.
public static class DateTimeExtensions {
public static DateTime AdjustUtcDiffByOffset(this DateTime time) {
return time.AddHours(DateTimeOffset.Now.Offset.Hours);
}
}
}
All of them cause different problems although I have tried a few ways like attributes or serialization methods in .NET on the application level. I have decided to use this extension in .NET for now but I think that this is not a complete and exact solution too.
Is there any solution for this problem on the database level without being dependent on programming language wtih an adjust or something else?
EDIT
I think that I should an explain more after comments below. I already know MongoDB stores times in UTC that linked in this post as you see above. This can be useful but I don't need any UTC time zone difference in my app and I don't want to deal in Presentation Layer for every programming language separately. And also, I don't even want only one extra row or function in the other layers because of move away than base logic or business.
Let the my architecture undertaking this. I'm pretty lazy, the life is really short and the birds are flying outside :) I don't want the different fields as like as string convertings unnecessarily. I need a datetime type in the db due to I'm doing many time calculation in the app.
I'm using .NET now and therefore MongoDB .NET driver. I have tried different serialization methods but it cause another problems in the Data Access architecture. In conclusion, I can use UTC in my another app but I don't want it now and I prefer the local time when I assign to the field. I have decided to use the encapsulation below for C# especially.
private DateTime _startTime;
public DateTime StartTime {
get => _startTime;
set => _startTime = new DateTime(value.Ticks, DateTimeKind.Utc);
}
I don't think it's possible from the db level. What I did, was to write custom setter for date properties which will force mongoDB to assume the time is already in UTC, thus avoid a conversion like below:
private DateTime _createdUTC;
public DateTime CreatedUtc
{
get
{
return _createdUTC;
}
set
{
_createdUTC = new DateTime(value.Ticks, DateTimeKind.Utc);
}
}
MongoDB always saves dates in UTC.
What you can do is indicate to the client that he must convert the date to the local date when retrieving data from the database.
You can register a serializer before instantiating the client:
BsonSerializer.RegisterSerializer(DateTimeSerializer.LocalInstance);
var client = new MongoClient(options) ...

How to serialize the DateTime field with DateTimeKind option through ProtoBuf-net

How to serialize the date time field with DateTimeKind option through ProtoBuf. When deserializing i want that date time field with DateTimeKind option.
I know we can achieve this by adding one additional property to convert the deserialized value in the UTC format.
For example, i have one date time field called UtcDateTime. it has the value in the UTC kind format. When serialize and deserialize this value i am getting the proper result, but it failed to retrieve date time kind option.
Here is my sample code:
[ProtoIgnore]
public DateTime UtcDateTime { get; set; }
[ProtoMember(3)]
public DateTime DateTimeValue
{
get { return UtcDateTime ; }
set { UtcDateTime = DateTime.SpecifyKind(value, DateTimeKind.Utc); }
}
Now consider my case, my application has many date time fields like this. Instead of adding additional property in all places i need one generic solution to achieve this. If possible, please explain how to achieve this with sample C# logic. Thanks in advance.
Include this code to run before serializing or deserializing anything:
RuntimeTypeModel.Default.IncludeDateTimeKind = true;
Now your contract can be as simple as this (no need to double the properties):
[ProtoMember(3)]
public DateTime DateTimeValue { get; set; }
And the value of DateTimeKind will be written and read automatically.
Compatibility concern
It worth noting that the original Protocol Buffers specification doesn't include DateTimeKind (it's specific to .NET) -- see protobuf-net does not deserialize DateTime.Kind correctly . Opt-in support of serializing DateTimeKind was added in protobuf-net v2.1.0.
If you exchange these messages with another system, it won't work unless the other end uses protobuf-net and sets IncludeDateTimeKind to true.

How to handle converting dates to specific user timezone (Generic) MVC

I've do a lot of researching about handling and converting dates in my application to work globally between timezones.
In my application user can choose from different UiCulture to display the web application and choose different timezone from the culture, like he/she can set the culture to en-US and set in his profile inside the database the timezone.
i have successfully achieved a generic way to convert the user input and store it in UTC format and restore it and formatting using the selected UiCulture.
I know i can use functions inside the controllers (or some other place), and btw i am using MVC, and i don't like doing copy-past code or repeating it.
Now, what i want is a generic way to handle the converting of time part of the date based on a user pre-defined timezone (dont want copy-past code in many places).
Converting the date to UTC has been done through model binder.
public class DateTimeModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (string.IsNullOrWhiteSpace(value.AttemptedValue))
{
return null;
}
DateTime dateTime;
string CleanedDate = Numbers.ArabicToEnglish(value.AttemptedValue);
var isDate = DateTime.TryParse(CleanedDate, Thread.CurrentThread.CurrentUICulture, DateTimeStyles.None, out dateTime);
if (!isDate)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid Nullable DateTime Value");
return null;
}
return dateTime;
}
}
I don't know if i can achieve a timezone handling for inserting and restoring via using "DefaultModelBinder". using the methods
protected override void SetProperty
protected virtual object GetPropertyValue
I don't want client side solution.
Sorry for my bad english as it is not my first language, and i don't know if u get my issue because i don't really know how to describe it.
I did a lot of reading and searching and could not find similar behavior of what i want.
Thank you in advance.

Compare DateTime as a string into a LINQ to Entities doesn't recognize DateTime.parse(string)

I'm developing a WCF RESTful service with C#, .NET Framework 4.0 and Entity Framework Code First.
I have this class (that represents a table on database):
[DataContract]
public class PostLine
{
public int PostLineId { get; set; }
[DataMember]
public int? UserId { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public string DateUtc { get; set; }
public User Author { get; set; }
}
And I'm trying to do this:
DateTime fourDaysAgo = DateTime.Now.Date.AddDays(-4);
var postLines =
context.PostLines.Where(p => DateTime.Compare(DateTime.Parse(p.DateUtc), fourDaysAgo) > 0).Include("Author");
But I get the following error:
{System.NotSupportedException: LINQ to Entities doesn't recognize the method 'System.DateTime Parse(System.String)', which can not be converted into an expression of the repository.
I need that PostLine.DateUtc to be a string because I'm going to use it on my web service, and send it as JSON, so its better for me to store it as a string.
If I use DateTime type, I'm going to get something like this on JSON response:
{
"DateUtc": "/Date(1380924000000+0200)/",
"Description": "post_1",
"UserId": 1
}
Do you know how can I compare a string with a DateTime on a LINQ expression?
I think the best approach will be to split the property in two.
Entity Framework wants a DateTime property. That makes perfect sense.
For serialization you want a string property. That also makes perfect sense.
However, you're trying to use a single property for both, and that doesn't make sense, and isn't necessary.
[DataContract]
public class PostLine
{
...
public DateTime DateUtcAsDateTime { get; set; }
[DataMember, NotMapped]
public string DateUtcAsString {
get { return DateUtcAsDateTime.ToString(); }
set { DateUtcAsDateTime = DateTime.Parse(value); }
}
...
}
Now, DateUtcAsDateTime will be used by Entity Framework, and DateUtcAsString will be ignored by Entity Framework as it has a NotMapped attribute.
DateUtcAsString, on the other hand, is the only one of these properties that has a DataMember attribute, so should be the only one that gets serialized.
You can of course rename one of these properties back to DateUtc, if you want.
Update: as Matt Johnson points out, an improvement would be to specify the format in a way that always results in the exact same string. This ensures your strings don't change, just because your code gets moved to another server that happens to have different regional settings.
[DataMember, NotMapped]
public string DateUtcAsString {
get { return DateUtcAsDateTime.ToString("o"); }
set { DateUtcAsDateTime = DateTime.Parse(value, "o", null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); }
}
Note that I am using DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal instead of the DateTimeStyles.RoundTripKind that he suggested, because the name DateUtc strongly suggests you always want UTC, never local time. And I'm not specifying any culture explicitly, as the "o" format is already independent of culture.
You could, if it's easier for your other code to handle, use "r" instead of "o" with the same benefits.
This is certainly an XY problem if I ever saw one. You're asking about comparing datetime as strings in Entity Framework, while it seems the real problem is that you don't like the default date format of the DataContractJsonSerializer that WCF uses by default.
Part of the problem is that you are mixing local and UTC. You're getting /Date(1380924000000+0200)/, which contains the local time zone offset of the server. This is because you started from DateTime.Now, which has a .Kind of DateTimeKind.Local.
If you instead used DateTime.UtcNow, it would have a .Kind of DateTimeKind.Utc, and would be serialized as /Date(1380924000000)/. And yes, the numerical portion of the format would be the same. Even when there is an offset specified, the number part is still related to UTC.
That's just one problem with this format. The other is that while DataContractJsonSerializer writes the local offset during serialization, it doesn't use it properly during deserialization. It just assumes that if any offset is provided, that the time should be local - even if the computer doing the deserialization has a completely different offset.
The better format to use in JSON is the ISO8601 format. For example, this UTC value would look like 2013-10-04T22:00:00.000Z. And while you can easily pass in a different date format to DataContractJsonSerializer if you use it directly, WCF doesn't easily expose this to you.
You could go down the route of changing the DataContractJsonSerializer settings via a custom WCF message formatter, such as described here, here and here, but it gets complicated very quickly. Be careful if you do this, and be sure to test thoroughly!
Another idea would be to write a custom WCF message formatter that uses JSON.Net instead of DataContractJsonSerializer. JSON.Net uses the ISO8601 format by default, so you would be set.
But honestly, the best solution is to not try to use WCF to build your REST endpoints. The date format issue is just the beginning. There are all sorts of other problems that can pop up along the way. Instead, use a modern framework that is designed for this purpose, such as ASP.Net WebAPI, or ServiceStack.
ASP.Net WebAPI uses JSON.Net, and ServiceStack has it's own JSON serializer called ServiceStack.Text. JSON.Net uses ISO8601 as it's default date format. If you use Service Stack, you'll need to set JsConfig.DateHandler = JsonDateHandler.ISO8601;
So in recap, you have two XY problems:
First, you chose WCF to build your rest endpoints, which was so problematic that the industry developed other solutions.
Second, you couldn't get DateTime to emit the correct format, so you decided to treat it as a string, which then you couldn't compare back to a DateTime in an EF query.
And yes, I realize I didn't answer your question of how to compare a DateTime input against a string property in Entity Framework, but I think now you can see why. If you really want to go down that road, you might find something useful in SqlFunctions or EntityFunctions - but even then, how are you going to build an efficient index on this column in your database? Querying will be really slow if you get a lot of data. I would advise against it.
I don't know how you are instantiating your DataContractJsonSerializer. If you are directly instantiating it...you can pass a DataContractJsonSerializerSettings, with a DateTimeFormat for setting how DateTime are serialized.
If you are using a behavior to instantiate your Serializer things are a little more complicated.
If you really want to go down the route of using a string for your class when transfering your data to your client, you should have a separate DTO class.
You can then use a library like AutoMapper to map your PostLine class to PostLineDto with an expression.
However the next problem you will face then is that your Expression<Func<PostLine,PostLineDto>> will contain Expression.MethodCall(DateTime.Parse) and you will have to inject an ExpressionVisitor that can convert...
p => p.DateUtc > DateTime.Parse("2013 Aug 1") - bool
into
p => p.DateUtc > new DateTime(1, Aug, 2013) - bool
Which is a real pain.

How can I allow sql to set datetime with subsonic

I have begun using SubSonic and I am wondering how to get it to ignore DateTime properties on insert. I am using the ActiveRecord template and have default methods to create the datetime properties on insert on the DB side.
When I try to insert I get the following:
SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
I would rather not have to set the datetime in code as all items will drop into a queue and I'd like their created on datetime to be the actual time the record was created. Is there any way to get this behavior?
Override the BeforeInsert() and BeforeUpdate() methods (Tested and confirmed) with SubSonic 2 and set the Columns you want to ReadOnly.
public partial class Products
{
protected override void BeforeInsert()
{
Schema.GetColumn(Columns.CreatedOn).IsReadOnly = true;
base.BeforeInsert();
Schema.GetColumn(Columns.CreatedOn).IsReadOnly = false;
}
protected override void BeforeUpdate()
{
Schema.GetColumn(Columns.ModifiedOn).IsReadOnly = true;
base.BeforeUpdate();
Schema.GetColumn(Columns.ModifiedOn).IsReadOnly = false;
}
}
Schema is static so you can do this once in your code (even from outside the class) and don't worry about it again but I would prefer this way.
I haven't tried this with SubSonic3 but the IsReadOnly Property is still there so it should work, too.
If you're talking about the SubSonic ORM for .NET - you might try asking this question on the SubSonic support blog or send and email to subsonicproject#googlegroups.com requesting help.
Try to define DateTime as parameter. The similar situation is described here - http://www.devart.com/blogs/dotconnect/?p=2982#first.

Categories

Resources