How to perform multiple operations in under single transaction - c#

I have a scenario where it requires to add a record in to table, then - creating a resource on the cloud if record is added, then update the record in table with the resource identifier if resource is created on cloud. So, they are 3 operations and I want to revert all of it when any of them doesn't succeed.
We have TransactionScope for Multiple Db Operations in one go but I'm wondering how to achieve this? Appreciate your help!
Edit
PS: There could be any number of operations like that - say 10 or more in a sequence, and they may not even related to DB operations. They could just be creating 10 files in a sequence - so when any of the file creation fails - all the previous files should be deleted/undone.

How about going a command pattern way? It's may not be perfect command pattern implementation but something very close. See below:
public interface ICommand {
ICommandResult Execute();
ICommandResult Rollback();
}
public interface ICommandResult {
bool Success { get; set; }
object Data { get; set; }
Exception Error { get; set; }
}
public class CommandResult : ICommandResult {
public bool Success { get; set; }
public object Data { get; set; }
public Exception Error { get; set; }
}
public class AddToDBCommand : ICommand {
private ICommandResult result;
private int newRecordId;
public AddToDBCommand(<params_if_any>) {
result = new CommandResult();
}
public ICommandResult Execute() {
try {
// insert record into db
result.Success = true;
result.Data = 10; // new record id
}
catch (Exception ex) {
result.Success = false;
result.Error = ex;
}
return result;
}
public ICommandResult Rollback() {
try {
// delete record inserted by this command instance
// use ICommandResult.Data to get the 'record id' for deletion
Console.WriteLine("Rolling back insertion of record id: " + result.Data);
// set Success
}
catch(Exception ex) {
// set Success and Error
// I'm not sure what you want to do in such case
}
return result;
}
}
Similarly you would create commands for creating cloud resource and updating record in db. In main code you can hold collection of ICommand objects and execute each one.
var commands = new List<ICommand>
{
new AddToDBCommand(<params_if_any>),
new AddToCloudCommand(<params_if_any>),
new UpdateInDBCommand(<param_if_any>)
};
Then in the loop you can call Execute, if it returns Success = false then record the current command index in collection and loop backward whilst calling Rollback on each command.

I assume you are using Azure as cloud.
So to support transactions you need to have -
1. Elastic database on Azure which supports transactions.
2. You need to have .NET framework 4.6.1 or higher to utilize distributed transaction.
I encourage you to go through https://learn.microsoft.com/en-us/azure/sql-database/sql-database-elastic-transactions-overview
Now in your case lets break 3 steps considering transaction scope is applied.
Add record to table -
If this fails then no worries I guess.
Create resource in cloud-
If this fails then Added record will be rolled back.
Update record in table with resource id created.
If this fails then 1 step will be rolled back.
After transaction scope is finished you need to check that record added in 3rd step exists. If it does not then you need to manually rollback resource creation by deleting it.

Related

Ensure blocking of database rows during the Entity Framework request

I have created service which communicates with my database. GetAvailableUserId service's method cannot be run simultaneously, because I don't want to return same user's id for two different calls. So far I have managed this:
public class UserService : IUserService
{
public int GetAvailableUserId()
{
using (var context = new UsersEntities())
{
using (var transaction = context.Database.BeginTransaction())
{
var availableUser = context.User
.Where(x => x.Available)
.FirstOrDefault();
if (availableUser == null)
{
throw new Exception("No available users.");
}
availableUser.Available = false;
context.SaveChanges();
transaction.Commit();
return availableUser.Id;
}
}
}
}
I wanted to test if service will work as intended, so I created simple console application to simulate synchronous requests:
Parallel.For(1, 100, (i, state) => {
var service = new UserServiceReference.UserServiceClient();
var id = service.GetAvailableUserId();
});
Unfortunately, It failed that simple test. I can see, that it returned same id for different for iterations.
Whats wrong there?
If I understood you correctly, you wan to lock method from other threads. If yesm then use lock:
static object lockObject = new object();
public class UserService : IUserService
{
public int GetAvailableUserId()
{
lock(lockObject )
{
// your code is omitted for the brevity
}
}
}
You need to spend some time and delve into the intricadies of SQL Server and EntityFramework.
Basically:
You need a database connection that handles repeatable results (which is a database connection string setting).
You need to wrap the interactions in EntityFramework within one transaction so that multiple instances do not possibly return the same result in the query and then make problems in the save.
Alternative method to achieve this is to catch DbUpdateConcurrencyException to check whether values in the row have changed since retrieving when you try to save.
So if e.g. the same record is retrieved twice. The first one to have the Available value updated in the database will cause the other one to thow concurrency exception when it tries to save because the value has changed since it was retrieved.
Microsoft - handling Concurrency Conflicts.
Add ConcurrencyCheck attribute above the Available property in your entity.
[ConcurrencyCheck]
public bool Available{ get; set; }
Then:
public int GetAvailableUserId()
{
using (var context = new UsersEntities())
{
try
{
var availableUser = context.User
.Where(x => x.Available)
.FirstOrDefault();
if (availableUser == null)
{
throw new Exception("No available users.");
}
availableUser.Available = false;
context.SaveChanges();
return availableUser.Id;
}
catch (DbUpdateConcurrencyException)
{
//If same row was already retrieved and updated to false, do not save, instead call the method again to get the next true row.
return GetAvailableUserId();
}
}
}

