Strange behavior CRM 2011 plugin - c#

I have registered a plugin for our quote products. The plugin worked well in our test environment. I have tested it lots of times. Then registered the plugin in the main server. However, the below scenario occurs:
When I create or update the quote products first the quote product form greys out:
After I click on the quote form the error appears. No log files are available (as you see). I debugged the plugin, but there is no error also. After I click the OK, the error disappears and the required business takes place on the quote product (for tax field). Means that the code of plugin has no errors and does its job well. The code is as:
using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Xrm;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk.Deployment;
public class Plugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
Entity entity;
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName != "quotedetail") { return; }
}
else
{
return;
}
try
{
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(
typeof(IOrganizationServiceFactory));
IOrganizationService service =
serviceFactory.CreateOrganizationService(context.UserId);
if (context.MessageName == "Create")
{
Entity QuoteProduct = (Entity)context.InputParameters["Target"];
Guid QPID = QuoteProduct.Id;
TaxCreator(service, QPID);
}
if (context.MessageName == "Update" && context.Depth < 3)
{
Entity QuoteProduct = (Entity)context.PostEntityImages["Target"];
Guid QPID = QuoteProduct.Id;
TaxUpdater(service, QPID);
}
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
"An error occurred in the plug-in.", ex);
}
}
private static void TaxCreator(IOrganizationService service, Guid QPID)
{
using (var crm = new XrmServiceContext(service))
{
var QuoteProduct = crm.QuoteDetailSet.Where(c => c.QuoteDetailId == QPID).First();
var SaleSetting = crm.new_salessettingSet.Where(c => c.statecode == 0).First();
double TaxPercent = (Convert.ToDouble(SaleSetting.new_TaxPercent) / 100);
if (QuoteProduct.IsPriceOverridden == false)
{
decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
QuoteProduct.Attributes["tax"] = new Money(Tax);
QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit);
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
}
}
}
private static void TaxUpdater(IOrganizationService service, Guid QPID)
{
using (var crm = new XrmServiceContext(service))
{
var QuoteProduct = crm.QuoteDetailSet.Where(c => c.QuoteDetailId == QPID).First();
var SaleSetting = crm.new_salessettingSet.Where(c => c.statecode == 0).First();
double TaxPercent = (Convert.ToDouble(SaleSetting.new_TaxPercent) / 100);
if (QuoteProduct.IsPriceOverridden == false)
{
decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
QuoteProduct.Attributes["tax"] = new Money(Tax);
QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit);
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
}
}
}
}
I also checked the event viewer on the server for the errors, and no result!
I have registered my plugin on create and update of the quote product.
Any help is greatly appreciated.

I'd go shotgun debug on it. Implement the following tracing. Usually, I place the declaration of the tracer as a class member (so it's visible to all my help methods within the class) and write to it (when desperate and frustrated) after every operation (more or less). Then, when the program crashes by itself (or when I intentionally crash it to see the trace log), I can usually pin-point the problem area.
public class MyPlugin _ IPlugin
{
private ITracingService _trace;
public void Execute(IServiceProvider service)
{
_trace = (ITracingService)service.GetService(typeof(ITracingService));
_trace.Trace("Commencing.");
...
_trace.Trace("Right before an operation. " + someValue);
PerformAnOperation();
_trace.Trace("Right before an other operation. " + someOtherValue);
PerformAnOtherOperation();
...
throw new Exception("Intentional!");
}
}
The, you can follow through to see exactly where the problem originates. In my experience, once one knows where it aches, one can easily suggest how to remedy the issue.
EDIT:
Since the OP requested more details, I'm taking his code and put in the tracing algorithm into it for him. A bit redundant but apparently, CRM can be very confusing. Been there myself.
public class Plugin : IPlugin
{
// Here we declare our tracer.
private ITracingService _trace;
public void Execute(IServiceProvider serviceProvider)
{
// Here we create it.
_trace = (ITracingService)serviceProvider
.GetService(typeof(ITracingService));
_trace.Trace("Commencing.");
// Here we crash our plugin just to make sure that we can see the logs
// with trace information. This statement should be gradually moved down
// the code to discover where exactly the plugin brakes.
throw new Exception("Intentional!");
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
Entity entity;
...
try { ... }
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
"An error occurred in the plug-in.", ex);
}
// Here we catch all other kinds of bad things that can happen.
catch(Exception exception) { throw exception; }
}
private static void TaxCreator(IOrganizationService service, Guid QPID)
{
// Here we add a line to the trace hoping that the execution gets here.
_trace.Trace("Entered TaxCreator.");
using (var crm = new XrmServiceContext(service))
{
var QuoteProduct = crm.QuoteDetailSet.Where(...).First();
var SaleSetting = crm.new_salessettingSet.Where(...).First();
double TaxPercent = (Convert.ToDouble(...) / 100);
// Here we add a line to the trace to see if we get past this point.
_trace.Trace("Approaching if statement.");
if (QuoteProduct.IsPriceOverridden == false) { ... }
}
}
private static void TaxUpdater(IOrganizationService service, Guid QPID)
{
// Here we add a line to the trace hoping that the execution gets here.
_trace.Trace("Entered TaxUpdater.");
...
}
}

