CRM 2011 plugin to check whether an Account has a parent - c#

I'm writing a plugin in C# for Dynamics CRM 2011 (On Prem).
The plugin is to prevent grandfathering in Account records (Accounts can have children OR a parent, but not both at the same time).
I'm struggling to check whether the Account has an associated parent or not, the attribute for parent ID is always null and I don't understand why.
I've correctly generated the early bound classes using crmsvcutil.exe and added the file to my project.
The generated code for the parent ID looks like this:
/// <summary>
/// Unique identifier of the parent account.
/// </summary>
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("parentaccountid")]
public Microsoft.Xrm.Sdk.EntityReference ParentAccountId
{
get
{
return this.GetAttributeValue<Microsoft.Xrm.Sdk.EntityReference>("parentaccountid");
}
set
{
this.OnPropertyChanging("ParentAccountId");
this.SetAttributeValue("parentaccountid", value);
this.OnPropertyChanged("ParentAccountId");
}
}
A basic version of my plugin looks like this:
namespace NoGrandparentAccounts
{
using CRM2011GeneratedBusinessModel; // Namespace for crmsvcutil generated early bound classes
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.ServiceModel;
public class Main : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// OBTAIN EXECUTION CONTEXT
var context(IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
// TRACING SERVICE FOR LOGGING
var tracingService =
(ITracingService)serviceProvider.GetService(typeof(ITracingService));
// WILL REPRESENT THE CURRENT ENTITY
Entity entity = null;
// INPUTPARAMETERS COLLECTION CONTAINS ALL DATA PASSED IN THE MESSAGE REQUEST
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
// SET ENTITY TO THE CURRENT ENTITY
entity = (Entity)context.InputParameters["Target"];
// CHECK WE'RE IN ACCOUNTS ENTITY
if (context.PrimaryEntityName != "account")
return;
// CHECK WE'RE CREATING OR UPDATING THE ENTITY
if (context.MessageName != "Create" && context.MessageName != "Update")
return;
}
else
{
return;
}
try
{
// GET ORGANIZATION SERVICE
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = serviceFactory.CreateOrganizationService(context.UserId);
// GET CURRENT ACCOUNT
var account = entity.ToEntity<Account>();
// HAS A PARENT ACCOUNT?
var hasParent = (account.ParentAccountId != null && account.ParentAccountId.Id != Guid.Empty);
}
catch (FaultException<OrganizationServiceFault> ex)
{
tracingService.Trace($#"Exception: {ex.Message} - {ex.StackTrace}");
throw new InvalidPluginExecutionException(#"An error occurred in the plugin.", ex);
}
catch (Exception ex)
{
tracingService.Trace($#"Exception: {ex.Message} - {ex.StackTrace}");
throw new InvalidPluginExecutionException(#"An error occurred in the plugin.", ex);
}
}
}
}
Unfortunately hasParent is always false because account.ParentAccountId is null. I don't know why because there is most definitely a parent account associated with this record and the attribute name is definitely parentaccountid.
The call to find the child Accounts works fine using parentaccountid:
// FETCH CHILDREN
var cExpr = new ConditionExpression("parentaccountid", ConditionOperator.Equal, account.Id);
var qExpr = new QueryExpression(Account.EntityLogicalName);
qExpr.ColumnSet = new ColumnSet(new string[] { "accountid" });
qExpr.Criteria.AddCondition(cExpr);
var results = service.RetrieveMultiple(qExpr);
// HAS CHILDREN
var hasChildren = results.Entities.Count > 0;
The entity count shows correctly as 3 and therefore hasChildren is true.
I've checked whether the attributes for this entity contains parentaccountid and it returns false which I assume is related to the problem:
if (entity.Attributes.Contains("parentaccountid"))
{
// Never gets here as this is false!?
}
What am I doing wrong?

The Target entity itself contains only basic entity identity data. If you want to access attributes for the entity you should configure a suitable PreImage or PostImage with the attributes that you want to access context.PreEntityImages, and you can then retrieve this via context.PreEntityImages or context.PostEntityImages. Alternatively (and less optimally) you can retrieve the latest version of the entity from within the PlugIn with the attributes you desire.

Do this
// GET CURRENT ACCOUNT
var account = (Account)service.Retrieve("account", entity.Id, new ColumnSet(new string[] { "accountid", "parentaccountid" }));
Instead of this
// GET CURRENT ACCOUNT
var account = entity.ToEntity<Account>();
As Josh Painter indicated in his comment, its possible that the event you registered against isn't passing in this value to the PluginExecutionContext.InputParameters["Target"], and thus the attribute becomes null when casting the Entity to an Account. Above is how to do a query to get the full record within the plugin, and that will always be populated, unless the plugin is registered pre-stage create (in which case your current code will work just fine, but since you're getting a null value, I assume you're not registering on pre-stage create).

Related

The given key was not present in the dictionary (plugin for dynamics 365)

I've made a plugin for primary entity 'incident' which is triggered on pre-operation stage. I am getting the error 'The given key was not present in the dictionary'. I am not sure what could be wrong here since it's my first plugin. The aim is to check if Case's serial number (zst_txtsn) exists in entity 'Warranty'
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xrm.Sdk;
using System.ServiceModel;
using Microsoft.Xrm.Sdk.Query;
namespace ActiveWarranty
{
public class Class1 : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService =
(ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity Case = (Entity)context.InputParameters["Target"];
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
try
{
string serialNumber = Case.Attributes["zst_txtsn"].ToString();
Entity Warranty = new Entity("zst_warranty");
QueryExpression query = new QueryExpression("zst_warranty");
query.ColumnSet = new ColumnSet(new string[] { "zst_name" });
query.Criteria.AddCondition("zst_serialno", ConditionOperator.Equal, serialNumber);
EntityCollection collection = service.RetrieveMultiple(query);
if (collection.Entities.Count > 0)
{
Case["zst_activewarranty"] = true;
}
else if (collection.Entities.Count == 0)
{
Case["zst_activewarranty"] = false;
}
service.Update(Case);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("An error occurred in FollowUpPlugin.", ex);
}
catch (Exception ex)
{
tracingService.Trace("FollowUpPlugin: {0}", ex.ToString());
throw;
}
}
}
}
}
Any help would be much appreciated.
When the object is null, .ToString() will throw an error. You can try one of these.
string serialNumber = Case.Attributes["zst_txtsn"] + "";
or
string serialNumber = "";
if(Case.GetAttributeValue<string>("zst_txtsn") != null)
{
serialNumber = Case.GetAttributeValue<string>("zst_txtsn").ToString();
}
Few things to note
1-
If this plugin is registered on the create event
The service.update(case) will fail because Target has no id yet
In this case any updates should be done on the Target without a service.update
2-
If this plugin is registered on the update event it’ll cause a loop if you’re not setting the filtering attributes properly for the trigger in the plugin registration tool. Additionally, since it’s on the pre stage, it should be enough to update the target with your boolean value to pass it to the platform.
In both cases above you should remove the service.update
3-
If zst_serialno is a lookup and you’re querying using the serial number text value
Then your query is wrong
You’ll have to use LinkEntities / $expand to query by related entity attributes

