Serialize/Deserialize Json DateTime in Neo4jClient - c#

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.

Related

Find documents in mongodb by Date field + some date value

I Need some help with C# Mongo Driver and mongodb.
C# class example:
public class Document
{
public string Id { get; set; }
public DateTime StartTime { get; set; }
public TimeSpan CustomPeriod { get; set; }
}
I need a way to find documents in mongodb by StartTime field + some TimeSpan value, like this predicate:
Expression<Func<Document, bool>> customExpression = x
=> x.StartTime.Add(x.CustomPeriod) <= DateTime.UtcNow;
These predicates are not working and I am getting an error when executing Collection.Find() query now:
{document}{StartTime}.Add({document}{CustomPeriod}) is not supported.
The issue is the query language used by the underlying find command at the MongoDB instance does not support comparing one field in a document with another, or performing operations on these fields before comparison.
The $expr operator permits using aggregation expressions, but you usually forfeit the ability to use an index for that portion of the query.
In the mongo shell that might look like:
db.collection.find({
$expr:{
$gte:[
new Date(),
{$add: [ "$StartTime", "$CustomPeriod"]}
]
}
})
I'm not familiar with C#, so I don't know how to express that using the .net driver

Creating index in ravendb with Nodatime's LocalDate

I hope somebody can help me.
I'm using Ravendb with the Nodatime bundles, and so far I didn't have any problem with it until I wanted to use some nodatime methods during index creation.
Product:
public class Product {
public string Id { get; set; }
public LocalDate ReleasedDate { get; set; }
// and more properties...
}
My index creation (AbstractMultiMapIndexCreationTask):
AddMap<Product>(Product =>
from product in products
let dateTimeUTc = DateTimeZone.Utc.AtStartOfDay(product.ReleasedDate)
let timeTicks = dateTimeUTc.ToInstant().Ticks
let hash = $"{product.Id}|{timeTicks}"
select new IndexEntry
{
Hash = hash,
Usage = 0
});
With that the index is being generated, the ticks are there, nothing is wrong except that I get an indexing error for each index record in ravendb:
The best overloaded method match for 'NodaTime.DateTimeZone.AtStartOfDay(NodaTime.LocalDate)' has some invalid arguments
Does anybody know why is that?

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! :)

Mongodb c# driver and ISODate

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; }
}

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