Is it possible to delete entity while same is still in plugin update transaction?
It seems following code is not working. I need to delete entity when its get updated and some other circumstances
Something like:
protected void ExecutePosAnnotationtUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
if (localContext.PluginExecutionContext.Depth > 1) return;
Entity postEntityImage = null;
if (localContext.PluginExecutionContext.PostEntityImages.Contains("PostImage"))
{
if (localContext.PluginExecutionContext.PostEntityImages["PostImage"] != null)
{
postEntityImage = localContext.PluginExecutionContext.PostEntityImages["PostImage"];
}
}
Entity preEntityImage = null;
if (localContext.PluginExecutionContext.PreEntityImages.Contains("PreImage"))
{
if (localContext.PluginExecutionContext.PreEntityImages["PreImage"] != null)
{
preEntityImage = localContext.PluginExecutionContext.PreEntityImages["PreImage"];
}
}
if ((bool)postEntityImage.Attributes["isdocument"])
{
if ( some condition )
localContext.OrganizationService.Delete(postEntityImage.LogicalName, postEntityImage.Id);
}
}
`
Since you're updating, the record is there in Target.
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = serviceFactory.CreateOrganizationService(context.UserId);
var target = context.InputParameters["Target"] as Entity;
var condition = /* whatever */
if(condition)
{
service.Delete(target.LogicalName, target.Id);
}
}
Works as expected when attached to Update message, Post-Operation, Asynchronous. Works inside the Sandbox, also.
Records will not disappear at once, it takes some time (~20 seconds on my on-premise playground). If you make it Synchronous it will still work but alerts are going to come up because data disappears while being handled by the CRM during the update.
Related
we have requirement to duplicate the confirm shipment action button for some business work and also need to update some custom fields on confirm shipment long run operation is completed.
Below is my code but while doing cache update i am getting Error: Collection was modified; enumeration operation may not execute.
Please correct me where i am doing wrong
public PXAction<PX.Objects.SO.SOShipment> ConfirmShipment;
[PXUIField(DisplayName = "Confirm Shipment")]
[PXButton]
protected virtual IEnumerable confirmShipment(PXAdapter adapter)
{
if (ShipFilter.Current != null)
{
var soOrderShip = Base.Document.Current;
if (soOrderShip != null)
{
var graph = PXGraph.CreateInstance<SOShipmentEntry>();
//We are recreating an adapter like the framework would do.
var a = new PXAdapter(graph.Document)
{
Searches = new object[] { soOrderShip.ShipmentNbr }
};
using (PXTransactionScope ts = new PXTransactionScope())
{
//Note: Confirm Shipment is Action 1 :
a.Arguments.Add("actionID", 1);
PXLongOperation.StartOperation(Base, () => { foreach (SOShipment soShipment in graph.action.Press(a)) ; });
//PXLongOperation.WaitCompletion(graph.UID);
PXAutomation.CompleteAction(graph);
PXLongOperation.WaitCompletion(graph.UID);
PXLongOperation.ClearStatus(graph.UID);
graph.Document.Cache.SetValueExt<SOShipmentExt.usrKWMXDCTimeStamp>(soOrderShip, Convert.ToDateTime(Convert.ToDateTime(new PX.Data.PXGraph().Accessinfo.BusinessDate).ToShortDateString() + " " + PX.Common.PXTimeZoneInfo.Now.ToLongTimeString()));
graph.Document.Cache.SetValueExt<SOShipmentExt.usrKWMXPieceCount>(soOrderShip, Convert.ToDecimal(Base.Document.Current.ShipmentQty));
graph.Document.Cache.SetValueExt<SOShipmentExt.usrKWMXEnteredBy>(soOrderShip, this.ShipFilter.Current.EnteredBy);
graph.Document.Update(soOrderShip);
graph.Save.Press();
ts.Complete();
}
}
}
return adapter.Get();
}
Thanks in advance.
You should override the Confirmation routine, execute the Base operation, and then add your code.
Extend existing event
Getting exception "A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe."
Below is the code.
public Task Invoke(HttpContext context)
{
try
{
var userId =context.Session.GetString("UserId");
if (userId != null)
{
var user =_context.Users.Where(u => u.Id == userId).FirstOrDefault();
user.TimeStamp = DateTime.Now;
_context.SaveChanges();
}
else
{ }
}
catch(Exception ex)
{
_logger.LogError(ex.Message);
}
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
I can't tell from your code but I'm guessing:
_context is your db context
you're injecting it via the ctor
As you've discovered, this probably won't work.
Instead, inject your db context via the Invoke method.
public Task Invoke(HttpContext context, YourDbContext dbContext)
{
try
{
var userId = context.Session.GetString("UserId");
if (userId != null)
{
var user = dbContext.Users.Where(u => u.Id == userId).FirstOrDefault();
...
}
}
catch(Exception ex){...}
}
This ensures you have a scoped instance of your db context, which should be threadsafe.
In the Azure app service mobile backend service, REST API requests are handled by TableController implementation. These methods can be invoked by using corresponding methods available in client SDKs. So, i can query for a particular entity and update its status from the client side.
But how to invoke them in the server side or within the same controller? For example, if I want to query for a particular todoItem and update its status from some custom method here like
Use LookUp(id) to get the item
Update the status
Use UpdateAsync(id, item)
Here I don't know how to create a Delta object of TodoItem to call UpdateAsync(id, patch) method.
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
initrackerserviceContext context = new initrackerserviceContext();
DomainManager = new EntityDomainManager<TodoItem>(context, Request);
}
// GET tables/TodoItem
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
// PATCH tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<TodoItem> PatchTodoItem(string id, Delta<TodoItem> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/TodoItem
public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
{
TodoItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteTodoItem(string id)
{
return DeleteAsync(id);
}
}
Just use the standard Entity Framework mechanisms. For instance, to find and update a record with a status, you can just use the context:
var item = await context.TodoItems.Where(i => i.Id.Equals(myId)).FirstOrDefaultAsync<TodoItem>();
if (item != null) {
item.Complete = true;
context.Entry(item).State = EntityState.Modified;
await context.SaveChangesAsync();
}
My EF coding is not the greatest ad-hoc, but you should get the idea. Just do the Entity Framework thing.
It's better to use TableController.ReplaceAsync() method that is already implemented for us here in the source code of EntityDomainManager.
var item = Lookup(item.Id).Queryable.FirstOrDefault();
if (item != null)
{
item.Complete = true;
item = await ReplaceAsync(item.Id, item);
}
The ReplaceAsync() method correctly handles the exceptions, so I would not recommend working directly with the EF context.
I'm trying to retrieve user data from Parse (xamarin.ios using c#). I'm using an async method with await. My challenge is,each time I navigate to the tableView in the app, which should populate the user data in question,the table is always empty.
I would like to wait until the results have been returned before proceeding with the other portion of code.I have tried to use the ContinueWith() function but constantly ran into a build error -
Cannot implicitly convert type 'void' to System.Collections.Generic.IEnumerable<Parse.ParseObject>
My Questions:
Is this the best way to wait for the result?
How do I solve the build error?
Here is my current implementation:
public async void retrieveData(string username)
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync().ContinueWith(t =>{
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
});
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
You shouldn't need a ContinueWith...that's what the await should handle.
await waits on a Task and then brings back the result with the proper return type. ContinueWith returns a Task, so you would have to grab the Result from the task to make it usable.
For more on this type of thing, you may want to check out Difference between await and ContinueWith
You can try something like this.
public async void retrieveData(string username, )
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync();
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
//This is your refresh method for your TableView
this.RefreshTableView();
//or, if in iOS
NSNotificationCenter.DefaultCenter.PostNotificationName("resultsRetrieved", null);
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
To show the results in the tableView, I would recommend moving the refreshing of the tableView to a separate method that gets triggered synchronously after the results have been retrieved and parsed. This is shown with the this.RefreshTableView() call above.
If in iOS on Xamarin, another option is to post a notification to the NSNotificationCenter (the Xamarin documentation for which is here). Use the PostNotificationName part seen above instead and then add an observer in the ViewControllers that you want to be dependent on the data. This is done as follows:
Make a notificationToken object:
NSObject notificationToken;
Then in your setup method (you could put this inside of your ViewDidLoad):
void Setup ()
{
notificationToken = NSNotificationCenter.DefaultCenter.AddObserver ("resultsRetrieved", RefreshData);
}
Make your RefeshData method:
void RefreshData (NSString notifString)
{
this.tableView.ReloadData();
}
And then, make sure you dispose of the notification observer when you tear down the class
void Teardown ()
{
NSNotificationCenter.DefaultCenter.RemoveObserver (notificationToken);
}
I had a similar issue so started using callbacks. I'm using them in Xamarin.Android, pretty sure they're available in Xamarin.iOS.
Method that starts the task method - Note I am passing in a method of this class as a parameter
private async void updatedData()
{
await Utils.DataTasks.getNewLiveTips(populateTipsList);
}
Method that calls for data from server
public class DataTasks
{
public static async Task getAllData(Action<IEnumerable<ParseObjects>> callback) {
var query = new ParseQuery<ParseObjects>().OrderByDescending("updatedAt").Limit(5);
IEnumerable<ParseObjects> parseTips = await query.FindAsync();
foreach (var tip in parseTips)
{
// Save data to DB if needed
}
callback(parseTips);
}
Method I passed as parameter in the first instance is now called
private void populateTipsList(IEnumerable<ParseObjects> results)
{
mAdapter = new TipAdapter(this.Activity, results);
mRecyclerView.SetAdapter(mAdapter);
refresher.Refreshing = false;
}
I am trying to update the telephone number on all associated contacts of an account entity. the following is the code that i have used,
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 != "account") { return; }
}
else
{
return;
}
try
{
string telephoneNum = string.Empty;
IOrganizationServiceFactory serviceFactory =(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service =serviceFactory.CreateOrganizationService(context.UserId);
var id = (Guid)context.OutputParameters["id"];
telephoneNum = entity.GetAttributeValue<string>("telephone1");
UpdateContact(service, id,telephoneNum);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
"An error occurred in the plug-in.", ex);
}
}
private static void UpdateContact(IOrganizationService service, Guid id,string telephoneNUm)
{
using (var crm = new XrmServiceContext(service))
{
var res = from c in crm.CreateQuery("contact")
where c["parentcustomerid"].Equals(id)
select c;
foreach (var c in res)
{
Entity e = (Entity)c;
e["telephone1"] = telephoneNUm;
crm.UpdateObject(e);
}
crm.SaveChanges();
}
}
}
I register the plugin on update, and primary entity "account" but whenever i try to save the account form after change of number in the telephone field i see an error pop up. Am i missing something? Thank you all!
Just a hunch..but I think you are getting the Id in the wrong way..try getting it by:
Guid Id = context.PrimaryEntityId
But as Pedro suggested..you need to see what is the exact error or try to debug the plugin using attach to process..