Entity framework error : Cannot access a disposed object

In my method below which is written using c# in asp.net core, I am getting error when executing any of the below update statements. What is the issue with using await SaveContextAsync(); I have noticed I dont get error when only using SaveContext();
The error that I am getting is as follows
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection
and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose()
on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency
injection container take care of disposing context instances.
Object name: 'FXDB1Context'.
I am not sure why I am getting this error.
public async Task<IdentityResult> ApproveUserChangeRequest(UserChangeRequest userChangeRequest, string approvedByAuthUserName, string authApplicationName)
{
var userChangeRequestRepository = UserChangeRequestRepository.GetAllAsList();
var userChangeRequestApprovalRepository = UserChangeRequestApprovalRepository.GetAllAsList();
var appSettingRepository = AppSettingRepository.GetAllAsList();
var clientCompanyContactRepository = ClientCompanyContactRepository.GetAllAsList();
var applicationUserRepo = ApplicationUserRepo.GetAllAsList();
int approvedByAuthUserID = GetApprovedByUserId(authApplicationName, approvedByAuthUserName);
// Check if UserChangeRequest is still Pending
bool isUserChangeRequestPending = userChangeRequestRepository.Any(x => x.Id == userChangeRequest.Id && x.ChangeStatus == "Pending");
if (isUserChangeRequestPending && approvedByAuthUserID > 0)
{
// Inserting record in the UserChangeRequestApproval table
InsertUserChangeRequestApproval(userChangeRequest);
await SaveContextAsync();
using (var userTransaction = Context.Database.BeginTransaction())
{
using (var securityTransaction = _securityContext.Database.BeginTransaction())
{
try
{
//Get the Number of approval required for Internal and External Users
int? internalApprovalsRequired = GetApprovals("InternalUserChangeRequestApprovalsRequired", appSettingRepository);
int? externalApprovalsRequired = GetApprovals("ExternalUserChangeRequestApprovalsRequired", appSettingRepository);
//Get the name of the application the auth user belongs to
var authUserApplicationName = GetApplicationName(userChangeRequest.AuthUserId);
//Get the Number of approvals for the request
var numberOfAprovals = userChangeRequestApprovalRepository.Where(x => x.UserChangeRequestId == userChangeRequest.Id).Count();
//If the number of approvals is equal or greater than the Approvals required then Update AppUser or Contact details
if ((authUserApplicationName == "ArgentexTrader" && numberOfAprovals >= internalApprovalsRequired) || (authUserApplicationName == "ArgentexClient" && numberOfAprovals >= externalApprovalsRequired))
{
//Updating the clientcontact table
UpdateClientContact(userChangeRequest, clientCompanyContactRepository);
//Updating the auth user table
UpdateAuthUser(userChangeRequest);
//Updating the IdentityDB user table
UpdateIdentityDBUser(userChangeRequest, applicationUserRepo);
//Updating the UserChangeRequest table
userChangeRequest.ChangeStatus = "Approved";
UserChangeRequestRepository.Update(userChangeRequest);
await SaveContextAsync();
userTransaction.Commit();
securityTransaction.Commit();
return IdentityResult.Success;
}
}
catch (Exception ex)
{
userTransaction.Rollback();
securityTransaction.Rollback();
_logger.Error(ex);
return IdentityResult.Failed(new IdentityError { Description = ex.Message });
}
}
}
}
return null;
}
The error says that a resolved context is been trying to made Disposable.
For More Information: Using Statements in C#
using statement is a scope only process, so this error means that your program tries to use the same instances of "context" in somewhere else, which it cant use it, because as I mentioned it is a "scope only" process.
In your example:
using (var userTransaction = Context.Database.BeginTransaction())
{
using (var securityTransaction = _securityContext.Database.BeginTransaction())
{
}
}
You can make it simple and dont use a using scope or you can be sure that, the context is just used in those using scopes.

