So let's say that I need to update a list of objects.
using(db)
{
repository = new Repository<Publication>(db);
foreach (KeyValuePair<int,int> item in publications)
{
Publication publication = repository.GetById(item.Key);
if (publication != null)
{
publication.Quantity = publication.Quantity - item.Value;
if (publication.Quantity > 0)
db.Publication.Attach(publication);
}
}
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException e)
{
throw new Exception("Could not update the database", e);
}
}
}
When I tried to save all the objects, if someone fails, it should be in the catch block, but my question is: how can I get the specific object that throws the exception?
You're catching DbUpdateConcurrencyException, which has an Entries property, documented as:
Gets DbEntityEntry objects that represent the entities that could not be saved to the database.
So basically that gives you all the problematic ones.
Related
I have the following situation in code, whats the best way to manage it, the comments contains the situations, and please recommend the best practice.
try
{
string errorMessage = AccountClient.GetAccount(id, out accountDetails);
// FIRST WAY : REMOVE THIS NULL CHECK AT ALL AND LEAVE GetAccountDetails to control
// the Null situation?
if (accountDetails == null)
{
// Second Way: This way? Throw exception here?
throw new ArgumentNullException(nameof(accountDetails));
//Third way? break the function?
break;
}
// GetAccount Details already has null control
Subscription subscription = AccountProcessor.GetAccountDetails(accountDetails);
}
catch (Exception e)
{
throw;
}
First of all, the costruction
catch (Exception e) {
throw;
}
is redundant one and can be eliminated. Now about nulls. There're two
cases:
null is an erroneous value and so it should be signalled
null is an expected, ordinary value and thus it should be proceeded
And so you have (null is an error)
string errorMessage = AccountClient.GetAccount(id, out accountDetails);
// What's wrong: it's id which doesn't correspond to any detail
// (we expect id being s.t. AccountClient.GetAccount(id...) returns not null detail)
if (accountDetails == null)
throw new ArgumentException($"Incorrect id {id} which doesn't have any detail.",
nameof(id));
Subscription subscription = AccountProcessor.GetAccountDetails(accountDetails);
Or (null is an expected outcome)
string errorMessage = AccountClient.GetAccount(id, out accountDetails);
if (accountDetails == null)
return null; // or any reasonable value, or just return, or create new Subscription
Subscription subscription = AccountProcessor.GetAccountDetails(accountDetails);
If you can do anything about null input then handle it.
try
{
string errorMessage = AccountClient.GetAccount(id, out accountDetails);
if (accountDetails == null)
{
// do something about it. Maybe write some logs, substitute with a default value
// or throw appropriate exception ...
}
Subscription subscription = AccountProcessor.GetAccountDetails(accountDetails);
}
catch (Exception e)
{
throw;
}
if you can't then let GetAccountDetails decide what should happen.
try
{
string errorMessage = AccountClient.GetAccount(id, out accountDetails);
Subscription subscription = AccountProcessor.GetAccountDetails(accountDetails);
}
catch (Exception e)
{
throw;
}
Also there is no need to catch an exception, doing nothing and then throw it so you can remove the whole try catch block.
It depends on where this ID is coming from. If the user typed the ID, then I wouldn't generate an Exception, since it is not a error in your program. Just treat the user input and show a proper message. Exceptions are costly, so I usually use them only when i have a real programa failure. Besides that, if you write a custom Exception Handler, it wouldn`t make sense to log a error caused by wrong user input. So i would make it like this:
if (AccountClient.AccountExists(id))
{
AccountDetails details = AccountClient.GetAccount(id);
Subscription subscription = AccountProcessor.GetAccountDetails(accountDetails);
}
Anyway, its good to treat the input on the same way, even if you had treated like above, in case there is any other non treated call to it:
public AccountDetails GetAccount(int id)
{
if (Exists(id))
GetTheAccount(id);
else
throw new Exception(String.Format("Account {0} doesn't exists", id));
}
In this case I would use an Exception because it could really represent an error, if the caller function is passing a wrong value, for instance.
I've got a plugin on Update (pre-op) of InvoiceDetail, in which I'm retrieving the associated Invoice to get some more information from it (ie: the tax profile that was selected at the invoice level) in CRM 2016.
Here's how I do it:
//xrmObjects is an object containing all useful objects in plugins/workflow...
var invoice = RetrieveEntity(xrmObjects.Service, xrmObjects.TracingService, image["invoiceid"] as EntityReference, new ColumnSet("invoiceid", "pricelevelid", "customerid", "opportunityid", "xtc_tax_definition"));
This specific line of code above triggers another Update on InvoiceDetail
Here's the method invoked above:
public static Entity RetrieveEntity(IOrganizationService service, ITracingService tracingService, EntityReference target, ColumnSet columnSet)
{
Entity entity = new Entity();
try
{
entity = CrmServiceExtensions.ExecuteWithRetry<RetrieveResponse>(service, new RetrieveRequest
{
Target = target,
ColumnSet = columnSet
}).Entity;
}
catch (Exception ex)
{
tracingService.Trace($"Error retrieving {target.LogicalName}: {ex.Message}");
throw;
}
return entity;
}
Here's ExecuteWithRetry:
public static T ExecuteWithRetry<T>(IOrganizationService service, OrganizationRequest request)
where T : OrganizationResponse
{
T response = null;
int i = 0;
// Maximum of five iterations.
while (i < 5)
{
try
{
response = (T)service.Execute(request);
// If the Execute does not throw an Exception, break the loop
break;
}
catch (System.Web.Services.Protocols.SoapException e)
{
// Retry if the SoapException is a "Generic SQL Error",
// otherwise rethrow the SoapException.
// "Generic SQL Error" might indicate a deadlock.
if (e.Detail.InnerText.ToLower().Contains("generic sql error"))
{
++i;
// Wait (sleep thread) for i * 1000 milliseconds.
// So, first iteration waits 1 second,
// while fifth iteration will wait 5 seconds.
System.Threading.Thread.Sleep(i * 1000);
}
else throw;
}
}
if (i >= 5)
{
throw new Exception("ExecuteWithRetry: too many retries");
}
return response;
}
I have validated that nothing funky is happening, the update message on InvoiceDetail is triggered again at the line response = (T)service.Execute(request);
I also tried by using early-bound and a context to retrieve the invoice but the LoadProperty methods which loads the invoice does the same thing....
using (XrmServiceContext ctx = new XrmServiceContext(xrmObjects.Service))
{
Xrm.InvoiceDetail image = xrmObjects.PluginContext.PreEntityImages["invoicedetail"].ToEntity<Xrm.InvoiceDetail>();
try
{
ctx.LoadProperty(image, "invoice_details");
}
catch (Exception ex)
{
throw new InvalidPluginExecutionException($"Error retrieving invoice details' invoice: {ex.Message}");
}
}
I can't see anything in my steps configuration that would do this. Any ideas?
Instead of using LoadProperty, I simply retrieved the invoice manually like so
var invoice = ctx.InvoiceSet.SingleOrDefault(x => x.Id == image.InvoiceId.Id);
Instead of:
ctx.LoadProperty(image, "invoice_details");
For some reason, LoadProperty is triggering unwanted update message on child invoice details...
I have a simple entity insert as follows:
using (var db = new MyContext())
{
var item = new Artist();
TryUpdateModel(item);
if (ModelState.IsValid)
{
db.Artist.Add(item);
try
{
db.SaveChanges();
gvArtist.DataBind();
gvArtist.Visible = true;
}
catch (Exception e)
{
Master.Warning = e.InnerException.InnerException.Message;
}
}
}
e.Message and e.InnerException.Message both equate to:
"An error occurred while updating the entries. See the inner exception for details."
But, e.InnerException.InnerException.Message gives the exception I'm looking for, which is:
"Violation of UNIQUE KEY constraint 'UQ_artist_Cuid'. Cannot insert duplicate key in object 'dbo.artist'. The duplicate key value is (11). The statement has been terminated."
I'm worried about missing other exceptions, or causing an exception if I just keep
Master.Warning = e.InnerException.InnerException.Message;
in play.
Your fears are completely founded. Something like this is what you're looking for.
catch (Exception ex)
{
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
Master.Message = ex.Message;
}
The reason for the errors being varying levels deep is that the errors can occur in different sections of code, and they may bubble up through a varying number of methods that wrap them inside other exceptions. You can't plan for them to come from a specific level.
I've been using azure table storage for years, and I'm not sure what the "proper" way to do this is with the newest WindowsAzure.Storage library, version 5.0.1-preview (for use in a new ASP.NET 5 application):
Problem:
Given a partition key and row key, delete the row without checking for existence first, and without failing if it does not exist.
Current Solution: This code works... but the exception handling is confusing:
public async Task DeleteRowAsync(CloudTable table, string partition, string row)
{
var entity = new DynamicTableEntity(partition, row);
entity.ETag = "*";
var op = TableOperation.Delete(entity);
try
{
await table.ExecuteAsync(op);
}
catch (Exception ex)
{
var result = RequestResult.TranslateFromExceptionMessage(ex.Message);
if (result == null || result.HttpStatusCode != 404)
throw ex;
}
}
Questions:
The exception itself pointed me to this TranslateFromExceptionMessage method... I can't find a whole lot of information on that and WrappedStorageException (the type of the exception that is thrown). Is this some kind of new/preferred way to check for 404 errors on storage exceptions? Does anyone know if all storage exceptions will now use this, or do I need to write code to test and figure it out?
There is an InnerException of type StorageException. Presumably our older code that used StorageException.RequestInformation.HttpStatusCode could access this inner exception in the same way. Is that "OK", or is parsing these new XML error messages better or more robust somehow?
Is there a different approach altogether that I should be considering for this case?
If you are using the latest client (Azure.Data.Tables), the delete method automatically swallows 404 responses and does not throw. This approach avoids the need to write code that introduces race conditions (checking first before performing an operations) or having to handle this condition with a try/catch block.
If you want to know if the operation actually deleted a table or it didn't exist, you can inspect the Status property of the response.
Response response = await tableClient.DeleteAsync();
if (response.Status == (int)HttpStatusCode.NotFound)
{
// entity didn't exist)
}
The RequestResult.TranslateFromExceptionMessage method is now marked [Obsolete] and I wanted a way to ignore 404's myself.
Based on your tip to check out the RequestInformation.HttpStatusCode I came up with the following:
try
{
await table.ExecuteAsync(op);
}
catch (StorageException storEx)
{
if (storEx.RequestInformation.HttpStatusCode != 404)
{
throw;
}
}
There is a similar approach found in the AspNet WebHooks project when configured to use Azure Table Storage. Take a look at the Microsoft.Aspnet.WebHooks.custom.AzureStorage StorageManager class.
I'm not sure this adds much on top of what you'd already found, but they handle everything without throwing an exception and always return a status code so you can react to that as necessary.
One difference here is they pass in the table and the operation to a multi-purpose ExecuteAsync method, rather than having one specifically for delete, but that's just an implementation detail.
Relevant code from their example:
public async Task<TableResult> ExecuteAsync(CloudTable table, TableOperation operation)
{
if (table == null)
{
throw new ArgumentNullException(nameof(table));
}
if (operation == null)
{
throw new ArgumentNullException(nameof(operation));
}
try
{
var result = await table.ExecuteAsync(operation);
return result;
}
catch (Exception ex)
{
var errorMessage = GetStorageErrorMessage(ex);
var statusCode = GetStorageStatusCode(ex);
var message = string.Format(CultureInfo.CurrentCulture, AzureStorageResources.StorageManager_OperationFailed, statusCode, errorMessage);
_logger.Error(message, ex);
return new TableResult { HttpStatusCode = statusCode };
}
}
public string GetStorageErrorMessage(Exception ex)
{
if (ex is StorageException storageException && storageException.RequestInformation != null)
{
var status = storageException.RequestInformation.HttpStatusMessage != null ?
storageException.RequestInformation.HttpStatusMessage + " " :
string.Empty;
var errorCode = storageException.RequestInformation.ExtendedErrorInformation != null ?
"(" + storageException.RequestInformation.ExtendedErrorInformation.ErrorMessage + ")" :
string.Empty;
return status + errorCode;
}
else if (ex != null)
{
return ex.Message;
}
return string.Empty;
}
public int GetStorageStatusCode(Exception ex)
{
return ex is StorageException se && se.RequestInformation != null ? se.RequestInformation.HttpStatusCode : 500;
}
The following code is causing an intermittent exception:
public int UnblockJob(int jobId)
{
using (var connect = MakeConnect())
{
var tag = connect.JobTag.SingleOrDefault(jt => jt.JobId == jobId && jt.Name == Metrics.TagNameItemBlockCaller);
if (tag == null)
{
return 0;
}
connect.JobTag.Remove(tag);
return connect.SaveChanges();
}
}
How can I correct or troubleshoot it?
From the documentation for DbUpdateConcurrencyException:
Exception thrown by DbContext when it was expected that SaveChanges for an entity would result in a database update but in fact no rows in the database were affected.
This means that the record you are attempting to delete has since been removed from the database. It would appear that you have another process that is deleting records or this function is able to be called concurrently.
There are several solutions, here are a couple:
Fix the source problem Stop other processes affecting the data.
Catch the error Wrap this method in a try/catch block, after all you may only care that the record has been deleted:
try
{
//Existing code here
}
catch(DbUpdateConcurrencyException)
{
//Safely ignore this exception
}
catch(Exception e)
{
//Something else has occurred
throw;
}