Hi I'm new to Dynamics and plugins in dynamics. I have created a simple Entity called Library that holds books.
After a new book is created I want the price of the book to increment by a GST of 10% on the server side via a plugin.
I know this would normally occur on the page before saving by I'm trying to work out how server side logic works.
I have created a postOperation (synchronous) step for the "Create" message to call the Plugin Execute() method. From my reading this should occur AFTER the record is saved in the database.
I also have a post image entity that I access.
In the Execute method I try to access the saved record via the PostMessageEntity to update the price, but I get an exception saying the record does not exist based on the record identifier that i have obtained. I can confirm the record was never created in the system, yet the postOperation has been called.
How do I access the just saved record in the plugin so that I can update the Price?
My code:
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));
// create a trace log so you can see where in the code it breaks
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
// create access to service
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
tracingService.Trace("have reached execute event in plugin.");
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
tracingService.Trace("We have a target and it is an entity.");
// Obtain the target entity from the input parameters.
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "new_books")
{
tracingService.Trace("the entity id of the record that was created is .." + entity.Attributes["new_booksid"].ToString());
// do we have a post update image of the new_books entity
if (context.PostEntityImages.Contains("newbookpostImage") && context.PostEntityImages["newbookpostImage"] is Entity)
{
tracingService.Trace("we have a postEntityImage.");
// // yep lets grab it.
Entity postMessageEntity = (Entity)context.PostEntityImages["newbookpostImage"];
// get book price as just saved to db
decimal bookPrice = ((Money)postMessageEntity.Attributes["new_price"]).Value;
// get id of the the record we have
Guid RecordID = ((Guid)postMessageEntity.Attributes["new_booksid"]);
tracingService.Trace("we have a post update bookprice.");
tracingService.Trace("the entity id of the post image entity is ..." + postMessageEntity.Attributes["new_booksid"].ToString());
Entity created_book = new Entity("new_books");
// use service to access a field of the current record as it is in the database and column we want to update.
created_book = service.Retrieve(created_book.LogicalName, RecordID, new ColumnSet(true));
//And the last line is where it dies and tells me new_books with id with d7bfc9e2 - 2257 - ec11 - 8f8f - 00224814e6e0 does not exist.
}
}
}
}
Entity postMessageEntity = (Entity)context.PostEntityImages["newbookpostImage"];
Is your PostEntityImage new_books entity?
Also if you have an entity postMessageEntity you can directly get Entity Record ID by
postMessageEntity.ID
rather than Guid RecordID = ((Guid)postMessageEntity.Attributes["new_booksid"]);
Here your code does nothing more than create empty object of type Entity new_books.
You have not set Priamry name field of entiy or any other. Also you have not created a record, you should use
Entity created_book = new Entity("new_books")
service.Create(created_book);
Below you are trying to fecth Record from Entity new_books based on postMessageEntity.Id
You should check postMessageEntity logical name is same as created_book.LogicalName and then use postMessageEntity.ID rather than RecordID
created_book = service.Retrieve(created_book.LogicalName, RecordID, new ColumnSet(true));
In the plugin pipeline you can actually add, modify and even remove attributes in the entity object on the fly. This must be done before the main operations take place: in the prevalidation or in the preoperation stage.
So, your code can be simplified like this:
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
Debug.Assert(context.Stage <= 20); // This only works in prevalidation and preoperation stages.
var book = (Entity)context.InputParameters["Target"]; // For message Create a Target parameter is always available.
// Using GetAttributeValue is more safe, because when price is not set, the attribute will not be available in the collection.
decimal? price = book.GetAttributeValue<Money>("new_price")?.Value;
if (price.HasValue)
book["new_price"] = new Money(price.Value * 1.1M);
}
In the synchronous post create stage you are still in a database transaction. At that point the record is created, but not yet committed.
The ID of the record created can be found in the OutputParameters collection. You can pick it up like this:
var recordId = (Guid)context.OutputParameters["id"];
There is no need to do checks on the context object. When your plugin is registered properly, all items you would expect to be available will be there. If not, a proper exception log will be your best friend. Just add a generic exception handler responsible for writing the error context to the plugin trace log.
Related
I have a simple controller in which I create and add a user and a profile to the database
public async ActionResult AddUserAndProfile(string id)
{
_context.Users.Add(new User { Id = id });
_context.SaveChanges(); // If this line is removed, error doesn't occur.
var profile = new Profile
{
Id = "id",
User = _context.Users.AsNoTracking().FirstOrDefault(u => u.Id.Equals(id))
}; // Exception given on this line.
_context.Profiles.Add(profile);
_context.SaveChanges();
return Ok();
}
When I call this controller with id = "0" I get the following exception:
The instance of entity type 'User' cannot be tracked because another instance with the key value '{Id: 0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
However, if I remove the first SaveChanges() call, this error is not shown.
1st question: Is it intended that entities get tracked after saving changes, wouldn't it make sense that they get tracked beforehand only? Also afaik, add actions don't mark entities as tracked.
2nd question: When is the best time to call SaveChages() in this situation? (It is important to note that add user and add profile actions are in different repo methods in the real project. I simplified the code here.)
3rd question: What is the best way to add foreign keys in situations like this?
You should be able to save whole hierarchy within one call to SaveChanges:
var profile = new Profile
{
Id = "id",
User = new User { Id = id }
};
_context.Profiles.Add(profile);
_context.SaveChanges();
It should answer 2nd and 3rd questions I think. As for the first one - there is a github issue with request to "Do not track after SaveChanges()".
Since you have different methods you can just set the UserId property on Profile (if you have explicitly added it to Profile entity) directly.
Save the user you create, and then specify that user for the profile.
var user = new User { Id = id };
_context.Users.Add(user);
_context.SaveChanges();
var profile = new Profile {Id = "id", User = user};
I am working on an eCommerce based web application with MVC and Entity Framework.
I just want to track/know which entities are updated. For example, when the user changes the stock and price for any product and clicks the Save button, I want to track that these two entities are updated with their ProductId.
Is there any way to achieve this?
Thanks.
Disclaimer: I'm the owner of the project Entity Framework Plus
This project allows to audit / track everything what's is modified. There is a lot of options available if you only want to access modified entities.
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntityContext();
// ... ctx changes ...
var audit = new Audit();
audit.CreatedBy = "ZZZ Projects"; // Optional
ctx.SaveChanges(audit);
// Access to all auditing information
var entries = audit.Entries;
foreach(var entry in entries)
{
foreach(var property in entry.Properties)
{
}
}
Documentation: EF+ Audit
NuGet: https://www.nuget.org/packages/Z.EntityFramework.Plus.EF6/
You can use rowversion if it is not in the same http request.
http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
Yes you can with this
//let 'myObj' is entity instance
//let db is your context instance
if ( db.Entry(myObj).State == System.Data.EntityState.Modified )
{
//Try your code here
}
I am writing a asp.net mvc4 app and I am using entity framework 5. Each of my entities have fields like EnteredBy, EnteredOn, LastModifiedBy and LastModifiedOn.
I am trying to auto-save them by using the SavingChanges event. The code below has been put together from numerous blogs, SO answeres etc.
public partial class myEntities : DbContext
{
public myEntities()
{
var ctx = ((IObjectContextAdapter)this).ObjectContext;
ctx.SavingChanges += new EventHandler(context_SavingChanges);
}
private void context_SavingChanges(object sender, EventArgs e)
{
ChangeTracker.DetectChanges();
foreach (ObjectStateEntry entry in
((ObjectContext)sender).ObjectStateManager
.GetObjectStateEntries
(EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
CurrentValueRecord entryValues = entry.CurrentValues;
if (entryValues.GetOrdinal("LastModifiedBy") > 0)
{
HttpContext currContext = HttpContext.Current;
string userName = "";
DateTime now = DateTime.Now;
if (currContext.User.Identity.IsAuthenticated)
{
if (currContext.Session["userId"] != null)
{
userName = (string)currContext.Session["userName"];
}
else
{
userName = currContext.User.Identity.Name;
}
}
entryValues.SetString(
entryValues.GetOrdinal("LastModifiedBy"), userName);
entryValues.SetDateTime(
entryValues.GetOrdinal("LastModifiedOn"), now);
if (entry.State == EntityState.Added)
{
entryValues.SetString(
entryValues.GetOrdinal("EnteredBy"), userName);
entryValues.SetDateTime(
entryValues.GetOrdinal("EnteredOn"), now);
}
else
{
string enteredBy =
entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy"));
DateTime enteredOn =
entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn"));
entryValues.SetString(
entryValues.GetOrdinal("EnteredBy"),enteredBy);
entryValues.SetDateTime(
entryValues.GetOrdinal("EnteredOn"), enteredOn);
}
}
}
}
}
}
My problem is that entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy")) and entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn")) are not returning the original values but rather the current values which is null. I tested with other fields in the entity and they are returning the current value which were entered in the html form.
How do I get the original value here?
I think the problem may be that you are using the instance provided by the model binder as the input to your controller method, so EF does not know anything about that entity and its original state. Your code may look like this:
public Review Update(Review review)
{
_db.Entry(review).State = EntityState.Modified;
_db.SaveChanges();
return review;
}
In that case, EF knows nothing about the Review instance that is being saved. It is trusting you and setting it as modified, so it will save all of its properties to the database, but it does not know the original state\values of that entity.
Check the section named Entity States and the Attach and SaveChanges Methods of this tutorial. You can also check the first part of this article, that shows how EF does not know about the original values and will update all properties in the database.
As EF will need to know about the original properties, you may first load your entity from the database and then update its properties with the values received in the controller. Something like this:
public Review Update(Review review)
{
var reviewToSave = _db.Reviews.SingleOrDefault(r => r.Id == review.Id);
//Copy properties from entity received in controller to entity retrieved from the database
reviewToSave.Property1 = review.Property1;
reviewToSave.Property2 = review.Property2;
...
_db.SaveChanges();
return review;
}
This has the advantage that only modified properties will be send and updated in the database and that your views and view models don't need to expose every field in your business objects, only those that can be updated by the users. (Opening the door for having different classes for viewModels and models\business objects). The obvious disadvantage is that you will incur an additional hit to the database.
Another option mentioned in the tutorial I referenced above is for you to save the original values somehow (hidden fields, session, etc) and on save use the original values to attach the entity to the database context as unmodified. Then update that entity with the edited fields. However I would not recommend this approach unless you really need to avoid that additional database hit.
Hope that helps!
I was running into a similar problem when trying to audit log the Modified values of an Entity.
It turns out during the post back the ModelBinder doesn't have access to the original values so the Model received is lacking the correct information. I fixed my problem by using this function which clones the current values, relods the object, and then reset the current values.
void SetCorrectOriginalValues(DbEntityEntry Modified)
{
var values = Modified.CurrentValues.Clone();
Modified.Reload();
Modified.CurrentValues.SetValues(values);
Modified.State = EntityState.Modified;
}
You can gain access to the DbEntityEntry though the change tracker, or the entry function from your context.
I'm trying to associate related records of an entity to a newly created one. The pluggin triggers on creation and pre-operation.
The error occurs when trying to associate the collection to the new entity : "new_ligneContrat With Id = ad630ba6-684e-e111-92e3-00155d151905 Does Not Exist"
Here is my code :
public void Execute(IServiceProvider serviceProvider)
{
// Instanciation des services
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = factory.CreateOrganizationService(null);
Entity target = (Entity)context.InputParameters["Target"];
EntityReference contrats = (EntityReference)target.Attributes["new_contratsid"];
FetchExpression fetch = new FetchExpression(#"
<fetch distinct='false' mapping='logical'>
<entity name='" + context.PrimaryEntityName + "'><link-entity name='new_contrats' alias='nombreligne' from='new_contratsid' to='new_contratsid'><filter type='and'><condition attribute='new_contratsid' value='" + contrats.Id + "' operator='eq'></condition></filter></link-entity></entity></fetch>");
EntityCollection lines = service.RetrieveMultiple(fetch);
// Vérification qu'il y a au moins une ligne de contrat associée
if (lines.Entities.Any())
{
var first = lines.Entities.Last();
if (first.GetAttributeValue<OptionSetValue>("statecode").Value == 1)
{
FetchExpression query = new FetchExpression(#"
<fetch distinct='false' mapping='logical'>
<entity name='incident'><filter type='and'><condition attribute='new_lignecontrat' value='"+first.Id+"' operator='eq'/></filter></entity></fetch>");
EntityCollection incident = service.RetrieveMultiple(query);
if (incident.Entities.Any())
{
foreach (var e in incident.Entities)
{
e.Attributes["new_lignecontrat"] = new EntityReference (target.LogicalName, target.Id);
}
}
}
}
What is wrong???
Thanks in advance!!
Edit 1: ok seems logical since the record does not exist yet ><. Just one thing : How can I change the value of a lookup field? What is its type?
Edit 2: I've got no error when executing my code, but fields of the incident entity do not update ><'....I've tested my code with invalidPluginExceptions and the end of the code is reached...Here is the code :
Edit 3 : Code updated...
To answer your original question and its edit, yeah, you can't associate the record with another record when the core database operation hasn't been done yet.
Pre-operation: Stage in the pipeline for plug-ins that are to execute before the main
system operation. Plug-ins registered in this stage are executed
within the database transaction.
So to handle association, you can either change the stage to post-operation, or have one IPlugin class handle the pre-operation stage and another handle the post-operation stage, either in one or multiple projects.
To answer the edit, lookup fields are of the class EntityReference. (Looks like you're working with a 1:N relationship?)
To answer the second edit, I don't see anywhere in your code snippet where you assign the new EntityReference to your target Entity. Moreover, you don't have to issue an Update request to the service in the pre-operation stage because the core database operation has not been performed yet. You can just set the attribute of the Entity to be equal to the the value of your choice, and this change will be carried over to the database.
if (entity.Attributes.ContainsKey("new_lignecontrat"))
{
entity.Attributes["new_lignecontrat"] = YourEntityReference;
}
else //attribute not included in the plugin operation
{
entity.Attributes.Add("new_lignecontrat", YourEntityReference);
}
Microsoft has a demonstration of this concept in the SDK: \sdk\samplecode\cs\plug-ins\accountnumberplugin.cs
I'm creating a Dynamics CRM workflow assembly to be executed when a new Note is created on another record of any type. I need to be able to access a property Prop1 on that newly created Note entity to accomplish other tasks.
Previously I've only accessed values that were input from a field or from the user, but never on a property of a newly created entity. Any guidance would be appreciated.
UPDATE:
This is regarding CRM 4.0.
More information while I wait:
Ultimately, this workflow assembly will create an email that contains a link to the parent entity of the newly created Note record. The property I need to get is the AnnotationId. Once the Note record is created, I will be retrieving the ObjectId and ObjectTypeCode based on the AnnotationId of the newly created Note.
(In case you were curious)
Ok so if your using 4.0 custom workflows and not 3.0 callouts, you should add a workflow assembly, and use the Context service and executing context of your workflow to pull the values from the new note.
See the example below on how to access a record using the context service and the ID of your current context of execution (that should be your note)
/// <summary>
/// The Execute method is called by the workflow runtime to execute an activity.
/// </summary>
/// <param name="executionContext"> The context for the activity</param>
/// <returns></returns>
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
// Get the context service.
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext context = contextService.Context;
// Use the context service to create an instance of CrmService.
ICrmService crmService = context.CreateCrmService(true);
BusinessEntity newNote = GetNote(crmService, context.PrimaryEntityId);
string noteAttrib;
noteAttrib = newNote.Properties.Contains("AnnotationId") ? ((Lookup)newNote.Properties["annotationid"]).name.ToString() : null;
return ActivityExecutionStatus.Closed;
}
GetNotes method would be a standard query for notes by Id through a CRM service call,
here is an example slightly modified from MSDN to return a note:
private BusinessEntity getNote(ICrmService service, guid noteid)
{
// Create the column set object that indicates the fields to be retrieved.
ColumnSet cols = new ColumnSet();
// Set the columns to retrieve, you can use allColumns but its good practice to specify:
cols.Attributes = new string [] {"name"};
// Create the target object for the request.
TargetRetrieveAnnotation target = new TargetRetrieveAnnotation();
// Set the properties of the target object.
// EntityId is the GUID of the record being retrieved.
target.EntityId = noteid;
// Create the request object.
RetrieveRequest retrieve = new RetrieveRequest();
// Set the properties of the request object.
retrieve.Target = target;
retrieve.ColumnSet = cols;
// Execute the request.
RetrieveResponse retrieved = (RetrieveResponse)service.Execute(retrieve);
return RetrieveResponse;
}