Search code examples
c#mongodblinqmongodb-.net-driver

Is there a way to get a nullable value from a MongoDB C# driver projection with expression and dictionary?


Lets say I have these classes:

public class Foo
{
   public IReadonlyDictionary<string, DateTime> Values { get; set; }
}

public class Bar
{
   public Foo Foo { get; set; }
}

Now if I do this projection:

var projection = Builders<Bar>.Projection.Expression(x => x.Foo.Values["key"])

I will get DateTime.MinValue and not a null in case the element key does not exist in the dictionary. The value itself indeed cannot be null (and I assume that is why MongoDB is doing default(DateTime)), but it can happen that it does not exist in the dictionary.

I am wondering if there is a good way to tell MongoDB that the result should be DateTime? so that I can get a null in case the element does not exist.

P.S. I can make the dictionary value DateTime?, and that will work but I would rather avoid it, since the Value itself should not ever be null.

Sample documents:

// With values
{
  "_t": "Bar",
  "Foo":
    {
      "1521": "2022-09-26T08:25:38.502+00:00"
    }
}

// Empty
{
  "_t": "Bar",
  "Foo":
    {
    }
}

Solution

  • The following query returns the values as DateTime?; if there is no Foo, it is null, if it is set, it returns the value:

    var bars = await (await barColl.FindAsync(
      Builders<Bar>.Filter.Empty, 
      new FindOptions<Bar, DateTime?>()
      {
        Projection = Builders<Bar>.Projection.Expression(
          x => (DateTime?) (x.Foo == null ? null : x.Foo.Value))
      })).ToListAsync();
    

    For the following documents

    { "_id": { "$oid": "632dc047e08f08bfaeabc2d6" }, "Foo": null}
    { "_id": { "$oid": "632dc047e08f08bfaeabc2d7" }, "Foo": { "Value": { "$date": {  "$numberLong": "1663942727428" } } }}
    

    the result is

    null
    2022-09-23T14:18:47Z
    

    Update: Dictionary

    If you want to project the DateTime from a Dictionary<string, DateTime>, you can use the following expression with a ternary operator that checks against default(DateTime):

    var projection = Builders<SoNullableDt>.Projection
      .Expression<DateTime?>(
        x => x.Foo["1521"] == default(DateTime) ? null : x.Foo["1521"]);
    var result = await (await coll.FindAsync(Builders<SoNullableDt>.Filter.Empty, 
      new FindOptions<SoNullableDt, DateTime?>() { Projection = p })).ToListAsync();
    

    Based upon the sample documents (with the datetime changed to a Date data type in MongoDB), this finds the following data:

    26.09.2022 08:25:38
    null