EF5 can not handle Concurrency when Updating selective fields

I am using EF5 and Data First approach to Update entities.
I am using approach suggested by other questions to conditionally update only modified properties in the Entities.
Oki so here's the scenario My controller call Service with POCO objects and gets POCO objects from Service, The Service layer talks with Data layer which internally uses EF5 to retrieve entity from DB and Update them in DB.
The View data is loaded by controller from DTO object retrieved from Service layer.
User makes changes to View and Posts back JSON data to controller which gets mapped to DTO object in controller (courtesy MVC).
The controller makes call to Service layer with the DTO object (POCO) object.
The Service maps the POCO object to EF entity object and calls the Data layer's(i.e Repository) Update method passing in the EF entity.
In the Repository I fetch the existing entity from DB and call ApplyCurrentvaluesValues method, then I check if any properties are modified .
If properties are modified then I apply my custom logic to other entities which are not related to current entity and also Update the "UpdatedAdminId" & "UpdationDate" of current entity.
Post this I call "SaveChanges" method on Centext.
Every thing above I mentioned is working fine , except if I insert a break point in "SaveChanges" call and update some field modified by User to different value then "DbUpdateConcurrencyException" is not thrown by EF5.
i.e. I can get conditional Update & fire my custom logic when properties of my interest are modified to work perfectly.
But I am not getting error in case of the concurrency i.e the EF is not raising "DbUpdateConcurrencyException" in case a record is updated in between me fetching the record from DB , updating the record and saving it.
In real scenario there is a offline cron running which checks for newly created campaign and creates portfolio for them and marks the IsPortfolioCreated property below as true, in the mean time user can edit the campaign and the flag can be set to false even though the cron has created the portfolios.
To replicate the concurrency scenario I put a break point on SaveChanges and then Update the IsPortfolioCreated feild from MS-Sql enterprise manager for the same entity, but the "DbUpdateConcurrencyException" is not thrown even though the Data in Store has been updated.
Here's my code for reference,
Public bool EditGeneralSettings(CampaignDefinition campaignDefinition)
{
var success = false;
//campaignDefinition.UpdatedAdminId is updated in controller by retreiving it from RquestContext, so no its not comgin from client
var updatedAdminId = campaignDefinition.UpdatedAdminId;
var updationDate = DateTime.UtcNow;
CmsContext context = null;
GlobalMasterContext globalMasterContext = null;
try
{
context = new CmsContext(SaveTimeout);
var contextCampaign = context.CampaignDefinitions.Where(x => x.CampaignId == campaignDefinition.CampaignId).First();
//Always use this fields from Server, no matter what comes from client
campaignDefinition.CreationDate = contextCampaign.CreationDate;
campaignDefinition.UpdatedAdminId = contextCampaign.UpdatedAdminId;
campaignDefinition.UpdationDate = contextCampaign.UpdationDate;
campaignDefinition.AdminId = contextCampaign.AdminId;
campaignDefinition.AutoDecision = contextCampaign.AutoDecision;
campaignDefinition.CampaignCode = contextCampaign.CampaignCode;
campaignDefinition.IsPortfolioCreated = contextCampaign.IsPortfolioCreated;
var campaignNameChanged = contextCampaign.CampaignName != campaignDefinition.CampaignName;
// Will be used in the below if condition....
var originalSkeForwardingDomain = contextCampaign.skeForwardingDomain.ToLower();
var originalMgForwardingDomain = contextCampaign.mgForwardingDomain.ToLower();
//This also not firing concurreny exception....
var key = ((IObjectContextAdapter) context).ObjectContext.CreateEntityKey("CampaignDefinitions", campaignDefinition);
((IObjectContextAdapter)context).ObjectContext.AttachTo("CampaignDefinitions", contextCampaign);
var updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
ObjectStateEntry entry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(updated);
var modifiedProperties = entry.GetModifiedProperties();
//Even tried this , works fine but no Concurrency exception
//var entry = context.Entry(contextCampaign);
//entry.CurrentValues.SetValues(campaignDefinition);
//var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified).ToList();
// If any fields modified then only set Updation fields
if (modifiedProperties.Count() > 0)
{
campaignDefinition.UpdatedAdminId = updatedAdminId;
campaignDefinition.UpdationDate = updationDate;
//entry.CurrentValues.SetValues(campaignDefinition);
updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
//Also perform some custom logic in other entities... Then call save changes
context.SaveChanges();
//If campaign name changed call a SP in different DB..
if (campaignNameChanged)
{
globalMasterContext = new GlobalMasterContext(SaveTimeout);
globalMasterContext.Rename_CMS_Campaign(campaignDefinition.CampaignId, updatedAdminId);
globalMasterContext.SaveChanges();
}
}
success = true;
}
catch (DbUpdateConcurrencyException ex)
{
//Code never enters here, if it does then I am planning to show the user the values from DB and ask him to retry
//In short Store Wins Strategy
//Code in this block is not complete so dont Stackies don't start commenting about this section and plague the question...
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
finally
{
if (context != null) context.Dispose();
if (globalMasterContext != null) globalMasterContext.Dispose();
}
return success;
}
Entity framework it's not doing anything special about concurrency until you (as developer) configure it to check for concurrency problems.
You are trying to catch DbUpdateConcurrencyException, the documentation for this exception says: "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. ", you can read it here
In a database first approach, you have to set the property 'Concurrency Mode' for column on 'Fixed' (the default is None). Look at this screenshot:
The column Version is a SQL SERVER TIMESTAMP type, a special type that is automatically updated every time the row changes, read about it here.
With this configuration, you can try with this simple test if all is working as expected:
try
{
using (var outerContext = new testEntities())
{
var outerCust1 = outerContext.Customer.FirstOrDefault(x => x.Id == 1);
outerCust1.Description += "modified by outer context";
using (var innerContext = new testEntities())
{
var innerCust1 = innerContext.Customer.FirstOrDefault(x => x.Id == 1);
innerCust1.Description += "modified by inner context";
innerContext.SaveChanges();
}
outerContext.SaveChanges();
}
}
catch (DbUpdateConcurrencyException ext)
{
Console.WriteLine(ext.Message);
}
In the example above the update from the inner context will be committed, the update from the outer context will thrown a DbUpdateConcurrencyException, because EF will try to update the entity using 2 columns as a filters: the Id AND the Version column.
Hope this helps!

Attaching an entity of type failed because another entity of the same type already has the same primary key value

Error message: Attaching an entity of type failed because another entity of the same type already has the same primary key value.
Question: How do I attached an entity in a similar fashion as demonstrated in the AttachActivity method in the code below?
I have to assume the "another entity" part of the error message above refers to an object that exists in memory but is out of scope (??). I note this because the Local property of the DBSet for the entity type I am trying to attach returns zero.
I am reasonably confident the entities do not exist in the context because I step through the code and watch the context as it is created. The entities are added in the few lines immediately following creation of the dbcontext.
Am testing for attached entities as specified here:what is the most reasonable way to find out if entity is attached to dbContext or not?
When looking at locals in the locals window of visual studio I see no entities of type Activity (regardless of ID) except the one I am trying to attach.
The code executes in this order: Try -> ModifyProject -> AttachActivity
Code fails in the AttachActivity at the commented line.
Note the code between the debug comments which will throw if any entities have been added to the context.
private string AttachActivity(Activity activity)
{
string errorMsg = ValidateActivity(activity); // has no code yet. No. It does not query db.
if(String.IsNullOrEmpty(errorMsg))
{
// debug
var state = db.Entry(activity).State; // Detached
int activityCount = db.Activities.Local.Count;
int projectCount = db.Activities.Local.Count;
if (activityCount > 0 || projectCount > 0)
throw new Exception("objects exist in dbcontext");
// end debug
if (activity.ID == 0)
db.Activities.Add(activity);
else
{
db.Activities.Attach(activity); // throws here
db.Entry(activity).State = System.Data.Entity.EntityState.Modified;
}
}
return errorMsg;
}
public int ModifyProject(Presentation.PresProject presProject, out int id, out string errorMsg)
{
// snip
foreach (PresActivity presActivity in presProject.Activities)
{
Activity a = presActivity.ToActivity(); // returns new Activity object
errorMsg = ValidateActivity(a); // has no code yet. No. It does not query db.
if (String.IsNullOrEmpty(errorMsg))
{
a.Project = project;
project.Activities.Add(a);
AttachActivity(a);
}
else
break;
}
if (string.IsNullOrEmpty(errorMsg))
{
if (project.ID == 0)
db.Projects.Add(project);
else
db.AttachAsModfied(project);
saveCount = db.SaveChanges();
id = project.ID;
}
return saveCount;
}
This is the class that news up the dbContext:
public void Try(Action<IServices> work)
{
using(IServices client = GetClient()) // dbContext is newd up here
{
try
{
work(client); // ModifyProject is called here
HangUp(client, false);
}
catch (CommunicationException e)
{
HangUp(client, true);
}
catch (TimeoutException e)
{
HangUp(client, true);
}
catch (Exception e)
{
HangUp(client, true);
throw;
}
}
I am not asking: How do I use AsNoTracking What difference does .AsNoTracking() make?
One solution to avoid receiving this error is using Find method. before attaching entity, query DbContext for desired entity, if entity exists in memory you get local entity otherwise entity will be retrieved from database.
private void AttachActivity(Activity activity)
{
var activityInDb = db.Activities.Find(activity.Id);
// Activity does not exist in database and it's new one
if(activityInDb == null)
{
db.Activities.Add(activity);
return;
}
// Activity already exist in database and modify it
db.Entry(activityInDb).CurrentValues.SetValues(activity);
db.Entry(activityInDb ).State = EntityState.Modified;
}
Attaching an entity of type failed because another entity of the same type already has the same primary key value. This can happen when using the Attach method or setting the state of an entity to Unchanged or Modified if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the Add.
The solution is that
if you had to use GetAll()
public virtual IEnumerable<T> GetAll()
{
return dbSet.ToList();
}
Change To
public virtual IEnumerable<T> GetAll()
{
return dbSet.AsNoTracking().ToList();
}
I resolved this error by changing Update method like below.
if you are using generic repository and Entity
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
or normal(non-generic) repository and entity , then
_dbContext.Set<TaskEntity>().AddOrUpdate(entityToBeUpdatedWithId);
If you use AddOrUpdate() method, please make sure you have added
System.Data.Entity.Migrations namespace.

Prevent Quote Detail editing but allow the revision of the Quote itself

All users but a specific one should not be allowed to edit or update Quote Details which not satisfy a specific condition, but they have to be capable of revising the Quote if they want to.
The issue is, revising the Quote (i.e. the user clicks the "Revise" button in an Active form record) triggers the Update of the Quote Details and I can't figure out how to recognize what's going on.
My current attempt is based on a plugin which code looks like this:
public class PreQuoteProductUpdate : Plugin
{
// I work with CRM Developer Tools to build plugins
// This goes in Update Message, Pre-Operation, Server Only, pre-image called "preImage"
protected void ExecutePreQuoteProductUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService srv = localContext.OrganizationService;
Entity preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null;
try
{
PluginBody(context, srv, preImageEntity);
}
catch (Exception ex)
{
throw new InvalidPluginExecutionException("Quote Details Pre-Update", ex);
}
}
protected void PluginBody(IPluginExecutionContext context, IOrganizationService srv, Entity preImage)
{
if(IsRevising()) return;
CheckSomeCondition(context, srv);
if (preImage.Attributes.ContainsKey("ica_daconfigurazione") && preImage.GetAttributeValue<bool>("ica_daconfigurazione"))
{
CheckUser(context, srv);
}
}
protected void IsRevising()
{
// I have no clue about the logic to put here: see below.
}
protected void CheckSomeCondition(IPluginExecutionContext context, IOrganizationService srv)
{
var entity = (Entity)context.InputParameters["Target"];
// if some fields of entity contain some specific data, throw
// this always happens
}
protected void CheckUser(IPluginExecutionContext context, IOrganizationService srv)
{
//allowedUser is read from a configuration entity
var allowedUser = new Guid();
if (context.InitiatingUserId.Equals(serviceUser.Id) == false)
throw new InvalidPluginExecutionException("Can't edit quote details");
}
}
I know that (in a Quote plugin) I can know a revision is ongoing by checking the ParentContext, is anything similar available in a QuoteDetail plugin ? I gave it a try but all I get are NullReferenceExceptions thrown at me.
Should I expect to have State/Status available to check ?
For any more info which I may have overlooked, just ask.
Register on the Pre Create message (stage 20) of QuoteDetail and filter on the parent context not being for Quote. If it is, just return (effectively doing nothing).
The same applies to the Update message of the QuoteDetail.
Both messages run in the context of the ReviseQuote message for Quote.
var parentContext = context.ParentContext;
// While there is a parent context...
while (parentContext != null) {
// When parent context is for "quote", return;
if (parentContext.PrimaryEntityName == "quote")
{
return;
}
// Assign parent's parent context to loop parent context.
parentContext = parentContext.ParentContext;
}

Categories