I've written a plugin with the following configuration:
I'm simply trying to set one datetime field to equal another datetime field:
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
// Obtain the target entity from the input parmameters.
Entity entity = (Entity)context.InputParameters["Target"];
try
{
if (entity.LogicalName == "list" && entity.Attributes["gbs_lastusedonoriginal"] != null)
{
entity.Attributes["lastusedon"] = entity.Attributes["gbs_lastusedonoriginal"];
service.Update(entity);
}
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occured in the plug-in.", ex);
}
}
The exception I get is:
Unhandled Exception:
System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault,
Microsoft.Xrm.Sdk, Version=6.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35]]: An error occured in the
plug-in.Detail:
-2147220891
OperationStatus
0
SubErrorCode
-2146233088
An error occured in the plug-in.
2015-01-15T05:34:00.1772929Z
[PreValidationMarketingList.Plugins:
PreValidationMarketingList.Plugins.PreValidateMarketingListCreate]
[5454a088-749c-e411-b3df-6c3be5a83130: PreValidateMarketingListCreate]
Entered
PreValidationMarketingList.Plugins.PreValidateMarketingListCreate.Execute(),
Correlation Id: 6d3ed105-f9c4-4006-9c80-08abd97c0140, Initiating User:
5e1b0493-d07b-e411-b592-f0921c199288
PreValidationMarketingList.Plugins.PreValidateMarketingListCreate is
firing for Entity: list, Message: Create, Correlation Id:
6d3ed105-f9c4-4006-9c80-08abd97c0140, Initiating User:
5e1b0493-d07b-e411-b592-f0921c199288 Exiting
PreValidationMarketingList.Plugins.PreValidateMarketingListCreate.Execute(),
Correlation Id: 6d3ed105-f9c4-4006-9c80-08abd97c0140, Initiating User:
5e1b0493-d07b-e411-b592-f0921c199288
What am I doing wrong?In crm 2013 how do I set one field to equal another field if both of them are datetimes?
You shouldn't be calling Update in this plugin because you haven't created and saved the record you are trying to update.
First, move this to pre-operation, not pre-validation. It's a nit but pre-op is really the appropriate place since setting lastusedon is not required for validation on Create of list.
I've reworked your code to do some additional checks:
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
// Obtain the target entity from the input parmameters.
Entity entity = (Entity)context.InputParameters["Target"];
try
{
if (entity.LogicalName == "list" && entity.Attributes.Contains("gbs_lastusedonoriginal") && entity["gbs_lastusedonoriginal"] != null)
{
if (entity.Attributes.Contains("lastusedon") )
entity.Attributes["lastusedon"] = entity.Attributes["gbs_lastusedonoriginal"];
else entity.Attributes.Add("lastusedon", entity.Attributes["gbs_lastusedonoriginal"];
}
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occured in the plug-in.", ex);
}
}
Check this link on MSDN. In the Pre-Event, the record is not yet saved in the SQL database. You can modify the Entity object from the InputParameters. After the pre-event plugin the record will be created with your modified attributes.
The main difference between Pre-validation and Pre-operation is that the Pre-operation stage is executed within the database transaction while Pre-validation not. See MSDN for more information.
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 have a plugin which fires on Update of the Incident entity. It creates a new record in another table (new_documentaccess). This new_documentaccess record needs to have the same Owner as that of the Incident entity.
Now, I understand that I cannot set the Owner field like any other field on the entity by doing a simple assignment.
So, I wrote in the following.
public void CreateDocumentAccess(Incident incident)
{
new_documentsaccess documentAccess = new new_documentsaccess();
documentAccess.new_CaseId = incident.ToEntityReference();
documentAccess.new_name = incident.OwnerId.Name;
Guid recordId = crmService.Create(documentAccess);
var request = new AssignRequest{
Assignee = new EntityReference(SystemUser.EntityLogicalName, incident.OwnerId.Id),
Target = new EntityReference(new_documentsaccess.EntityLogicalName, recordId)};
crmService.Execute(request);
}
However, I got the following error during execution when I was debugging with
Break when an exception is Thrown: enabled for Common Language Runtime Exceptions.
thrown at the line
var request = new AssignRequest{
Assignee = new EntityReference(SystemUser.EntityLogicalName, incident.OwnerId.Id),
Target = new EntityReference(new_documentsaccess.EntityLogicalName, recordId)};
Principal user (Id=e9e3a98d-a93e-e411-80bc-000c2908bc67, type=8) is
missing prvAssignnew_documentsaccess privilege
(Id=7ecaf3da-77c8-4ee3-9b29-e5a4455cd952)"}
My Plugin code is as follows
try
{
CreateDocumentAccess(Incident incident);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("An error occurred while creating the document access.", ex);
}
catch (Exception ex)
{
TracingService.Trace("ExecutePreIncidentUpdate: {0}", ex.ToString());
throw;
}
If I just run it as User using the front end, I get the following error.
Exiting PluginPreIncidentUpdate.Execute(), Correlation Id: 4e7e4c3c-3cef-46ab-8d08-a6d0dbca34c7, Initiating User: be179876-9b39-e411-80bb-000c2908bc67
Questions
What code changes should I be making so that even if it errors out
on the above mentioned error, the ErrorDetails.txt captures the
error Principal user (Id=e9e3a98d-a93e-e411-80bc-000c2908bc67, type=8) is missing prvAssignnew_documentsaccess privilege
(Id=7ecaf3da-77c8-4ee3-9b29-e5a4455cd952)?
Why is it not happening already?
Firstly: To answer your questions:
Trace logs are only included with InvalidPluginExecutionException. Your second catch is being used and is throwing the caught exception not an "InvalidPluginExecutionException"
Changing this:
catch (Exception ex)
{
TracingService.Trace("ExecutePreIncidentUpdate: {0}", ex.ToString());
throw;
}
To something like this should pull your trace logs through into the ErrorDetails attachment.
catch (Exception ex)
{
TracingService.Trace("ExecutePreIncidentUpdate: {0}", ex.ToString());
throw new InvalidPluginExecutionException("An error has occurred");
}
Secondly: The cause
The error you are getting is indicating that the user does not have privileges to assign a new_documentaccess record.
When you register a plugin and add a new step; you can choose which user's context to run it in.
By default this is set to "Calling User".
Can you confirm that the calling user is in a security role that has the assign privilege for new_documentaccess records?
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 working on a CRM Dynamics Plugin. There is a field on custom entity named "email". I want to make sure that for two entity records email addresses should be unique. For that purpose I have written following code:
public class Class1 : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)
serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
// Get a reference to the organization service.
IOrganizationServiceFactory factory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = factory.CreateOrganizationService(context.UserId);
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
//</snippetAccountNumberPlugin2>
// Verify that the target entity represents an account.
// If not, this plug-in was not registered correctly.
if (context.MessageName.ToUpper() == "CREATE")
{
if (entity.LogicalName == "new_assignment1entity")
{
try
{
QueryExpression query = new QueryExpression("new_assignment1entity");
query.ColumnSet.AddColumns("new_email");
EntityCollection result1 = service.RetrieveMultiple(query);
foreach (var a in result1.Entities)
{
int size = result1.Entities.Count;
if (a.Attributes["new_email"].ToString().Equals(entity["new_email"]))
throw new InvalidPluginExecutionException("Duplicate Email found!");
}
}
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
{
//You can handle an exception here or pass it back to the calling method.
throw new InvalidPluginExecutionException("Some problem occurred while Querying Records!");
}
}
}
else if (context.MessageName.ToUpper() == "UPDATE")
{
if (entity.LogicalName == "new_assignment1entity")
{
try
{
QueryExpression query = new QueryExpression("new_assignment1entity");
query.ColumnSet.AddColumns("new_email");
EntityCollection result1 = service.RetrieveMultiple(query);
foreach (var a in result1.Entities)
{
int size = result1.Entities.Count;
if (a.Attributes["new_email"].ToString().Equals(entity["new_email"]))
throw new InvalidPluginExecutionException("Duplicate Email found!");
}
}
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
{
//You can handle an exception here or pass it back to the calling method.
throw new InvalidPluginExecutionException("Some problem occurred while Querying Records!");
}
}
}
}
}
}
When User creates a new entity record with duplicate email address this code works and shows a dialog box printing error message. But when User edit an existing record (update and existing record) and makes the email address duplicate then this code does not work and updated record with duplicated email address saved.
I am guessing that Context message with UPDATE else part is not working.
Please help me out.
It's not really worth trying to debug this as unfortunately you are going about this in a horribly inefficient way. (Though the most likely cause is the way you are querying being subject to a "feature" of CRM which means you are not querying all the records you think you are).
In short, your code says:
Get me ALL(*) instances of the new_assignment1entity entity
Look at each record until I find one with an email address that matches (case-sensitive) the value just provided in the update
Throw an exception when you encounter the first exact match (otherwise continue with the transaction)
Mains points of note:
QueryExpression will only return the maximum first 5000 records in CRM
You should be filtering your query to only return new_assignment1entity records where the new_email attribute matches the provided value
String.Equals(string) is case-sensitive so to truly check for a duplicate, you should convert the case of each value
Your size variable serves no purpose
Your code will throw an exception if the new/updated record has no value for new_email. You should check that the attribute exists before attempting to access it
I resolved this issue. The problem why only Create execution flow was running and not Update is that I had only registered the plugin for create message step. To overcome this issue, I added a new step in same plugin and registered it with update message as show in following screenshot:
And it worked like charm.
Apart from this, #GregOwens mentioned very helpful points.These should follow as best practices in CRM Development.
In my application I have the following code...
public Boolean SaveUserInformation(UserInfoDTO UserInformation)
{
return dataManager.SaveUserInfo(new UserInfo()
{
UserInfoID = UserInformation.UserInfoID.HasValue ? UserInformation.UserInfoID.Value : 0,
UserID = UserInformation.UserID,
ProxyUsername = UserInformation.ProxyUsername,
Email = UserInformation.Email,
Status = UserInformation.Status
});
}
This code calls a method on a dataManager object that utilizes Entity Framework...
public Boolean SaveUserInfo(UserInfo userInfo)
{
try
{
//Validate data prior to database update
if (userInfo.UserID == null) { throw new Exception("UserInfoDomainModel object passed to PriorityOne.Data.DataManager.SaveUserInfo with UserID property set to NULL."); }
if (userInfo.ProxyUsername == null) { throw new Exception("UserInfoDomainModel object passed to PriorityOne.Data.DataManager.SaveUserInfo with ProxyUsername property set to NULL."); }
if (userInfo.Email == null) { throw new Exception("UserInfoDomainModel object passed to PriorityOne.Data.DataManager.SaveUserInfo with Email property set to NULL."); }
if (userInfo.UserInfoID == 0)
{
//Perform Insert
using (PriorityOneEntities entities = new PriorityOneEntities())
{
entities.UserInfoes.AddObject(userInfo);
entities.SaveChanges();
}
}
else
{
//Perform Update
using (PriorityOneEntities entities = new PriorityOneEntities())
{
entities.Attach(userInfo);
entities.SaveChanges();
}
}
return true;
}
catch (Exception ex)
{
//TODO: Log Error
return false;
}
}
The insert on this code works just fine. But when I try to perform an update I'm getting an error saying: "An object with a null EntityKey value cannot be attached to an object context."
It occurs on this line of code: entities.Attach(userInfo);
What I'm trying to accomplish is to avoid making a round trip to the database just to select the record that I will later make changes to and update, thus making two round trips to the database.
Any ideas what is going wrong, or how I could better accomplish this?
Thanks.
Seems like you're using EF 4.1+
You have to tell EF that you want your entity to be updated (Modified state):
//Perform Update
using (PriorityOneEntities entities = new PriorityOneEntities())
{
entities.Entry(userInfo).State = EntityState.Modified;
entities.SaveChanges();
}
P.S. You don't have to explicitly call Attach. It's done under the hood.
Update:
based on your comments, you're using EF 4.0. Here's what you have to do to attach your object as modified in EF 4.0:
ctx.AddObject("userInfoes", userInfo);
ctx.ObjectStateManager.ChangeObjectState(userInfo, EntityState.Modified);
ctx.SaveChanges();
You cannot use Attach method. From http://msdn.microsoft.com/en-us/library/bb896271.aspx:
If more than one entity of a particular type has the same key value, the Entity Framework will throw an exception. To avoid getting the exception, use the AddObject method to attach the detached objects and then change the state appropriately.
From MSDN
The object that is passed to the Attach method must have a valid
EntityKey value. If the object does not have a valid EntityKey value,
use the AttachTo method to specify the name of the entity set.
Hope this will help you.