public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
Entity entity;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName != "quotedetail") { return; }
}
else
{
return;
}
try
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
QuoteDetail QuoteProduct = ((Entity)context.InputParameters["Target"]).ToEntity<QuoteDetail>();
if (context.MessageName == "Create" || context.MessageName == "Update")
// && context.Depth < 3) //try to avoid depth unless you have to have it
{
TaxSetter(service, QuoteProduct);
}
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException( "An error occurred in the plug-in.", ex);
}
}
private static void TaxSetter(IOrganizationService service, QuoteDetail product)
{
using (var crm = new TrainingContext(service))
{
var QuoteProduct = product.ToEntity<QuoteDetail>();
if (QuoteProduct.IsPriceOverridden == false)
{
double TaxPercent = Convert.ToDouble(50 / 100);
decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
QuoteProduct.Tax = Tax; //depending on how you the parameters passed into CrmSvcUtil
QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit); //same deal here
//crm.UpdateObject(QuoteProduct);
//crm.SaveChanges();
//code not needed, the Plugin context will take care of this for you
}
}
}
The idea is, you want your plugin context to work for you; and you want to overtly do as little as possible. I changed the tax calculation because I don't have your attributes; you'll want to change them back. If I've still misread your problem, I'd be happy to delete my post (or try).

Related

Better way than try-catch to pass failure messages back to Web API caller?

