Mongodb c# driver and ISODate - c#

I have the following test that passes:
namespace MongoDateTest
{
[TestFixture]
public class DateTesting
{
public class TestEntity
{
public string Id { get; set; }
public string StringTest { get; set; }
public DateTime DateTest { get; set; }
}
[Test]
public void MongoDateConversion()
{
const string connectionString = "mongodb://localhost";
var client = new MongoClient(connectionString);
var server = client.GetServer();
var database = server.GetDatabase("test");
var collection = database.GetCollection<TestEntity>("entities");
var entity = new TestEntity {
Id = "1",
StringTest = "Test",
DateTest = new DateTime(2013, 10, 13) //this is the date
};
collection.Save(entity);
var id = entity.Id;
var query = Query<TestEntity>.EQ(e => e.Id, id);
var entityNew = collection.FindOne(query);
Assert.AreEqual(entityNew.Id, entity.Id);
Assert.AreEqual(entity.StringTest, entityNew.StringTest);
//Assert.AreEqual(entity.DateTest,entityNew.DateTest);
// This gives one day error:
// Expected: 2013-10-13 00:00:00.000
// But was: 2013-10-12 22:00:00.000
//Assert.AreEqual(entity.DateTest.ToLocalTime(),entityNew.DateTest.ToLocalTime());
// This gives a 2 hours error.
// Expected: 2013-10-13 02:00:00.000
// But was: 2013-10-13 00:00:00.000
Assert.AreEqual(entity.DateTest, entityNew.DateTest.ToLocalTime());
}
}
}
If I uncomment any of the Asserts.AreEqual I get an error (commented below).
The saved entity is:
{
"_id" : "1",
"StringTest" : "Test",
"DateTest" : ISODate("2013-10-12T22:00:00Z")
}
I understand that this could be something related to ISODate and UTC (I am in UTC+1) but I a bit annoyed that my dates are saved with one day difference in the collection and requires me to convert to localTime any time I fetch some data with dates.
What is the reason for this behaviour and is there a way to avoid it?

In most case you want to store UTC date times in the database so your DateTime should be constructed as:-
DateTest = new DateTime(2013, 10, 13, 0, 0, 0, DateTimeKind.Utc) //this is the date
With this the first of your commented unit tests now passes.
Without specifying the DateTimeKind you are leaving it up to chance. MongoDB appears to assume that it's local and converts it to UTC in the database.
Note also that MongoDB DateTime values have less precision than .NET DateTime values. If you want to store arbitrary DateTime values and get them back in such a way that they still match then you will need to round them to the nearest millisecond before storing them.
If you really do want to store local times I recommend you switch from DateTime to DateTimeOffset and serialize it as a long Tick value for the UTC DateTime and a value for the offset.
Note that unless you store the offset calculated at the time the DateTime value was obtained then the .NET methods to convert to LocalTime are essentially useless since they do not know when daylight savings time started, nor do they even know what zone the DateTime value comes from. Overall, .NET DateTime handling leaves a lot to be desired and contains many misleading methods that claim to help but really do not.

