I have a very simple query for mongo:
db.items.find( { MyFieldName: { $exists: true, $eq: null } } );
Not that it needs to be explained, but it finds documents which have a MyFieldName and where the value of that field is null. It seems like this would be really simple to do with the C# driver:
var fieldExistsFilter= Builders<BsonDocument>.Filter.Exists("MyFieldName", true);
var fieldValueIsNullFilter = Builders<BsonDocument>.Filter.Eq("MyFieldName", null);
However the second filter, fieldValueIsNullFilter, does not build if I try to check for null. It works fine if I write "testString" or anything like that, but not with null.
tl:dr; version: How do I create a filter to check if field is null in MongoDb C# driver?
Note, I checked other answers and they recommend $exists does what I want - it does not, as per mongo docs:
When is true, $exists matches the documents that contain the
field, including documents where the field value is null. If
is false, the query returns only the documents that do not contain the
field.
This actually works as expected with a little modification, which depends on:
BsonNull.Value
See this question for details:
How to create Bson Document with Null value using C# official driver?
So the query is:
var fieldValueIsNullFilter = Builders<BsonDocument>.Filter.Eq("MyFieldName", BsonNull.Value);
Initially, I didn't realize BsonNull has a Value property.
In addition to the comment above you can write like this if you have some entity (depends on the property type):
public class CustomEntity
{
public string Id { get; set; }
public string StringProperty { get; set; }
public DateTime? DateTimeProperty { get; set; }
}
var filterIfStringPropertyNull = Builders<CustomEntity>.Filter.Eq(o => o.StringProperty, null); // if property is string
var filterIfDatePropertyNull = Builders<CustomEntity>.Filter.Eq(o => o.DateTimeProperty, BsonNull.Value.ToNullableUniversalTime()); // if property is DateTime?
And so on.
It could be easier
Related
I have a request object for my API and I want to enforce that my properties are always present in the body. Here is my object:
public class Code
{
[JsonProperty("id", Required = Required.Always)]
public string id { get; set; }
[JsonProperty("type", Required = Required.Always)]
public string type { get; set; }
}
However, when I don't pass in the type property in my request body, my Code object is null. Instead I would want a bad request error to get propagated back to the client. Will the Newtonsoft decorator not do that for me here? Or would I have to manually add checks to see if the properties are not null?
This following code throws for me as expected:
string serialized = #"{ 'noId': '123' }";
Code deserialized = JsonConvert.DeserializeObject<Code>(serialized);
Console.WriteLine(deserialized.Id);
My code class:
class Code
{
[JsonProperty("id", Required = Required.Always)]
public string Id { get; set; }
}
Can you confirm Newtonsoft.Json is used? If you are using ASP.NET Core 3.x or above, please refer to How to use Newtonsoft.Json as default in Asp.net Core Web Api? to set your project up to use Newtonsoft.Json.
Fluent Validation is the solution for you. So, you don't have to use JsonProperty attributes.
Usage:
First, create a validator for your class.
public class CodelValidator : AbstractValidator<Code>
{
public CodelValidator()
{
RuleFor(x => x.id).NotEmpty().WithMessage("id is required.");
RuleFor(x => x.type).NotEmpty().WithMessage("type is required.");
}
}
Inside your controller method:
public ActionResult TestMethod(Code code)
{
var validator = new CodeValidator();
var validationResult = await validator.ValidateAsync(code);
if (validationResult.IsValid == false)
{
var errorMessages = validationResult.Errors.Select(s => s.ErrorMessage);
// manage how you want to show the erros.
}
...
}
So, as you get all the errors. Now you can show however you want.
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1064
Currently the value for JsonProperty.Required only determines if the value is required - it does not allow you to indicate that a value may or may not be null.
Also in looking at the code it looks like all empty strings get converted to null
https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L282
Here is the C# query I have:
public Task<UserStats> GetStatsForUserWithId(string userId, DateTime? cutoffDate = null)
{
var idFilter = Builders<UserStats>.Filter.Eq(s => s.UserId, userId);
var cutoffDateFilter = Builders<UserStats>.Filter.Lt(s => s.Timestamp, cutoffDate ?? DateTime.MaxValue);
return _stats
.Find(idFilter & cutoffDateFilter)
.SortByDescending(stats => stats.Timestamp)
.FirstOrDefaultAsync();
}
Here is the equivalent MongoDB query I'm trying to replicate - this works:
db.getCollection('stats').find({
"UserId": "5ca13b15d74d5633c45a3304",
"Timestamp": { $lt: ISODate("2022-04-01T02:17:04+0000") }
}).sort({
"Timestamp": -1
}).limit(1)
Here is the entity being stored, in case it matters:
public class UserStats
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string UserId { get; set; }
public Stats1V1 Stats1V1 { get; set; }
public Stats2V2 Stats2V2 { get; set; }
public StatsOverall StatsOverall { get; set; }
public DateTime Timestamp { get; set; }
}
The problem: When I run the C# query, I get no results. The MongoDB query works fine, suggesting there is some difference between the two that I'm not seeing. What is it?
Here's a sample object of what is stored in the DB, with the non-relevant fields omitted to keep things short:
{
"_id" : ObjectId("5ca167ac8c7e7250843553a4"),
"UserId" : "5ca13b15d74d5633c45a3304",
"Stats1V1" : {
// omitted a bunch of fields
},
"Stats2V2" : {
// omitted a bunch of fields
},
"StatsOverall" : {
// omitted a bunch of fields
},
"Timestamp" : ISODate("2019-04-01T01:21:48.861Z")
}
EDIT: I should point out that the query worked perfectly before the cutoff date filter was added. The sorting, Id filter, etc. worked before, everything stopped working when I added the new cutoff date filter.
So, I solved the problem with the help from a coworker.
It turns out it was actually caused by the way C# Web API controller methods handle query parameters that are not specified. I had assumed (being new to .NET) that like any function parameter in C# that isn't provided, it defaults to null. However, for query parameters, this is apparently not the case - it assumes a default value of the beginning of our calendar.
I was coalescing null values into some reasonable date in the future, but this operation would never execute because the variable was never actually null.
I should have discovered this when I stepped through it in the debugger, but I apparently missed it somehow, not sure how.
However, I wanted to share the information that helped me discover the cause of the problem - I used the profiling tools built into MongoDB to track down the cause.
By running the following lines in the MongoDB shell:
db.setProfilingLevel(0)
db.system.profile.drop()
db.createCollection( "system.profile", { capped: true, size:4000000 } )
db.setProfilingLevel(2)
to enable profiling, running my API call, and then running the following line in the Mongo shell:
db.system.profile.find().pretty()
I was able to receive this output (shortened to the important part):
"command" : {
"find" : "stats",
"filter" : {
"Timestamp" : {
"$lt" : ISODate("0001-01-01T00:00:00Z")
}
// omitted
Notice that the timestamp here is in the year 0001. That lead to me investigating how it got that value, which was eventually revealed by the debugger.
Hopefully this approach can help someone else in the future.
I'm new to MongoDB so this might be a naive question, yet I have not found any relevant/up to date information by googling around: I am trying to use the MongoDB C# driver (version 2.2.4) to compose a LINQ-based query, one piece at a time, from a received filter POCO object, like this:
IQueryable<BsonDocument> parts = collection.AsQueryable();
if (filter.Name != null)
parts = parts.Where(d => d["Name"].Equals(filter.Name));
// ... etc; I'll then call ToList() to get results ...
Now, one of my filter properties is a string array, meaning that I should match any document whose field Vendor (a string property in the MongoDB document) is equal to any of the strings in the array (like MongoDB native $in: https://docs.mongodb.com/manual/reference/operator/query/in/).
To this end, I tried with Contains (the special case for a 1-sized array is just an optimization):
if (filter.Vendors != null && filter.Vendors.Length > 0)
{
parts = filter.Vendors.Length == 1
? parts.Where(d => d["Vendor"].Equals(filter.Vendors[0]))
: parts.Where(d => filter.Vendors.Contains(d["Vendor"].AsString));
}
This compiles, but throws an ArgumentException: "Expression of type 'MongoDB.Bson.BsonValue' cannot be used for parameter of type 'System.String' of method 'Boolean Contains[String](System.Collections.Generic.IEnumerable`1[System.String], System.String)'".
Looking at http://mongodb.github.io/mongo-csharp-driver/2.2/reference/driver/crud/linq/, there is nothing about Contains or $in; yet, from https://jira.mongodb.org/browse/CSHARP-462 it seems that the driver should now be capable of handling that method.
BTW, the same happens if I slightly change the code to:
parts.Where(d => filter.Vendors.Any(s=> d["Vendor"].Equals(s)));
which does not involve Contains at all. The exception message complains about not being able to use BsonValue for string, yet that BsonValue is right a string. Could anyone suggest a solution?
The exception messages dance around the idea of fully embracing BsonValue to let mongo handle the types instead of trying to cast to string. I got it to work having Vendors as type List<BsonValue>.
class Filter
{
public List<BsonValue> Vendors { get; set; }
}
...
var list = parts.Where(d => filter.Vendors.Contains(d["Vendor"]));
foreach (var document in list)
{
Console.WriteLine(document["Name"]);
}
Another alternative is to map your documents to a C# class instead of using BsonDocument as the collection type.
class MyDocument
{
public ObjectId Id { get; set; }
public string Name { get; set; }
public string Vendor { get; set; }
}
...
var collection = db.GetCollection <MyDocument> ("parts");
I have the following POCO class in my app -
public class Course
{
public String Title { get; set; }
public String Description { get; set; }
}
But the Course collection in mongodb has some other fields also including those. I am trying to get data as follows-
var server = MongoServer.Create(connectionString);
var db = _server.GetDatabase("dbName");
db.GetCollection("users");
var cursor = Photos.FindAs<DocType>(Query.EQ("age", 33));
cursor.SetFields(Fields.Include("a", "b"));
var items = cursor.ToList();
I have got that code from this post in stackoverflow.
But it throws an exception-
"Element '_id' does not match any field or property of class"
I don't want '_id' field in my POCO. Any help?
_id is included in Fields by default.
You can exclude it by using something like:
cursor.SetFields(Fields.Exclude("_id"))
Use-case:
PHP symfony project which has to communicate with a C# back-end with Mongo. In the PHP front-end it is possible to make a query to get data from Mongo. This query is send via an API (XML). The C# back-end deserializes this XML to get the objects. Then I want to execute an Linq-to-objects query (which is the query send via the API) on a collection in my memory. So I wanted to make my own "LinqBuilder" so I can query the objects and return them to my PHP front-end.
I have the following object:
public class MongoDoc
{
public int Id { get; set; }
public string Kind { get; set; }
public BsonDocument Data { get; set; }
}
Below is an example of what I'm trying to achieve.
var list = source.Where(x => x.Data["Identifier"] == "H7PXXK").ToList(); // source is collection of MongoDoc objects
The code line above is what I want to build with expressions because it has to be dynamic. What I did achieve is to query the "Kind" property of my MongoDoc object as follow:
ParameterExpression _expr = Expression.Parameter(typeof(MongoDoc), "x");
expression = Expression.Equal(
Expression.PropertyOrField(_expr, "Kind"),
Expression.Constant("KindValue")
);
This will produce the following lamdba:
x => (x.Kind == "KindValue")
That is correct, but now I need to get the property Identifier in the BsonDocument property Data. Normally it would be something like above: x => x.Data["Identifier"] == "Value". This is exactly what my problem is. How can I achieve this?
think it should be something like that.
var _expr = Expression.Parameter(typeof(MongoDoc), "x");
//x.Data
Expression member = Expression.PropertyOrField(_expr, "Data");
//x.Data["Identifier"]
member = Expression.Property(member, "Item", new Expression[]{Expression.Constant("Identifier")});
//x.Data["Identifier"] == "H7PXXK"
member = Expression.Equal(member, Expression.Constant((BsonValue)"H7PXXK"));
EDIT :
from your comment, it should be
//x.Data["MoreData"]
member = Expression.Property(member, "Item", new Expression[]{Expression.Constant("MoreData")});
//x.Data["MoreData"]["Identifier"]
member = Expression.Property(member, "Item", new Expression[]{Expression.Constant("Identifier")});