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.
Related
I am currently writing a NET 6 web API. I have to implement a method which saves a list of items. I wrote the following POST-Method to do that:
[HttpPost]
public IActionResult PostCustomer(List<Customer> customers)
{
foreach (var customer in customers)
{
SaveCustomer(customer);
}
return Ok();
}
The SaveCustomer() method makes a lot of validation and could throw an error. So it is possible, that a customer cannot be saved. If I am adding a try-catch around SaveCustomer(), all other customers are saved. But the response is not telling me, that one customer couldn't be saved because of an error. How can I create a correct response, like a warning?
Something like this: Warning: Customer x is not saved
you can return a list of failed customers in the response and add the failed customer object details to this (like id or name).
List<string> failedCustomers { get; set; }
foreach (var customer in customers)
{
try
{
SaveCustomer(customer);
}
catch (Exception ex)
{
failedCustomers.Add(customer.Id);
}
}
if (failedCustomers.Any())
{
return StatusCode(StatusCodes.Status207MultiStatus, failedCustomers);
}
else
{
return Ok();
}
Pay attention to the response code Status207MultiStatus, refer to this question.
I have a simple entity and trying to make fail the save method for my unit test.
The question is, how can I make the save method to fail and return false?
public class Sampple
{
public int Id { get; set; }
public string Name{ get; set; }
}
public bool Save()
{
return (_applicationDbContext.SaveChanges() >= 0);
}
Like #Yohannis said, dont waste your time testing EF itself.
If you are looking to test what might happen if a dbcontext.SaveChanges() failed, whether that be for an incorrectly parsed property or whatever.
try something like this:
`try {
//_applicationDbContext.SaveChanges()
throw new Exception();
// Remember to replace _applicationDbContext.SaveChanges() with
//'new Exception' when you are outside of the development db
return(true); //whilst exception active, here is not hit
}
catch (Exception e) {
//Error handling here
return(false);
}`
A try catch will try to complete a process, if it cant, the catch will 'catch' the thrown exception. In our case we have purposely thrown anew Exception so that we can see exactly what would happen if _applicationDbContext.SaveChanges() did fail. I have included a very basic Exception but there are many types that you can use and tailor to exactly what kind of error you might want to test for.
I have included a link with some relatively simple examples for your consideration.
https://www.codeproject.com/Articles/850062/Exception-handling-in-ASP-NET-MVC-methods-explaine
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.
I need to make an API Post method where I insert data into the database.
The database has 3 fields: One field has default values given by the database, one I have to insert with a new Guid.NewGuid() method, and one is inserted by the user.
How can I do this? Never seen anything like this in a tutorial.
If you can give me an example I will appreciate.
I'm new in web APIs and have seen a bunch of tutorials since Wednesday and can't reach a solution.
EDIT:
Here is my code:
public HttpResponseMessage Post(Company value)
{
try
{
if(ModelState.IsValid)
{
bd.Companies.Add(value);
bd.SaveChanges();
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, "Invalid Model");
}
}
catch (Exception ex )
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
}
}
How can i put a Guid.NewGuid()in this code to give a value to one of my fields?
EDIT2: My class to receive the values from Post
public class CompanyPostViewModel : Company
{
public static Guid Guid.NewGuid(); -->how can i do this?
public string Name { get; set; }
public DateTime? CreatedDate { get; set; }
}
If you are looking for an example, that fully goes from the front-end, to using WEB API [Post], to writing to the database, please see the following site. It should provide you enough context to complete what you are trying to accomplish. If this is insufficient, please post your current code, and where you are having any issues.
I may be going about this incorrectly but this is my class that I wrap my entity object:
using System;
using System.Linq;
namespace SSS.ServicesConfig.data
{
public partial class GlobalSetting
{
private static GlobalSetting _globalSettings;
public static GlobalSetting GlobalSettings
{
get
{
if (_globalSettings == null)
{
GetGlobalSetting();
}
return _globalSettings;
}
}
private static void GetGlobalSetting()
{
try
{
using (var subEntities = PpsEntities.DefaultConnection())
{
_globalSettings = (from x in subEntities.GlobalSettings
select x).FirstOrDefault();
if (_globalSettings == null)
{
_globalSettings = new GlobalSetting();
_globalSettings.GlobalSettingId = Guid.NewGuid();
_globalSettings.CompanyCode = string.Empty;
_globalSettings.CorporationId = Guid.Empty;
_globalSettings.DefaultBranch = "01";
_globalSettings.SourceId = Guid.Empty;
_globalSettings.TokenId = Guid.Empty;
subEntities.AddToGlobalSettings(_globalSettings);
subEntities.SaveChanges();
}
}
}
catch (Exception ex)
{
Logging.Log("An error occurred.", "GetGlobalSetting", Apps.ServicesConfig, ex);
throw new Exception(string.Format("Unable to retrieve data: [{0}].", ex.Message));
}
}
internal static void SaveGlobalSettings()
{
using (var entities = PpsEntities.DefaultConnection())
{
entities.Attach(_globalSettings);
entities.SaveChanges();
}
}
}
}
I'm trying to make it where they have to go through my class to get the settings record and save it though the same class. This is in a separate project that several other projects are going to import.
My save isn't saving to the database and I see no errors or changes on the record. In this particular table, there is only one record so it's not adding another record either.
Any suggestions?
First your save is not being called after the initial value is assigned to _globalSettings.
Second You should not be trying to change the value with a get accessor. It is bad form.
http://msdn.microsoft.com/en-us/library/w86s7x04.aspx
I recommend that you separate the responsibility of the save to the database to a new method (you could expose the SaveGlobalSettings method by making it public), but if you are determined to obfuscate the save from the user, then I would recommend you remove the save to the database from get accessor of the GlobalSettings property, create a set accessor for the GlobalSettings property, and put the save to the database in the GlobalSettings properties set accessor.
One other note, you are killing your stack trace.
throw new Exception(string.Format("Unable to retrieve data: [{0}].", ex.Message));
You can still catch and log the exception the way that your are doing it, but re-throw the exception like this:
catch (Exception ex)
{
Logging.Log("An error occurred.", "GetGlobalSetting", Apps.ServicesConfig, ex);
throw;
}
This will preserve the original exception.