You could also do this in your model.
public class TestEntity
{
public string Id { get; set; }
public string StringTest { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
public DateTime DateTest { get; set; }
}

Related

Best way to Pass Typescript Date property as a parameter to a Web Api 2.0 method

I have a date picker in html which is bound to a Date property of an employee.
ex: i have selected the date as "Thu Feb 22 2018 00:00:00 GMT+0530 (India Standard Time)".this is the value i get when i log in console.
But when i tried to debug in C# the value is changed.
{21-02-2018 18:30:00}
How to handle or work with the typescript date object when passing it to a API method and displaying it back
My typescript Model
export class Visitor {
public id: number;
public firstname: string;
public lastname: string;
public dob: Date;
public genderId: number;
public age: number;
}
and C# model
public class Visitor
{
public int Id { get; set; }
public string Lastname { get; set; }
public string Firstname { get; set; }
public int Age { get; set; }
public DateTime DOB { get; set; }
}
As long as you are passing a valid Date object you should be fine.
Your problem probably lies in your Culture settings in .NET side.
Can you please try doing this in your controller action:
var culture = new CultureInfo("gu-IN");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
Since you provide no code, this is merely a guess.
When you are printing in console, browser is printing the date in local timezone which is India in your case. When you are retrieving the value on server, C# is treating date in UTC. That is why you see a difference of 5.30 hours. India time is UTC + 5.30 Hours.
If you are storing the date value in UTC format on server side then convert the value of date object from client side to UTC using the libraries such as momentjs.
While retrieving the value from server, send the UTC value from server and convert it to local time zone using libraries such as momentjs. I think this strategy will work.

store only date in database not time portion C#

I have a test class and an ExecutionDate property which stores only date but when we use [DataType(DataType.Date)] that also stores the time portion in database but I want only date portion.
public class Test
{
[Key]
public int Id { get; set; }
[DataType(DataType.Date)]
public DateTime ExecutionDate { get; set; }
}
Is any way to store only date on time portion in db using Entity Framework? Please help me....
I have added snapshot when use [DataType(DataType.Date)] that stores time portion 00:00 I want remove that
I think you are trying to specify database column type. You could use data annotations as described in this article.
Here is an example :
[Table("People")]
public class Person
{
public int Id { get; set; }
[Column(TypeName = "varchar")]
public string Name { get; set; }
[Column(TypeName="date")]
public DateTime DOB { get; set; }
}
By default, string is translated to nvarchar, we have changed that here. Also Datetime (this is what you asked I suppose) which by default maps to datatime in sql server, is changed to date which stores only the date portion and not the time portion of a DateTime value.
On EF core one may add override OnModelCreating in DbContext class.
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Test>().
Property(p => p.ExecutionDate)
.HasColumnType("date");
}
As David said above, once you bring that data into the application it will be a DateTime with the timestamp added onto the date. This would be the same result too if you stored the date as a string and used Convert to change to a DateTime for any manipulation:
string date = "2015-11-17";
var dateTime = Convert.ToDateTime(date);
Unless you want to manipulate strings in your application to avoid the timestamp, you can only work with DateTime. For display purposes though, you can always format the date and remove the timestamp:
var dateTime = DateTime.Now;
var formatDate = dateTime.ToString("yyyy-MM-dd");
In .NET 6 now you have new special type - DateOnly
More info here:
https://github.com/dotnet/efcore/issues/24507
https://www.stevejgordon.co.uk/using-dateonly-and-timeonly-in-dotnet-6
Change datatype on the database from Datetime to Date type
Is it really an issue for you anyway? You may want time in there at some point,
Change DateTime to nvarchar(10) in database
if u use DateTime in database 00.00.00 will be automatically assigned by database
string date = DateTime.Today.ToShortDateString();

Aggregating By Date in Mongodb

I am writing a piece of functionality in which I am required to group by Date. Here is how I do currently:
//Assuming this is my sample document in the collection
{
"_id" : ObjectId("56053d816518fd1b48e062f7"),
"memberid" : "7992bc31-c3c5-49e5-bc40-0a5ba41af0bd",
"sourceid" : NumberInt(3888),
"ispremium" : false,
"createddate" : {
"DateTime" : ISODate("2015-09-25T12:26:41.157+0000"),
"Ticks" : NumberLong(635787808011571008)
},
"details": {
//a large sub-document
}
}
Given the member id, start date and end date; I am required to search the collection matching these filters and group the results by Date. In other words, the result I need to achieve is a list like (e.g., 12/10/2015 - count is 5, 13/10/2015 - count is 2). StartDate and EndDate are the types of DateTime.
C# is the programming language I use and currently, the way I have written is:
var builder = Builders<MyCollection>.Filter;
var filter = builder.Eq(d => d.MemberId, memberId) & builder.Gte(d => d.CreatedDate, startDate) & builder.Lt(d => d.CreatedDate, endDate.AddDays(1));
var group = collection.Aggregate()
.Match(filter)
.Group(new BsonDocument { { "_id", "$createddate" }, { "count", new BsonDocument("$sum", 1) } })
.ToListAsync().Result;
I then deserialize the result to custom class...
List<CustomAggregateResult> grouped = group.Select(g => BsonSerializer.Deserialize<CustomAggregateResult>(g)).OrderBy(g => g.Date).ToList();
Where this code fails, which is obvious, is the grouping part. What would be ideal in my case is, to group by Date rather than DateTime. I have read the group documentation and some similar threads here but, unfortunately, I couldn't get it working. One of my attempts was to do as suggested in the documentation. Raw mongo query for that would be:
db.mycollection.aggregate(
[
{
$group : {
_id : { month: { $month: "$createddate" }, day: { $dayOfMonth: "$createddate" }, year: { $year: "$createddate" } },
count: { $sum: 1 }
}
}
]
)
I had left out the $match just to get this bit working. The exception I got was "cannot convert from BSON type Object to Date".
In summary, my current code works but "group" based on the DateTime (instead of just Date) and it ends up separate counts for one particular day. I am curious whether or not it is achievable. Unknown is the mongo part to me as I haven't figured out how to do this.(even in a raw mongo query).
Just some additional information to clarify, I have the following data annotation for the datetime object in C# (not sure if this affects):
[BsonDateTimeOptions(Representation = BsonType.Document)]
public DateTime CreatedDate {get; set; }
One solution in my mind is whether it's possible to project the "createddate" field on the fly and format it as "Y-m-d" and do the grouping based on the projected field.
I tried to add as many details as I can and the work I have done so far, just to make it clearer. I hope this didn't cause any confusion. I'd appreciate any help/suggestion that would help me to produce the result I want. Thanks!
I was able to fix it, according to #chridam 's comment. Thanks again!
I am writing the solution below, just in case, if someone ever runs into the same problem I did.
I changed my query so that I became:
var group = collection.Aggregate()
.Match(filter)
.Group(new BsonDocument { { "_id", new BsonDocument { { "month", new BsonDocument("$month", "$createddate.DateTime") }, { "day", new BsonDocument("$dayOfMonth", "$createddate.DateTime") }, { "year", new BsonDocument("$year", "$createddate.DateTime") } } }, { "count", new BsonDocument("$sum", 1) } })
.ToListAsync().Result;
This gave me a serialized object. Then I deserialized it into the custom class I had:
var grouped = group.Select(g => BsonSerializer.Deserialize<RootObject>(g));
Here is the custom class definition which will be a bit polished-up:
public class Id
{
public int month { get; set; }
public int day { get; set; }
public int year { get; set; }
}
public class RootObject
{
public Id _id { get; set; }
public int count { get; set; }
}
I hope this will be useful. Thanks! :)

