Mongo single upsert to add or update a field in c# - c#

I was trying to do something that it would be nice if it was supported, but appears not to be.
I would like to be able to update a record if it exists, and if not set the field to a default value. Specifically, I am using .AddToSet() to append an object to a list.
This will not work:
UpdateBuilder<TestDocument> update = Update<TestDocument>
.Set(d => d.UpdatedOn, DateTime.Now)
.SetOnInsert(d => d.ListOfItems, new List<Item> () {ItemToAdd})
.AddToSet(d => d.ListOfItems, ItemToAdd);
TestCollection.Update(query,update, UpdateFlags.Upsert);
What I would expect:
On an update just add the record to the end of the existing list.
If it doesn't exist, have it do one of the following:
Set the field to the value, and ignore the update.
Set the field to the value, and then do the update. (so, I would change the code to do
SetOnInsert (d => d.ListOfItems, new List< Item> ())
But instead, it throws an error that the field is specified twice.
I could (and have) fixed this by doing a search first, if not found, do a upsert with the first record. If that returns 0 documents affected (someone else upserted it.) then I fall back and do the upsert - which of course, is also done if the document exists.
Is there a cleaner way? Or ideally, doing it in a single command?

SetOnInsert is only necessary when you want something to only apply on insert. In this case, you want the AddToSet to apply whether it's an update or an upsert. So, if you just use AddToSet, it will accomplish what you want.

Related

Best way to update a collection of SQL data

I am writing a .net/entity framework code snippet that's supposed to update/delete a bunch of MS SQL rows with the latest data passed from UI.
Say the table originally has 20 rows and the latest collection contains 15 records. Out of the 15, 9 have changes and 6 remain the same. So 9 rows will be updated, and the 5 rows that are not in the latest collection, will be deleted from the table.
So my question is, what's the best way of doing this: If I iterate over all 20 rows and try to find each of them, it would be O(mn). Deleting all table rows and re-insert them may be faster but I am not sure.
All help appreciated!
So you have a user interface element filled with items of some class. You also have a database with a table filled with items of the same class.
IEnumerable<MyClass> userInterfaceElements = ...
IQueryable<MyClass> databaseElements = ...
Note: the query is not executed yet!
You want to update the database such, that after your update your database contains the items from your user interface elements.
User interface elements that are not in the database yet will be added
Database elements that are not in the user interface need to be removed
User interface elements that are also in the database need to be updated.
You didn't write how you decide whether a user interface element is also in the database.
Let's assume you don't invent primary keys. This means that elements with a default value (zero) for your primary key are elements that are not in the database.
var itemsToAdd = userInterfaceElements.Where(row => row.Id == 0);
var itemsToUpdate = userInterfaceElements.Where(row => row.Id != 0);
var idsItemsToKeep = itemsToUpdate.Select(row => row.Id);
var itemsToRemove = databaseElements.Where(row => !idsItemsToKeep.Contains(row.Id))
The last one: remove all items that have an Id that is not in your user interface elements anymore.
Note: we still have not executed any query!
Adding the items to your database will change databaseElements, so before you make any changes you need to materialize the items
var addList = itemsToAdd.ToList();
var updateList = itemsToUpdate.ToList();
var removeList = itemsToRemove.ToList();
By now you've queried your database exactly once: you fetched all items to remove. You can't order entity framework to remove items without fetching them first.
dbContext.MyClasses.RemoveRange(removeList);
dbContext.MyClasses.AddRange(addList);
To update in entity framework, the proper method would be to fetch the data and then change the properties.
Some people prefer to attach the items to the dbContext's change tracker and tell that it is changed. This might be dangerous however, if someone else has changed some properties of these items, especially if you don't show these values in your user interface elements. So only do this if you really have a long list of items to update.
Proper way:
foreach(var itemToUpdate in updateList)
{
var fetchedItem = dbContext.MyClasses.Find(itemToUpdate.Id);
// TODO: update changed properties of the fetchedItem with values from itemToUpdate
}
Dangerous method:
foreach(var itemToUpdate in updateList)
{
dbContext.Entry(itemToUpdate).State = entityState.Modified;
}
Finally:
dbContext.SaveChanges();
Improved delete method
You've got a problem when you filled your user interface element with database values, and some other process removed one of these values from your database.
When your code looks at the primary key, it will think it is in the database, however it is not there anymore. What to do with this element? Add it again? Act as if the user also wanted it to be deleted?
To solve this kind of problems, quite often people don't delete items from their database, but declare them obsolete instead. They add a Boolean column to the table that indicates whether the item is to be deleted in the near future. This solves the problem that people want to update items while others want them to be removed.
Regularly, every month or so, a process is started to remove all obsolete objects. The chance that you want to update an obsolete object are much lower.
If this needs to be full save: don't remember a Boolean obsolete, but the obsolete date. Periodically remove all items that are obsolete for a longer time.
The nice thing about the obsolete, is that if someone declared an item obsolete by accident, there is still some time to repair this.

Is an upsert in mongodb atomic with the filter and the actual update

I have a document I want to upsert. It has a unique index on one of the properties, so I have something like this to ensure I get no collisions
var barVal = 1;
collection.UpdateOne(
x=>x.Bar == barVal,
new UpdateDefinitionBuilder<Foo>().Set(x=>x.Bar, barVal),
new UpdateOptions { IsUpsert = true });
But I seem to sometimes get collisions from this on the unique index on bar.
Is mongo atomic around upserts, so if the filter matches the document cant be changed before the update completes?
If it is I probably have a problem somewhere else, if its not I need to handle the fact its not.
The docs don't seem to sugguest that this is one way or the other.
https://docs.mongodb.com/v3.2/reference/method/Bulk.find.upsert/
https://docs.mongodb.com/v3.2/reference/method/db.collection.update/
Actually, docs says something about this. Here is what I found in db.collection.update#use-unique-indexes
To avoid inserting the same document more than once, only use upsert:
true if the query field is uniquely indexed.
...
With a unique index, if multiple applications issue the same update
with upsert: true, exactly one update() would successfully insert a
new document.
The remaining operations would either:
update the newly inserted document, or
fail when they attempted to insert a duplicate.
If the operation fails because of a duplicate index key error,
applications may retry the operation which will succeed as an update
operation.
So, if you have created a unique index on the field you are querying, it is guaranteed that the insertion is "atomic" and a sort of rollback is performed if a failure occures.

Inserting Documents into MongoDB and ignore the duplicate documents

I am trying to insert documents into MongoDB but I want to have only unique documents and whenever encounter a duplicate document, just ignore it if it is already exists and go to the next one. I am using the following code but apparently it does not work.
var keys = IndexKeys.Ascending("TrackingNumber");
var options = IndexOptions.SetUnique(true).SetDropDups(true);
_collection.CreateIndex(keys, options);`
If you really want to ignore these, it's probably best to do it in code, though that might not be that easy in a multi-client environment.
The dropDups flag is a parameter of the index creation only, so it will drop duplicates it finds while creating the index. The flag will be ignored for inserts afterwards, because it's not even a parameter of the index.
A better way, though not exactly the behavior you're looking for, is to use upserts, i.e. operations that insert a document if not yet present and update it if the document that was searched existed before. That has the advantage of being an idempotent operation (which the ignore strategy is not).

in MongoDB how do I update a list of key/value pairs c#

In MongoDB, accessing from C# driver:
I want to keep a list of keys (ints are fine), that have a current value. (Dictionary<int,int>) works well for the concept)
I need to have multiple (10+) machines setting values in this Document. Multiple threads on each machine.
Using C# and the MongoDB driver I need to:
If the key exists, increment the value for that key.
If it does not exist, I need to Add it, and set the value to 1.
Critically Important:
It can't overwrite values others are writing (I.E. No get doc and call Save() to save it)
It has to handle adding values that don't already exist gracefully.
I might be able to have a query that inserts a new document with all of the keys set to values of 0 - if this would help, but it won't be easy, so that is not a preferred answer.
I've tried using a Dictionary, and can't seem to figure out how to update it without it creating:
null,
{ v=1}
On my insert (which doesn't include the k=, and has the null, that I don't want. and causes deserialization to blow)
I don't care what method of serialization is used for the dictionary, and am open to any other method of storage.
Any ideas?
My best guess so far is to keep a list of keys that is separate from the values (two List and append the key to the key list if it isn't found, requery the list and use the first one as the index in the 2nd list. (This seems like it might have concurrency issues that could be hard to track down.)
I would prefer the Linq syntax, but am open to using the .Set (string,value) syntax if that makes things work.

how to do automatic insert default value or update ignore in MongoDB

I got a problem that use Upsert operation.
i have two date fields in the table,"created" and "updated",i use update operation that do INSERT operation when one record doesn't exist,otherwise do UPDATE operation.
when first time insert,the "created" field and "update" field both automatic set current date,when next time update,"the created" field ignore,update "update" field.
but i can't in one statement do that.Here is my code:
colls.Update(Query.EQ("_id", page.Id),
Update.Set("created", page.Created)//in here,how to??
.Set("updated", page.LastUpdated)
.Inc("freq", 1), UpdateFlags.Upsert, SafeMode.False);
my serialize code:
cm.MapProperty<DateTime>(c => c.Created).SetElementName("created")
.SetSerializationOptions(datetimeSerializationOptions)
.SetDefaultValue(DateTime.Now);
cm.MapProperty<DateTime>(c => c.LastUpdated).SetElementName("updated")
.SetSerializationOptions(datetimeSerializationOptions);
Currently mongodb doesn't support "upsert"s which behaves differently if its an insert or an update. but its on their plan in jira. So eigther you do a check if the record exists before deciding on insert or update (simply use multi query); or wait for the new version of mongodb.

Categories