I create custom error logger in CRM 2013 have functionality to save error information into CRM entity. I debug my code and find that my code works well. But the problem is when CRM rollback the transaction, the log entity also disappear. I want to know is it possible to create entity on catch block and still throw that error?
public void Execute(IServiceProvider serviceProvider)
{
try
{
...
}
catch (Exception ex)
{
IPluginExecutionContext context =
(IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.
GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(Guid.Empty);
var log = new Log
{
Message = ex.Message
};
service.Create(log);
throw;
}
}
I found the other way to solve this issue. We can create new service to create new transaction outside the transaction being failed. Here some snippet if you want to do the same:
try
{
...
}
catch (Exception ex)
{
var HttpCurrentContext = HttpContext.Current;
var UrlBase = HttpCurrentContext.Request.Url.Host;
string httpUrl = #"http://";
if (HttpCurrentContext.Request.IsLocal)
{
UrlBase += ":" + HttpCurrentContext.Request.Url.Port;
}
if (!UrlBase.Contains(httpUrl))
{
UrlBase = httpUrl + UrlBase;
}
var UriBase = UriBuilder(UrlBase.ToLowerInvariant().Trim() + "/xrmservices/2011/organization.svc").Uri;
IServiceConfiguration<IOrganizationService> orgConfigInfo =
ServiceConfigurationFactory.CreateConfiguration<IOrganizationService>(UriBase);
var creds = new ClientCredentials();
using (_serviceProxy = new OrganizationServiceProxy(orgConfigInfo, creds))
{
// This statement is required to enable early-bound type support.
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
_service = (IOrganizationService)_serviceProxy;
var log = new Log
{
Message = ex.Message
};
_service.Create(NewLog);
}
throw;
}
Essentially, no. You cannot prevent that an exception rolls back the transaction. See a similar question on StackOverflow.
A common approach is to create a separate logging service that can store logs outside of the database transaction.
B.t.w. Dynamics CRM 2015 spring release introduces the capability to store logs regardless if your plugin is participating in a database transaction.
Related
I am using a pre-operation retrieve multiple plugin to add a condition to account subgrid lookups. This works fine, however it applies to all queries on account entities. I want it to only apply when the user accesses the lookup within one subgrid on one form. Is there any way to retrieve the lookup which fires the query? Alternatively is the any way to achieve what I want to do by other means? The purpose of this is to filter the accounts which can be added to the subgrid.
Here is my code:
public class FilterAversedSuppliers : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
//Extract the tracing service for use in debugging sandboxed plug-ins.
ITracingService tracingService =
(ITracingService)serviceProvider.GetService(typeof(ITracingService));
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Query") &&
context.InputParameters["Query"] is QueryExpression)
{
try
{
QueryExpression objQueryExpression = (QueryExpression)context.InputParameters["Query"];
ConditionExpression condition = new ConditionExpression()
{
AttributeName = "customertypecode",
Operator = ConditionOperator.Equal,
Values = { 4 }
};
objQueryExpression.Criteria.AddCondition(condition);
tracingService.Trace("Custom Filter Added");
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("An error occurred in the FollowupPlugin plug-in.", ex);
}
catch (Exception ex)
{
tracingService.Trace("FollowupPlugin: {0}", ex.ToString());
throw;
}
}
}
}
On the criteria for the lookup view, add something like “Name” equals “FilterMe”.
Now in your plugin, inspect the incoming fetchxml query. If it contains your special criteria, you know to apply your special filtering. Don’t forget to remove the special criteria from the query in your code.
Now all other queries should not trigger your special filter.
I am building a system that need to send some transactional mails, and to achieve this I am using Azure storage queues to store the message temporarily before it is picked up by a WebJob and sent off to the intended recipient.
My Code is as follows:
SendGridMessage message = new SendGridMessage();
//Populate message with details - omitted for brevity
var serializer = new JavaScriptSerializer();
var modelAsString = serializer.Serialize(message);
try
{
var setting = CloudConfigurationManager.GetSetting("AzureStorageConnectionString");
var account = CloudStorageAccount.Parse(setting);
var queueClient = account.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference("FSPortalEmailQueue");
queue.CreateIfNotExists();
queue.AddMessage(new CloudQueueMessage(modelAsString));
}
catch (Exception ex)
{
//Something went wrong
}
Each time I try to execute the coder, an exception is thrown on the
var modelAsString = serializer.Serialize(message);
"Exception has been thrown by the target of an invocation."
The inner exception thrown was
{"Bad key path!"} from source "SendGrid.SmtpApi"
Please advise what I am doing wrong here.
After a bit more digging, it turns out that the message.header node was not being initialised. After adding
message.Header = new SendGrid.SmtpApi.Header();
message.Header.SetTo(new List<String> { enquiry.EnquiryCreatedBy.Email });
all started working pretty magically
I am using an Entity Framework 6.1 Model from Database 'wizard' setup.
When I create a Business object from my context and then try to add for attachment and then SaveChanges() nothing happens. Is there a tracing mode? or something I can turn on to see what is really happened under the covers.
Simple example:
var fb = _context.Business.Create();
//fb.Id exists and is an int but it is auto incr in the db
fb.Name = ub.ACCOUNT_NAME;
fb.ServiceManager = ub.SERVICE_MANAGER;
fb.AccountManager = ub.ACCOUNT_MANAGER;
fb.SalesPerson = ub.SALESPERSON;
fb.Created = DateTime.UtcNow;
fb.Updated = DateTime.UtcNow;
_context.Add(fb);
_context.SaveChanges();
The best way I have found to catch EF errors is by overriding the SaveChange method like below. If you have a centered place to recover logs (like log4net), the function will be able to insert it there.
public partial class Business
{
/// <summary>Override the SaveChange to return better error messages</summary>
public override int SaveChanges()
{
try {
return base.SaveChanges();
}
catch (System.Data.Entity.Validation.DbEntityValidationException ex) {
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Add some logging with log4net here
// Throw a new DbEntityValidationException with the improved exception message.
throw new System.Data.Entity.Validation.DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
Have you tried checking for any validation errors?
Here is the try block and validation method I am using in one of my new classes, so treat it as a code sample and not a 100% tested solution as I am still putting together some unit tests:
public List<string> ValidationErrorList = new List<string>();
try
{
_context.SaveChanges();
}
catch (Exception)
{
GetErrors(_context);
}
private void GetErrors(System.Data.Entity.DbContext context)
{
IEnumerable<System.Data.Entity.Validation.DbEntityValidationResult> ve;
ve = context.GetValidationErrors();
ValidationErrorList.Clear();
foreach (var vr in ve)
{
if (vr.IsValid == false)
{
foreach (var e in vr.ValidationErrors)
{
var errorMessage = e.PropertyName.Trim() + " : " +
e.ErrorMessage;
ValidationErrorList.Add(errorMessage);
}
}
}
}
While the above sample only calls the GetErrors method when an exception is triggered, you might also want to try calling it right after the SaveChanges() to see if there are validation errors that are not throwing an exception.
Have you tried creating a new Business object and adding it in? instead of creating one first?
var fb = new Business();
//fb.Id exists and is an int but it is auto incr in the db
fb.Name = ub.ACCOUNT_NAME;
fb.ServiceManager = ub.SERVICE_MANAGER;
fb.AccountManager = ub.ACCOUNT_MANAGER;
fb.SalesPerson = ub.SALESPERSON;
fb.Created = DateTime.UtcNow;
fb.Updated = DateTime.UtcNow;
_context.Business.Add(fb);
_context.SaveChanges();
I am trying to run a sample plugin for MS CRM. but I am getting following error:
An error occurred. Contact a system administrator or refer to the
Microsoft Dynamics CRM SDK troubleshooting guide.
here is the code:
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("account") &&
context.InputParameters["account"] is Entity)
{
// Obtain the target entity from the input parmameters.
Entity entity = (Entity)context.InputParameters["account"];
try
{
//check if the account number exist
if (entity.Attributes.Contains("account number") == false)
{
//create a task
Entity task = new Entity("task");
task["subject"] = "Account number is missing";
task["regardingobjectid"] = new EntityReference("account", new Guid(context.OutputParameters["id"].ToString()));
//adding attribute using the add function
// task["description"] = "Account number is missng for the following account. Please enter the account number";
task.Attributes.Add("description", "Account number is missng for the following account. Please enter the account number");
// Obtain the organization service reference.
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
// Create the task in Microsoft Dynamics CRM.
service.Create(task);
}
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}//end class
This is an example code, and I have verified that all the entities and fields which are utilized by this plugin are defined and are at there places. but I am continuously getting this Business Error.
I found the Solution: Instead of explicitly mentioning "account", we have to use:
Entity entity = (Entity)context.InputParameters["Target"];
The second reason for error was the restriction inside CRM which was not allowing creation of new accounts. Works fine when used to create new "contact."
Thanks a lot everyone for help.
Please check it like this
Entity entity = context.InputParameters["account"] as Entity;
some times that doesn't work properly.
I am developping an application and I have an error from time to time, I have to synchronize several worker roles with a lease on a blob.
Below is my init code for worker role
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("StorageAccount"));
string blobContainerName = RoleEnvironment.GetConfigurationSettingValue("BlobContainer");
CloudBlobContainer blobContainer = storageAccount.CreateCloudBlobClient().GetContainerReference(blobContainerName);
blobContainer.CreateIfNotExists();
string blobName = RoleEnvironment.GetConfigurationSettingValue("BlobToBeLeased");
_blob = blobContainer.GetBlockBlobReference(blobName);
if (!_blob.Exists())
{
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("This is dummy data")))
{
try
{
_blob.UploadFromStream(ms);
}
catch (StorageException storageException)
{
if (storageException.RequestInformation.HttpStatusCode != 412)
throw;
}
}
}
And here is my AcquireLease method:
private void AcquireLease()
{
try
{
var leaseId = _blob.AcquireLease(null, null);
Trace.WriteLine("==========> Lease acquired! <========== ID => " + leaseId);
_accessCondition = new AccessCondition {LeaseId = leaseId};
}
catch (Exception)
{
Trace.WriteLine("==========> Lease rejected! <==========");
}
}
A screenshot of the problem:
The problem is when I call the AcquireLease method it sometimes gives me two leases... Anyone has an idea on how to solve this...
Are CloudBlockBlob operations atomic?
After a talk with a guy from Microsoft it appears that the emulator does have some discrepencies from the storage service itself.
Running the same code on Azure works fine!
This answer is valid for emulator v1.8.0.0.