Serialize/Deserialize Json DateTime in Neo4jClient

I use Neo4jClient to use Neo4j, I use cypher code for CRUD entity , Follow code :
_graphClient.Cypher.Merge("(n:Movie { Id:101 })")
.Set("n.Key = 55,n.DateTime='" +DateTime.UtcNow.ToString()+"'").ExecuteWithoutResults();
_graphClient.Cypher
.Match("(n:Movie)-[r:RelName]-(m:Movie)")
.Where((EntityNode n) => n.Id == 20)
.Return.......
public class EntityNode
{
public int Id { get; set; }
public string Key { get; set; }
public DateTime DateTime { get; set; }
}
ERROR :Neo4j returned a valid response, however Neo4jClient was unable to deserialize into the object structure you supplied.Can't deserialize DateTime.
On other hand i use jsonconvertor in different ways, for example :
_graphClient.Cypher.Merge("(n:Movie { Id:101 })")
.Set("n.Key = 55,n.DateTime=" +JsonConvert.SerializeObject(DateTime.UtcNow)).ExecuteWithoutResults();
I still have the ERROR
Pass it as a proper parameter:
graphClient.Cypher
.Merge("(n:Movie { Id:101 })")
.Set("n.Key = {key}, n.DateTime = {time}")
.WithParams(new {
key = 55,
time = DateTimeOffset.UtcNow
})
.ExecuteWithoutResults();
This way, Neo4jClient will do the serialization for you, and you don't introduce lots of security and performance issues.
This is in the doco here: https://github.com/Readify/Neo4jClient/wiki/cypher#parameters
I have faced the same issue recently its because of date time value coming from neo.
I have stored the date time in neo as epoch time but while retrieving i used long in the class. because of this its given me the above error.
Try using string for the above date time.
Hope this helps you.

Unable to filter on Date fields in MongoDB using C# Driver

