zosim
zosim

Reputation: 2979

MongoDb c# driver update multiple documents in one atomic operation

I want to ask you if the following code will be executed in one atomic operation. I'm using mongodb c# driver.

The input to the method is list of id of the objects I want to update.

public void Update(IEnumerable<string> ids)
{
    var query = Query<T>.Where(t => ids.Contains(t.Id));
    var update = Update.Set("Modified", DateTime.Now); //this is just example of update

    var options = new MongoUpdateOptions {Flags = UpdateFlags.Multi};

    Collection.Update(query, update, options);
}

I'm interesting about the case, when I have milions of documents to update. What will happen if there will be a fail (power or hardware problem) during this update? Will be the database in a consistent state?

thanks.

Upvotes: 3

Views: 9181

Answers (2)

Hassan Faghihi
Hassan Faghihi

Reputation: 2021

Requirments

MongoDB >= 4.0 C# Driver >= 2.7 Here how i did it...

TL;DR

Goto "Sample Code"


Sessions

In my DbContext class, where i had access to my client (IMongoClient), i defined sessions:

public IClientSessionHandle StartSession()
{
    return _client.StartSession(new ClientSessionOptions());
}

public async Task<IClientSessionHandle> StartSessionAsync()
{
    return await _client.StartSessionAsync(new ClientSessionOptions());
}

And as documentation says, they are capable of executing multiple transactions one after another, if only these transactions are coming one after another

A session is used to group together a series of operations that are related to each other and should be executed with the same session options. Sessions are also used for transactions.

these session said that they should be closed as soon as you ending your operation...

so you should actually write it like following, or perform manual disposing depend on your scenario:

// db is what i named my context, where i defined all my collections and database related stuffs. 
// if you have direct access to client, you can call `StartSession/Async` exactly over the `client` object
using(var session = _db.StartSessionAsync()) {
    //... All transactional code will go here
} 
// Here, on ending the block, Will automatically call `Dispose` method, and the object will no longer exists

over session object

Transactions

Transactions are started, committed or aborted using methods of IClientSession. A session can only execute one transaction at a time, but a session can execute more than one transaction as long as each transaction is committed or aborted before the next one is started.

In this step you need to start a transaction before actually perform changes on database...

session.StartTransaction();

Once the session start, you should perform your transactions, and at the end...

If process is successful, you should call:

session.CommitTransaction();

Otherwise, you need to rollback

session.AbortTransaction();

Sample Code

As you can see i have two write operation on mongodb, and another external process which is critical in my case, and i need these three to be performed together,.. the first two are managed by transaction, and as long as the third doesn't throw exception, the database should keep its new state.

bool error;
using (var session = await _db.StartSessionAsync())
{
    session.StartTransaction();

    try
    {
        var deletedImage = _db.GetUserStore<ApplicationUser>().CollectionInstance.UpdateOneAsync(
            Builders<ApplicationUser>.Filter.Where(w => w.Id == userId),
            Builders<ApplicationUser>.Update.Pull(x => x.ProfilePictures, photoFromRepo));

        await _db.ProfilePicture().DeleteAsync(new ObjectId(photoFromRepo.ImageId));

        if (photoFromRepo.CloudinaryPublicId != null)
        {
            var deleteParams = new DeletionParams(photoFromRepo.CloudinaryPublicId);
            var result = _cloudinary.Destroy(deleteParams);
            if (result.Result == "ok")
            {
                // ignore
            }
            else
            {
                throw new Exception("Cannot delete file from cloud service...");
            }
        }

        await session.CommitTransactionAsync();
        error = false;
    }
    catch (Exception ex)
    {
        await session.AbortTransactionAsync();
        error = true;
    }
}

Does this work? Does it support multiple collections? Only god knows, i write this based on documentation and some sample i saw earlier today on my way home, and what i thought that may be right and possible ...

Extra Notes:

There are options that you can pass session, one of these option controls read/write concerns, and another one, control how much the data should be early before performing transactions (what it mean? i didn't got it myself, if you understand, please edit my post)

public class ClientSessionOptions
{
    public bool? CausalConsistency { get; set; }
    public TransactionOptions DefaultTransactionOptions { get; set; }
}

public class TransactionOptions
{
    public ReadConcern ReadConcern { get; };
    public ReadPreference ReadPreference { get; };
    public WriteConcern WriteConcern { get; };

    public TransactionOptions(
        Optional<ReadConcern> readConcern = default(Optional<ReadConcern>),
        Optional<ReadPreference> readPreference = default(Optional<ReadPreference>),
        Optional<WriteConcern> writeConcern = default(Optional<WriteConcern>));

    public TransactionOptions With(
        Optional<ReadConcern> readConcern = default(Optional<ReadConcern>),
        Optional<ReadPreference> readPreference = default(Optional<ReadPreference>),
        Optional<WriteConcern> writeConcern = default(Optional<WriteConcern>))
}

Upvotes: 1

dwursteisen
dwursteisen

Reputation: 11515

MongoDB do not support transaction or atomic multi documents. MongoDB perform atomic operation only on one document.

You can check this in the documentation of Mongodb

So if you update with your query 1000 documents and your server crash during this operation, some documents may be updated, other won't.

Upvotes: 0

Related Questions