How to clear sqlite database on logout

I am using Xamarin Forms and the WindowsAzure.MobileServices.SQLiteStore NuGet to handle synchronization between my sqlite database and my azure database. Everything works fine, but when I want to logout my user I want to clean the database. This way on the next login, it will once again, regenerate the database and synchronize from zero. I have tried purging the tables but this only removes local data and when you log back in it will synchronize any new data only.
Currently my dispose does the following:
// Globally declared and initialized in my init() method
MobileServiceSQLiteStore store { get; set; }
public MobileServiceClient MobileService { get; set; }
public async Task Dispose()
{
try
{
initialized = false;
// await this.userTable.PurgeAsync<AzureUser>("CleanUsers", this.userTable.CreateQuery(), CancellationToken.None);
store.Dispose();
MobileService.Dispose();
store = null;
MobileService = null;
}
catch (Exception ex)
{
throw ex;
}
}
Any idea of how I can clean my sqlite database on logout using this component? Thanks!
If you want to purge all items, use:
this.userTable.PurgeAsync(null, null, true, CancellationToken.None);
See the code.

Adding a new record by using EntityFramework

I am creating a test project in order to learn using asp.net 5 and the mvc 6 framework.
I have decided to create a simple webpage that each menu item comes from the database. To do so I have created a model like such
namespace TestTemplate.Models
{
public class SideMenuItem
{
public int Id { get; set; }
public string Level { get; set; }
public string Label { get; set; }
public string Link { get; set; }
}
}
Inside my Models folder I also have a file named `TestContext.cs'
namespace TestTemplate.Models
{
public class TestContext : DbContext
{
public DbSet<SideMenuItem> SideMenuItems { get; set; }
}
}
That is my EntityFramework DbContext class.
When trying to create a new SideMenu item by using a simple view with a form to adding all the needed data, then using my angular factory that looks like this
return $resource('/api/sidemenu/:id');
I get the error:
An exception of type 'Microsoft.Data.Entity.DbUpdateException' occurred in EntityFramework.Core.dll but was not handled in user code
Additional information: An error occurred while updating the entries. See the inner exception for details.
-->System.Data.SqlClient.SqlException: Invalid object name 'SideMenuItem'.
This error occurs on SideMenuController.cs where I define my API at the part where I am trying to Post the new item:
[HttpPost]
public IActionResult Post([FromBody]SideMenuItem sideMenuItem)
{
if (sideMenuItem.Id == 0)
{
_dbContext.SideMenuItems.Add(sideMenuItem);
_dbContext.SaveChanges(); // ERROR HERE.
return new ObjectResult(sideMenuItem);
}
else
{
var original = _dbContext.SideMenuItems.FirstOrDefault(m => m.Id == sideMenuItem.Id);
original.Level = sideMenuItem.Level;
original.Label = sideMenuItem.Label;
original.Link = sideMenuItem.Link;
_dbContext.SaveChanges();
return new ObjectResult(original);
}
}
I also should mention that before running the app i used
>dnx ef migration add initial
>dnx ef database update
I believe it has to do with me not creating my database correctly. Since I am not seeing any folder on my project that had anything to do with databases or migrations.
Why is it complaining that SideMenuItem is invalid, and how can I fix the issue?
After trying to create my database again, I noticed that I had a typo on my migration command, hence the database was not created.
The command should have been dnx ef migrations ... with an s.
A good starting point with all the commands can be found here.

Cross system transactions

I am probably using the wrong term here (would love the proper name if someone knows it) but I regularly run into the issue of wanting to save something to the database + something else. In my current scenario I have the following code.
public class Stock
{
public Guid StockID { get; set; }
public string Name { get; set; }
public byte[] Image { get; set; }
}
IEnumerable<Stock> stock = new StockService().Get();
using (Database db = DBFactory.Create())
{
try
{
db.BeginTransaction();
db.InsertAll(stock);
foreach (Stock item in stock)
IsolatedStorage.SaveFile(item.Name, item.Image);
db.Commit();
}
catch
{
db.Rollback();
throw;
}
}
As you can hopefully tell what I'm doing is saving something to a database (in my case Sqlite) and to the IsolatedStorage on a Windows Phone device.
Now if the code as shown above fails it obviously leaves the IsolatedStorage in an inconsistant state. I can modify this code and delete any images from the IsolatedStorage in the catch block as such:
catch (Exception ex)
{
db.Rollback();
foreach (Stock item in stock)
IsolatedStorage.Delete(item.Name);
throw;
}
but I have run into this problem so many times and I can't help but feel there must be a better way. So is there some pattern that applies when you want to do something with a database in a transaction + do something else?
If you put the db.Commit(); before the foreach loop then any errors with the Commit() will be caught before the foreach gets executed. Only when Commit() is successful will the foreach loop run.

Performing an effcient upsert in mongodb

I have the following C# model class:
public class Thingy
{
public ObjectId Id { get; set; }
public string Title { get; set; }
public DateTime TimeCreated { get; set; }
public string Content { get; set; }
public string UUID { get; set; }
}
and the following ASP.MVC controller action:
public ActionResult Create(Thingy thing)
{
var query = Query.EQ("UUID", thing.UUID);
var update = Update.Set("Title", thing.Title)
.Set("Content", thing.Content);
var t = _collection.Update(query, update, SafeMode.True);
if (t.UpdatedExisting == false)
{
thing.TimeCreated = DateTime.Now;
thing.UUID = System.Guid.NewGuid().ToString();
_collection.Insert(thing);
}
/*
var t = _collection.FindOne(query);
if (t == null)
{
thing.TimeCreated = DateTime.Now;
thing.UUID = System.Guid.NewGuid().ToString();
_collection.Insert(thing);
}
else
{
_collection.Update(query, update);
}
*/
return RedirectToAction("Index", "Home");
}
This method either does an update or insert. If it needs to do an insert, it must set the UUID and TimeCreated members. If it needs to do an update, it must leave UUID and TimeCreated alone, but must update the members Title and Content.
The code that's commented out works, but does not seem to be most efficient. When it calls FindOne, that is one trip to mongodb. Then if it goes to the else clause, it does another query and an update operation, so that's 2 more trips to mongodb.
What is a more efficient way to do what I'm trying to accomplish?
As mentioned in the linked SO answer, for upserts to work, you need to update the entire document, not just a few properties.
Personally I would separate the Create and Edit into separate MVC actions. SRP. Creating a Thingy has different considerations from updating it.
If you still want to do an upsert instead of separate insert/update calls, you will need to use the following code:
_collection.Update(
Query.EQ("UUID", thing.UUID),
Update.Replace(thing),
UpsertFlags.Upsert
);
The question now becomes, how do we ensure the thing has the appropriate values for both cases, ie insert as well as update.
My assumption is (based on your code model binding to a Thingy instance), your view is sending back all fields (including UUID and TimeCreated). Which implies, in case of an update, the view already has the values pre-populated for UUID and TimeCreated. So in the case of a Thingy being updated, the thing object has the latest values.
Now in case of an create, when the view is rendered, you could store DateTime.MinValue for the TimeCreated field. In your Create MVC action, you could check if TimeCreated is DateTime.MinValue, then set it to current time and also store a new value for UUID.
This way, in the case of a insert as well, the thing has the latest values. We can thus safely do an Upsert.
I take this approach when doing upserts for Mongo from the controller
public ActionResult Create(Thingy model)
{
var thing = _collection.FindOneAs<Thingy>(Query.EQ("UUID", model.UUID));
if(thing == null)
{
thing = new Thingy{
TimeCreated = DateTime.Now,
UUID = System.Guid.NewGuid().ToString(),
Id = ObjectId.GenerateNewId()
}
}
else
{
thing.Content = model.Content;
//other updates here
}
_collection.Save<Thingy>(thing);
return View();
}

Categories