I am manually serializing a number of POCOs using Newtonsoft JsonTextWriter and saving the result as a MongoDB BsonDocument.
//
// POCO to store in MongoDB
public class Session
{
public DateTime? StartUTCTimestamp { get; set; }
public DateTime? StartTimestamp { get; set; }
public DateTime? EndTimestamp { get; set; }
public void ToJSON(ref JsonTextWriter writer)
{
Session session = this;
writer.WriteStartObject(); // {
writer.WritePropertyName("StartUTCTimestamp");
writer.WriteValue(session.StartUTCTimestamp);
writer.WritePropertyName("StartTimestamp");
writer.WriteValue(session.StartTimestamp);
writer.WritePropertyName("EndTimestamp");
writer.WriteValue(session.EndTimestamp);
writer.WriteEndObject(); // }
}
}
A method in a test application which is used to import the data, retrieves all the Session objects from a SQL Server database (using Telerik's Open Access ORM) storing the results in an List. I serialize each Session by calling the ToJSON() method defined on the POCO (see above), passing in a reference to a JsonTextWriter. The resulting JSON string is then deserialized, using the C# MongoDB driver, into a BsonDocument which is then saved to Mongo. (The example below is from an ASP.NET Web Forms page, hence the alert box user controls).
private void LoadData()
{
DateTime startDate = new DateTime(2015,12,31,23,59,59);
var collection = _database.GetCollection<BsonDocument>("sessions");
using (DbContext ctx = new DbContext())
{
List<Session> sessions = ctx.Sessions.Where().ToList();
foreach (Session item in sessions)
{
JsonTextWriter writer = null;
try
{
StringWriter sw = new StringWriter();
writer = new JsonTextWriter(sw);
writer.CloseOutput = true;
item.ToJSON(ref writer);
String json = sw.ToString();
BsonDocument doc = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(json);
collection.InsertOne(doc);
this.ucAlertMsg.Show("bg-info", "Completed without exception");
}
catch (Exception ex)
{
while (ex.InnerException != null) { ex = ex.InnerException; }
this.ucAlertMsg.Show("bg-danger", ex.Message);
}
finally
{
writer.Close();
}
}
}
}
This is saving the document just fine, however, I am unable to effectively query the documents, filtering on a date range. I believe, based on several other posts and articles that I have read, that this may be because the value is stored as a string instead of an "ISODate()".
//
// This is what IS saved
{
"_id" : ObjectId("5729128cd9017a248cbe6284"),
"StartUTCTimestamp" : "2015-12-15T23:24:06",
"StartTimestamp" : "2015-12-15T18:24:06",
"EndTimestamp" : "2015-12-15T18:26:59",
}
//
// Is this what I need?
{
"_id" : ObjectId("5729128cd9017a248cbe6284"),
"StartUTCTimestamp" : ISODate("2015-12-15T23:24:06"),
"StartTimestamp" : ISODate("2015-12-15T18:24:06"),
"EndTimestamp" : ISODate("2015-12-15T18:26:59"),
}
In my LoadData() method I have tried a number of configurations on the TextWriter which, according to some of the articles I have read seems like it should have helped ...
StringWriter sw = new StringWriter();
writer = new JsonTextWriter(sw);
writer.DateFormatHandling = DateFormatHandling.IsoDateFormat;
writer.CloseOutput = true;
Assigning the "IsoDateFormat" to the "DateFormatHanding" setting on the writer produced no difference. I also tried "MicrosoftDateFormat" and the data was still stored as a string but the format was different.
The actual question
So that's all the set up ... the question is "how do I search MongoDB documents based on date"?
Using the C# driver for MongoDB allows me to search using Linq. Here's the linq query I am using.
IMongoCollection<Session> collection = database.GetCollection<Session>("sessions");
DateTime startDate = (this.StartDate.HasValue) ? this.StartDate.Value : DateTime.Now.AddDays(-7);
DateTime endDate = (this.EndDate.HasValue) ? this.EndDate.Value : DateTime.Now;
var data = collection.Find<Session>(e => e.StartTimestamp.Value >= startDate && e.StartTimestamp.Value <= endDate).ToList();
Since the JSON maps directly back to a Session POCO I should be able to use that type (?). I can successfully filter on other fields in the Session POCO. It's just the dates that are giving me fits.
I am guessing that there is either something amiss or an oversight in my implementation or that since the data is being stored as a string it's not able to be compared as a Date(?).
Any insight would be greatly appreciated.
Thanks,
-G
The date in your filter expression must be created using BsonValue.Create(yourDate);
Example:
IMongoDatabase db = GetMongoDbConnection();
IMongoCollection<BsonDocument> collection = db.GetCollection<BsonDocument> ("yourCollectionName");
DateTime date = BsonValue.Create(DateTime.Now.Date);
var filter = Builders<BsonDocument>.Filter.Gt("dateFieldToFilterOn", date);
List<BsonDocument> bsonDocuments = await collection.Find(filter).ToListAsync();
Not many people have seen this post so perhaps I have tagged it poorly, but for the one person who upvoted it I did come accross what I regard more as a "work around" than an actual solution but perhaps it will help you as well.
Instead of, or in addition to saving dates in the typical String format ("yyy/MM/dd HH:mm:ss"), I saved the dates as Int64 ticks. The parser/writer will recognize these as numbers and store them as such. And numbers are, of course, easily sorted and ordered.
So the Session model from the original post now looks like this ...
//
// POCO to store in MongoDB
public class Session
{
public DateTime? StartUTCTimestamp { get; set; }
public DateTime? StartTimestamp { get; set; }
public DateTime? EndTimestamp { get; set; }
//
// If the StartUTCDate is defined then return the number of "ticks" in the date
[BsonElement("StartUTCTimestampTicks")]
public Int64? StartUTCTimestampTicks
{
get
{
return (this.StartUTCTimestamp.HasValue) ?
(Int64?)this.StartUTCTimestamp.Value.Ticks :
null;
}
}
//
// If the StartDate is defined then return the number of "ticks" in the date
[BsonElement("StartTimestampTicks")]
public Int64? StartTimestampTicks
{
get
{
return (this.StartTimestamp.HasValue) ?
(Int64?)this.StartTimestamp.Value.Ticks :
null;
}
}
//
// If the EndDate is defined then return the number of "ticks" in the date
[BsonElement("EndTimestampTicks")]
public Int64? EndTimestampTicks
{
get
{
return (this.EndTimestamp.HasValue) ?
(Int64?)this.EndTimestamp.Value.Ticks :
null;
}
}
public void ToJSON(ref JsonTextWriter writer)
{
Session session = this;
writer.WriteStartObject(); // {
if (session.StartUTCTimestamp.HasValue)
{
writer.WritePropertyName("StartUTCTimestamp");
writer.WriteValue(session.StartUTCTimestamp);
writer.WritePropertyName("StartUTCTimestampTicks");
writer.WriteValue(session.StartUTCTimestampTicks);
}
if (session.StartTimestamp.HasValue)
{
writer.WritePropertyName("StartTimestamp");
writer.WriteValue(session.StartTimestamp);
writer.WritePropertyName("StartTimestampTicks");
writer.WriteValue(session.StartTimestampTicks);
}
if (session.EndTimestamp.HasValue)
{
writer.WritePropertyName("EndTimestamp");
writer.WriteValue(session.EndTimestamp);
writer.WritePropertyName("EndTimestampTicks");
writer.WriteValue(session.EndTimestampTicks);
}
writer.WriteEndObject(); // }
}
}
I've added properties exposing the number of ticks in each date, if defined. If not they simply return null. I did have to decorate them with the BsonElement attribute to make them recognizable to the JsonTextWriter (Not sure why these needed it and the other properties did not), otherwise I received an error.
I also modified the ToJSON() method on the model to check that the dates were provided before serializing the values. If the date is not defined then we just don't add the json element to the document.
The resulting document stored in MongoDB now looks like ...
{
"_id" : ObjectId("572b4486d9016b151846b8ed"),
"StartUTCTimestamp" : "2016-04-24T17:02:12",
"StartUTCTimestampTicks" : NumberLong(635971141320000000),
"StartTimestamp" : "2016-04-24T13:02:12",
"StartTimestampTicks" : NumberLong(635970997320000000),
"EndTimestamp" : "2016-04-24T13:05:16",
"EndTimestampTicks" : NumberLong(635970999160000000)
}
Why these values are being stored with a "NumberLong()" datatype function and the dates are not stored with the "ISODate()" function is a mystery to me, but it now allows me to query dates and date ranges based on Ticks rather than date strings.
IMongoCollection<Session> collection = database.GetCollection<Session>("sessions");
DateTime startDate = (this.StartDate.HasValue) ? this.StartDate.Value : DateTime.Now.AddDays(-7);
DateTime endDate = (this.EndDate.HasValue) ? this.EndDate.Value : DateTime.Now;
var data = collection.Find<Session>(e => e.StartTimestampTicks.Value >= startDate.Ticks && e.StartTimestampTicks.Value <= endDate.Ticks).ToList();
This actually has me considering NOT saving the date strings at all. I am preserving them more as labels because they are more readable when viewing the actual JsonDocument. But, as long as the primary use of the document is to be viewed through a user interface, it's a trivial matter to convert Ticks to more readable date string representations for purposes of viewing.
Anyway ... I hope this help ... someone ... :)

Categories