I've got many Web API calls that delegate to methods in data-layer classes that call my ORM (Entity Framework) and look like this:
public OperationResult DeleteThing(Guid id)
{
var result = new OperationResult() { Success = true };
using (var context = this.GetContext())
{
try
{
context.Things.Where(x => x.Id == id).Delete();
context.SaveChanges();
}
catch (Exception ex)
{
Logger.Instance.LogException(ex);
result.AddError("There was a database error deleting the thing. Check log for details.");
}
return result;
}
(You may recognize the return value as similar to the Notification pattern.)
So I have many of the same try-catch blocks and it smells bad to me. I'd like to get rid of them all and use a global exception handler to log errors instead, but in addition to logging, I also need to pass a message back to the consumer, specific to each different service method, so that the consumer can perhaps pass the message as the results of the service call appropriately. Web service consumers, e.g. our web site, ultimately can display the message generated here to clue the user in to the nature of the error.
Can anyone suggest a better way? My instinct is to go through and replace with catches of specific exception types, but that seems like a lot of work for zero practical benefit and a harm to my code maintainability.
Similar to Stuart's answer, you can also use a Filter attribute inherited from ExceptionFilterAttribute to modify the response based on any input you require.
Here's a full working example that accomplishes:
Custom message for exception type
Modifying the operation result
Fall through generic message for all exception types
ValuesController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Filters;
using Demo.Models;
namespace Demo.Controllers
{
public class ValuesController : ApiController
{
// DELETE api/values/5
[OperationError("The operation failed to delete the entity")]
public OperationResult Delete(int id)
{
throw new ArgumentException("ID is bad", nameof(id));
}
// DELETE api/values/5?specific=[true|false]
[OperationError("The operation tried to divide by zero", typeof(DivideByZeroException))]
[OperationError("The operation failed for no specific reason")]
public OperationResult DeleteSpecific(int id, bool specific)
{
if (specific)
{
throw new DivideByZeroException("DBZ");
}
else
{
throw new ArgumentException("ID is bad", nameof(id));
}
}
}
public class OperationErrorAttribute : ExceptionFilterAttribute
{
public Type ExceptionType { get; }
public string ErrorMessage { get; }
public OperationErrorAttribute(string errorMessage)
{
ErrorMessage = errorMessage;
}
public OperationErrorAttribute(string errorMessage, Type exceptionType)
{
ErrorMessage = errorMessage;
ExceptionType = exceptionType;
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
// Exit early for non OperationResult action results
if (actionExecutedContext.ActionContext.ActionDescriptor.ReturnType != typeof(OperationResult))
{
base.OnException(actionExecutedContext);
return;
}
OperationResult result = new OperationResult() {Success = false};
// Add error for specific exception types
Type exceptionType = actionExecutedContext.Exception.GetType();
if (ExceptionType != null)
{
if (exceptionType == ExceptionType)
{
result.AddError(ErrorMessage);
}
else
{
// Fall through
base.OnException(actionExecutedContext);
return;
}
}
else if (ErrorMessage != null)
{
result.AddError(ErrorMessage);
}
// TODO: Log exception, generate correlation ID, etc.
// Set new result
actionExecutedContext.Response =
actionExecutedContext.Request.CreateResponse(HttpStatusCode.InternalServerError, result);
base.OnException(actionExecutedContext);
}
}
}
Specific exception:
Generic exception:
You could move your logic up the stack into a custom ExceptionHandler. This is a simple example, but the basic idea is to handle specific exceptions and control the status code and (not pictured below) normalize error messages for the caller.
public class ApiExceptionHandler: ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
if (context == null) throw new ArgumentNullException("context");
LogManager.GetLoggerForCurrentClass().Error(context.Exception, "Captured in ExceptionHandler");
if (context.Exception.GetType() == typeof(NotFoundException))
{
context.Result = new NotFoundResult(context.Request);
}
else if (context.Exception.GetType() == typeof(ArgumentException))
{
// no-op - probably a routing error, which will return a bad request with info
}
else if (context.Exception.GetType() == typeof(ArgumentNullException))
{
context.Result = new BadRequestResult(context.Request);
}
else
{
context.Result = new InternalServerErrorResult(context.Request);
}
}
}
Hook this up in the WebApiConfig:
config.Services.Replace(typeof(IExceptionHandler), new ApiExceptionHandler());

Xamarin app crash when attempting to sync SyncTable

