[CSHARP-1805] Collection.ReplaceOne() with upsert flag does not auto-generate Id Created: 19/Oct/16 Updated: 08/Jan/18 Resolved: 08/Jan/18 |
|
| Status: | Closed |
| Project: | C# Driver |
| Component/s: | API |
| Affects Version/s: | 2.3 |
| Fix Version/s: | None |
| Type: | Bug | Priority: | Minor - P4 |
| Reporter: | Todd Behunin | Assignee: | Robert Stam |
| Resolution: | Done | Votes: | 0 |
| Labels: | None | ||
| Remaining Estimate: | Not Specified | ||
| Time Spent: | Not Specified | ||
| Original Estimate: | Not Specified | ||
| Environment: |
windows10, .net46, .netCore |
||
| Description |
|
Given an entity defined as such: public class Foo public string Bar { get; set; } } I want the ability to "upsert" a document with an object having that type, where a new record would have an auto-generated "_id". After executing the following: var foo = new Foo { Bar = "hello world" }; ); The mongo shell, however, reports the following: The _id inserted is equivalent to ObjectId.Empty. Even applying the [BsonIgnoreIfDefault] attribute atop the Id property has no effect. Expected Behavior: The .ReplaceOne() and .ReplaceOneAsync() methods would recognize that the Id has not been explicitly initialized and will "insert" with an auto-generated _id (when used in conjunction with the "upsert" flag) instead of performing, what it appears to be, an "update". |
| Comments |
| Comment by Robert Stam [ 08/Jan/18 ] | |||||||||||||
|
Just to reiterate, if the filter includes the _id then the server will always use that value as the _id of the new document if no existing document matches and the ReplaceOne call results in an upsert. This is true even if the value of the _id in the filter is null (or an empty ObjectId). It doesn't really make sense to try and do an upsert when your filter is { _id : null }. If _id is null you should just use InsertOne instead. | |||||||||||||
| Comment by Kevin Versfeld [ 01/Jun/17 ] | |||||||||||||
|
Same problem here: I am trying to use ReplaceOne as a single place to upsert items, which may or may not exist yet - and the only thing I have in code to know this is the Id. As a workaround, I'm basically doing a manual check against the Id on my side, and if it is null/empty, I pass in x => false as the filter predicate to ReplaceOne..... Watching this issue for a fix or alternative. | |||||||||||||
| Comment by Jeremy Stafford [ 25/Feb/17 ] | |||||||||||||
|
I'm having the same problem when I use a string type for the Id. The ID never gets generated and is inserted as null. Using 2.4, myself.
| |||||||||||||
| Comment by Todd Behunin [ 27/Oct/16 ] | |||||||||||||
The problem with that is, ObjectId is typed as a struct, which means it's not instantiated with the new keyword. Therefore, it will always have a "default" value unless explicitly assigned. In addition, correct me if I'm wrong, but the filter in the call to ReplaceOne() can't be null/empty. It is used to determine which record I intend to update (or insert). So, having a FilterDefinition of x => x.Id.Equals(foo.Id) should do the trick. However, it still gets inserted as all zeros, even if I add [BsonIgnoreIfDefault] to the ObjectId property. The use case scenario is, I have an object of type Foo (see description above). It has an Id property of type ObjectId. I don't know if it's in the db or not but I want it saved regardless - and have an automatic id generated. How does one call the ReplaceOne() or ReplaceOneAsync() to make this happen? | |||||||||||||
| Comment by Robert Stam [ 26/Oct/16 ] | |||||||||||||
|
When the filter includes the _id the server uses the _id value from the filter as the _id of the upserted document. Your C# code is equivalent to the following shell code (assuming you put [BsonIgnoreIfDefault] on the Id property):
which results in the following document being inserted (the collection was empty when I ran the shell command above):
If you want the server to generate a brand new ObjectId when the ReplaceOne results in an upsert then your filter should not include the _id. |