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
Related
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.
I'm fairly new to developing but I've encountered a problem when creating a plugin for Dynamics CRM. The plugin is supposed to render the parentcustomerid field null on contact entity when the account entity it is linked to is updated when the field primarycontactid is updated to something else, either that be null on linked to another contact.
With the code I have written currently no errors are thrown and the code is executed successful but parentcustomerid field still contains the account it is linked too when it should remove the link.
Here is the code i have currently:
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
//Obtain the execution context
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
//obtain organizational services
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
EntityReference prePCID;
Entity PreImage;
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
// Obtain the image entity from the Pre Entity Images.
tracingService.Trace
("trace1: Getting the target entity from Input Parameters.");
PreImage = (Entity)context.InputParameters["Target"];
// Verify that the target entity represents an account.
// If not, this plug-in was not registered correctly.
tracingService.Trace
("trace2: Verifying that the target entity represents a account.");
if (PreImage.LogicalName == "account")
{
if (PreImage.Attributes.Contains("primarycontactid"))
{
tracingService.Trace
("trace3: Setting the primary contact id in the prePCID.");
//prePCID = (EntityReference)PreImage.Attributes["primarycontactid"];
prePCID = (EntityReference)PreImage["primarycontactid"];
tracingService.Trace
("trace4: Primary Contact Name: " + prePCID.Name + " Creating a variable that stores the contact using the prePCID.");
Entity contactToRemoveReference = service.Retrieve("contact", prePCID.Id, new ColumnSet("parentcustomerid")) as Entity;
tracingService.Trace
("trace5: Removes the id: " + prePCID.Id + " of the parentcustomerid.");
contactToRemoveReference.Attributes["parentcustomerid"] = null;
service.Update(contactToRemoveReference);
tracingService.Trace
("trace6: Execution Successful.");
}
}
}
}
If anyone could solve this issue for me, that would be great thanks.
You are populating Entity variable PreImage from primary entity in the input parameters.
I think you should get it from Pre image like:
Entity PreImage;
if (context.PreEntityImages.Contains("yourPreImageName") && context.PreEntityImages["yourPreImageName"] != null)
{
PreImage = context.PreEntityImages["yourPreImageName"];
}
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've read through at least a dozen other questions just like this one, but I am having trouble grasping some of this stuff.
I'm used to developing ASP.NET MVC3 with repositories and code-first entities linking to the entity framework.
I've recently switched to database-first ADO.NET with services development.
I find this to be very clean since I can access stuff through my foreign keys.
Anyway, my old save methods seem to be broken since I constantly get this error
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker
So here's a look at my save action and my service:
Action:
[HttpPost]
public ActionResult AddReview(Review review, int id)
{
User loggedInUser = userService.GetUserByusername(User.Identity.Name);
review.WriterId = loggedInUser.UserId;
review.ProductId = id;
if (ModelState.IsValid)
{
reviewService.Save(review);
Product product = productService.GetProduct(id);
if(product.Quantity>=1)
product.Quantity--;
product.TimesBought++;
productService.UpdateRating(product, reviewService);
loggedInUser.GoldCoins -= product.Price;
Session["goldCoins"] = loggedInUser.GoldCoins;
userService.Save(loggedInUser);
productService.Save(product);
}
else
{
return View(review);
}
return RedirectToAction("Index", "Answers", new { reviewId = review.ReviewId });
Service:
public class ReviewService : Service<Review, CapstoneEntities>
{
...
public void Save(Review review)
{
using (var db = new CapstoneEntities())
{
if (review.ReviewId == 0)
{
db.Reviews.Add(review);
db.Entry(review).State = EntityState.Added;
}
else
{
db.Entry(review).State = EntityState.Modified;
}
db.SaveChanges();
}
}
}
My suspicion is with this line of code: using (var db = new CapstoneEntities()) but I'm not sure how else to do this. Again, this worked perfectly with my old way of doing things but now I get errors on just about ever CRUD operation.
Thank you.
It looks like this is being caused by having an entity belong to multiple DataContexts. Whatever code that is calling that action should use the same DataContext to create the entity as the one used to persist it to the datastore.
In most instances you should only keep one instance of the DataContext. You can use a DI framework like Castle to define/store a dependency (in this case the DataContext) as Transient or PerWebRequest and inject it into the service and controller, so you'll always have a reference to the same instance of the DataContext.
I am new to MVC & Entity frame work. I got same problem after fighting a lot
This solution worked for me. Hope it can be useful for you guys.
var mediaItem = db.MediaItems.FirstOrDefault(x => x.Id == mediaItemViewModel.Id);
mediaItem.Name = mediaItemViewModel.Name;
mediaItem.Description = mediaItemViewModel.Description;
mediaItem.ModifiedDate = DateTime.Now;
mediaItem.FileName = mediaItem.FileName;
mediaItem.Size = KBToMBConversion(mediaItemViewModel.Size);
mediaItem.Type = mediaItem.Type;
//db.Entry(mediaItem).State = EntityState.Modified;// coment This line
db.SaveChanges();
Cause you are reading the the whole object from db and holding it in the current context and when you try to modify the entity state its tells you already one entity attached to the current context. just call save changes it will save it.