I making an app using xamarin and azure mobile service. I am attempting to add offline sync capabilities but I am stuck. I have a service which looks like this
class AzureService
{
public MobileServiceClient Client;
AuthHandler authHandler;
IMobileServiceTable<Subscription> subscriptionTable;
IMobileServiceSyncTable<ShopItem> shopItemTable;
IMobileServiceSyncTable<ContraceptionCenter> contraceptionCenterTable;
IMobileServiceTable<Member> memberTable;
const string offlineDbPath = #"localstore.db";
static AzureService defaultInstance = new AzureService();
private AzureService()
{
this.authHandler = new AuthHandler();
this.Client = new MobileServiceClient(Constants.ApplicationURL, authHandler);
if (!string.IsNullOrWhiteSpace(Settings.AuthToken) && !string.IsNullOrWhiteSpace(Settings.UserId))
{
Client.CurrentUser = new MobileServiceUser(Settings.UserId);
Client.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;
}
authHandler.Client = Client;
//local sync table definitions
//var path = "syncstore.db";
//path = Path.Combine(MobileServiceClient.DefaultDatabasePath, path);
//setup our local sqlite store and intialize our table
var store = new MobileServiceSQLiteStore(offlineDbPath);
//Define sync table
store.DefineTable<ShopItem>();
store.DefineTable<ContraceptionCenter>();
//Initialize file sync context
//Client.InitializeFileSyncContext(new ShopItemFileSyncHandler(this), store);
//Initialize SyncContext
this.Client.SyncContext.InitializeAsync(store);
//Tables
contraceptionCenterTable = Client.GetSyncTable<ContraceptionCenter>();
subscriptionTable = Client.GetTable<Subscription>();
shopItemTable = Client.GetSyncTable<ShopItem>();
memberTable = Client.GetTable<Member>();
}
public static AzureService defaultManager
{
get { return defaultInstance; }
set { defaultInstance = value; }
}
public MobileServiceClient CurrentClient
{
get { return Client; }
}
public async Task<IEnumerable<ContraceptionCenter>> GetContraceptionCenters()
{
try
{
await this.SyncContraceptionCenters();
return await contraceptionCenterTable.ToEnumerableAsync();
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine(#"Invalid sync operation: {0}", msioe.Message);
}
catch (Exception e)
{
Debug.WriteLine(#"Sync error: {0}", e.Message);
}
return null;
}
public async Task SyncContraceptionCenters()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
try
{
//await this.Client.SyncContext.PushAsync();
await this.contraceptionCenterTable.PullAsync(
//The first parameter is a query name that is used internally by the client SDK to implement incremental sync.
//Use a different query name for each unique query in your program
"allContraceptionCenters",
this.contraceptionCenterTable.CreateQuery());
}
catch (MobileServicePushFailedException exc)
{
if (exc.PushResult != null)
{
syncErrors = exc.PushResult.Errors;
}
}
// Simple error/conflict handling. A real application would handle the various errors like network conditions,
// server conflicts and others via the IMobileServiceSyncHandler.
if (syncErrors != null)
{
foreach (var error in syncErrors)
{
if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
{
//Update failed, reverting to server's copy.
await error.CancelAndUpdateItemAsync(error.Result);
}
else
{
// Discard local change.
await error.CancelAndDiscardItemAsync();
}
Debug.WriteLine(#"Error executing sync operation. Item: {0} ({1}). Operation discarded.", error.TableName, error.Item["id"]);
}
}
}
I am getting this error:
System.NullReferenceException: Object reference not set to an instance of an object. When the SyncContraceptionCenters() is run. As far as I can tell I reproduced the coffeeItems example in my service But I am stuck.
I think I found the solution. The issue was the way the tables were being synced.
by calling SyncContraceptionCenters() and SyncShop() at the same time shopItemtable.PullAsync and contraceptionTable.PullAsync were happening at the same time. Which is bad apparently bad. So but putting them in the same method and awaiting them they run separately and they work as expected.

ASP.NET 5 EF 7 cannot update entry in database table

I am creating an API that will enable CRUD functionality to Create, Read, Update, and Delete record in database.
I am running into an issue that is not allowing me to update entry inside a table due to an instance is shared and I get below error.
cannot be tracked because another instance of this type with the same key is already being tracked. For new entities consider using an IIdentityGenerator to generate unique key values."
Here is my code:
[HttpPut("{id}")]
public JsonResult Put(int Id, [FromBody]PackageVersion updatePackage)
{
try
{
if (ModelState.IsValid)
{
if (updatePackage == null || updatePackage.Id != Id)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { status = "Bad Request" });
}
var package = _respository.GetPackageById(Id);
if (package == null)
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return Json(new { status = "Not Found", Message = package });
}
_respository.UpdatePackage(updatePackage);
if (_respository.SaveAll())
{
Response.StatusCode = (int)HttpStatusCode.Accepted;
return Json(updatePackage);
}
}
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { status = "Failed", ModelState = ModelState });
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { status = "Failed", Message = ex.Message });
}
}
In the above code you will notice I am first getting a record by using repository _repository.GetPackageById(Id), which allows me to validate that a record is in database and I can continue to update by using _repository.UpdatePackage(updatePackage) repository. If I comment out below code in my controller, I am able to save the data in the database.
//var package = _respository.GetPackageById(Id);
//if (package == null)
//{
// Response.StatusCode = (int)HttpStatusCode.NotFound;
// return Json(new { status = "Not Found", Message = package });
//}
I also made sure that I was using AddScoped in startup configuration as mentioned in this thread.
services.AddScoped<IAutomationRepository, AutomationRepository>();
I am not sure why I cant use multiple DBContext instances for same ID when it is called.
Any suggestion is really appreciated. :)
UPDATE 1:
public class AutomationRepository : IAutomationRepository
{
private AutomationDBContext _context;
public AutomationRepository(AutomationDBContext context)
{
_context = context;
}
public void AddPackage(PackageVersion newPackage)
{
_context.Add(newPackage);
}
public void DeletePackage(int id)
{
var package = _context.PackageVersions.SingleOrDefault(p => p.Id == id);
_context.PackageVersions.Remove(package);
}
public IEnumerable<PackageVersion> GetAllPackages()
{
return _context.PackageVersions.OrderBy(p => p.PackageName).ToList();
}
public object GetPackageById(int id)
{
return _context.PackageVersions.SingleOrDefault(p => p.Id == id);
}
public bool SaveAll()
{
return _context.SaveChanges() > 0;
}
public void UpdatePackage(PackageVersion updatePackage)
{
_context.Update(updatePackage);
}
Change your repository method like below. This may help. Let us know what happens
public object GetPackageById(int id)
{
return _context.PackageVersions.AsNoTracking().SingleOrDefault(p => p.Id ==id);
}

ServiceStack Redis problems with simultaneous read requests

I'm using the ServiceStack.Redis implementation for caching events delivered over a Web API interface. Those events should be inserted into the cache and automatically removed after a while (e.g. 3 days):
private readonly IRedisTypedClient<CachedMonitoringEvent> _eventsCache;
public EventMonitorCache([NotNull]IRedisTypedClient<CachedMonitoringEvent> eventsCache)
{
_eventsCache = eventsCache;
}
public void Dispose()
{
//Release connections again
_eventsCache.Dispose();
}
public void AddOrUpdate(MonitoringEvent monitoringEvent)
{
if (monitoringEvent == null)
return;
try
{
var cacheExpiresAt = DateTime.Now.Add(CacheExpirationDuration);
CachedMonitoringEvent cachedEvent;
string eventKey = CachedMonitoringEvent.CreateUrnId(monitoringEvent);
if (_eventsCache.ContainsKey(eventKey))
{
cachedEvent = _eventsCache[eventKey];
cachedEvent.SetExpiresAt(cacheExpiresAt);
cachedEvent.MonitoringEvent = monitoringEvent;
}
else
cachedEvent = new CachedMonitoringEvent(monitoringEvent, cacheExpiresAt);
_eventsCache.SetEntry(eventKey, cachedEvent, CacheExpirationDuration);
}
catch (Exception ex)
{
Log.Error("Error while caching MonitoringEvent", ex);
}
}
public List<MonitoringEvent> GetAll()
{
IList<CachedMonitoringEvent> allEvents = _eventsCache.GetAll();
return allEvents
.Where(e => e.MonitoringEvent != null)
.Select(e => e.MonitoringEvent)
.ToList();
}
The StructureMap 3 registry looks like this:
public class RedisRegistry : Registry
{
private readonly static RedisConfiguration RedisConfiguration = Config.Feeder.Redis;
public RedisRegistry()
{
For<IRedisClientsManager>().Singleton().Use(BuildRedisClientsManager());
For<IRedisTypedClient<CachedMonitoringEvent>>()
.AddInstances(i => i.ConstructedBy(c => c.GetInstance<IRedisClientsManager>()
.GetClient().GetTypedClient<CachedMonitoringEvent>()));
}
private static IRedisClientsManager BuildRedisClientsManager()
{
return new PooledRedisClientManager(RedisConfiguration.Host + ":" + RedisConfiguration.Port);
}
}
The first scenario is to retrieve all cached events (several hundred) and deliver this over ODataV3 and ODataV4 to Excel PowerTools for visualization. This works as expected:
public class MonitoringEventsODataV3Controller : EntitySetController<MonitoringEvent, string>
{
private readonly IEventMonitorCache _eventMonitorCache;
public MonitoringEventsODataV3Controller([NotNull]IEventMonitorCache eventMonitorCache)
{
_eventMonitorCache = eventMonitorCache;
}
[ODataRoute("MonitoringEvents")]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public override IQueryable<MonitoringEvent> Get()
{
var allEvents = _eventMonitorCache.GetAll();
return allEvents.AsQueryable();
}
}
But what I'm struggling with is the OData filtering which Excel PowerQuery does. I'm aware of the fact that I'm not doing any serverside filtering yet but that doesn't matter currently. When I filter for any property and click refresh, PowerQuery is sending multiple requests (I saw up to three) simultaneously. I believe it's fetching the whole dataset first and then executing the following requests with filters. This results in various exceptions for ServiceStack.Redis:
An exception of type 'ServiceStack.Redis.RedisResponseException' occurred in ServiceStack.Redis.dll but was not handled in user code
With additional informations like:
Additional information: Unknown reply on multi-request: 117246333|company|osdmonitoringpreinst|2014-12-22|113917, sPort: 54980, LastCommand:
Or
Additional information: Invalid termination, sPort: 54980, LastCommand:
Or
Additional information: Unknown reply on multi-request: 57, sPort: 54980, LastCommand:
Or
Additional information: Type definitions should start with a '{', expecting serialized type 'CachedMonitoringEvent', got string starting with: u259447|company|osdmonitoringpreinst|2014-12-18|1
All of those exceptions happen on _eventsCache.GetAll().
There must be something I'm missing. I'm sure Redis is capable of handling a LOT of requests "simultaneously" on the same set but apparently I'm doing it wrong. :)
Btw: Redis 2.8.12 is running on a Windows Server 2008 machine (soon 2012).
Thanks for any advice!
The error messages are indicative of using a non-thread-safe instance of the RedisClient across multiple threads since it's getting responses to requests it didn't expect/send.
To ensure your using correctly I only would pass in the Thread-Safe IRedisClientsManager singleton, e.g:
public EventMonitorCache([NotNull]IRedisClientsManager redisManager)
{
this.redisManager = redisManager;
}
Then explicitly resolve and dispose of the redis client in your methods, e.g:
public void AddOrUpdate(MonitoringEvent monitoringEvent)
{
if (monitoringEvent == null)
return;
try
{
using (var redis = this.redisManager.GetClient())
{
var _eventsCache = redis.As<CachedMonitoringEvent>();
var cacheExpiresAt = DateTime.Now.Add(CacheExpirationDuration);
CachedMonitoringEvent cachedEvent;
string eventKey = CachedMonitoringEvent.CreateUrnId(monitoringEvent);
if (_eventsCache.ContainsKey(eventKey))
{
cachedEvent = _eventsCache[eventKey];
cachedEvent.SetExpiresAt(cacheExpiresAt);
cachedEvent.MonitoringEvent = monitoringEvent;
}
else
cachedEvent = new CachedMonitoringEvent(monitoringEvent, cacheExpiresAt);
_eventsCache.SetEntry(eventKey, cachedEvent, CacheExpirationDuration);
}
}
catch (Exception ex)
{
Log.Error("Error while caching MonitoringEvent", ex);
}
}
And in GetAll():
public List<MonitoringEvent> GetAll()
{
using (var redis = this.redisManager.GetClient())
{
var _eventsCache = redis.As<CachedMonitoringEvent>();
IList<CachedMonitoringEvent> allEvents = _eventsCache.GetAll();
return allEvents
.Where(e => e.MonitoringEvent != null)
.Select(e => e.MonitoringEvent)
.ToList();
}
}
This will work irrespective of what lifetime of what your EventMonitorCache dependency is registered as, e.g. it's safe to hold as a singleton since EventMonitorCache is no longer holding onto a redis server connection.

CRM - How to create a plugin for Update message

So I created a plugin, based on the given example by Microsoft on the PLUGIN WALKTHROUGH. It is actually on Create message, so I modified some codes and registered it under Update message.
But when I register it on Update, an exception is thrown that says:
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
So I tried to register it under Create message, and it works.
But I need to register it under update, and I can't find a reliable source on how to change my code so Update can handle it. Here is my code:
using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Xrm;
public class Plugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
Entity entity;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName != "scs_barangayclearanceapplication") { return; }
}
else
{
return;
}
try
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
//var id = (Guid)context.OutputParameters["id"];
SendEmailToContact(service, entity.Id);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
"An error occurred in the plug-in.", ex);
}
}
private static void SendEmailToContact(IOrganizationService service, Guid id)
{
using (var crm = new XrmServiceContext(service))
{
var testData = crm.scs_barangayclearanceapplicationSet.Where(b => b.Id == id).First();
string sr = testData.scs_ServiceRequestNumber;
var note = new Annotation
{
Subject = "Created with plugin",
NoteText = " Service Request ID = " + id + " Service Request Number = " + sr,
ObjectId = testData.ToEntityReference(),
ObjectTypeCode = testData.LogicalName
};
crm.AddObject(note);
crm.SaveChanges();
}
}
}
I really need your help. Thanks!

Categories