I'm trying to make a custom workflow on Dynamics CRM. I need to delete some entities when another entity is deleted.
I created my class library and I retrieved the Guid of the deleted entity with this code:
protected override void Execute(CodeActivityContext executionContext)
{
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory =
executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service =
serviceFactory.CreateOrganizationService(context.UserId);
mService = service;
mExecutionContext = executionContext;
Guid myTipologyTypeDeleted = context.PrimaryEntityId;
bool isReading = context.PrimaryEntityName.Equals(new_tipologialettura_richiesta.EntityLogicalName);
bool isMaintenance = context.PrimaryEntityName.Equals(new_tipologiamanutenzionerichiesta.EntityLogicalName);
bool myResult = AddOnIntervention(isReading, isMaintenance, myTipologyTypeDeleted);
// Retrieve the summands and perform addition
result.Set(executionContext, myResult);
}
And here all works, I get the Guid and I get the type (reading or maintenance).
My problem is when I try to retrieve the entity with this code (the same code is working perfectly in another workflow started on record creation, but on record delete gives me error).
Entity myReadingEntity = mService.Retrieve(new_tipologialettura_richiesta.EntityLogicalName, myTipologyTypeDeleted, new ColumnSet(true));
Here I get an exception saying that no record of type MyType with id myId has been found.
I checked the record and it still exist in the DB so it has not been deleted. What I'm doing wrong?
Thanks
I think the best thing to write your custom logic here will be a Plugin, You Should write a plugin that runs on the
Message: Delete
Stage: POST
After registering the plugin on Post Delete Operation, you should add an pre-image that will be available on Post Delete with all the attributes.Instead of issuing a retrieve, the best practice is to push the required data in an image instead.
Taken from MSDN:
Registering for pre or post images to access entity attribute values results in improved plug-in performance as compared to obtaining entity attributes in plug-in code through RetrieveRequest or RetrieveMultipleRequest requests.
In your plugin change the lines of code:
Entity myReadingEntity = mService.Retrieve(new_tipologialettura_richiesta.EntityLogicalName, myTipologyTypeDeleted, new ColumnSet(true));
to
if (context.PreEntityImages.Contains("YourImageName"))
{
Entity myReadingEntity = context.PreEntityImages["YourImageName"]
}
Related
The application was built on a bunch of asp .net core mvc and entity framework.
I have a map with markers on it. I want to change the parameters of a certain object through textboxes. The request from the frontend is written in axios, and it works flawlessly. From the first time I get the changes in the database. (mysql, provider: pomelo.mysql).
When I try to access the get request for the first time, I get the old state of the object.
HttpGet request is described here:
public async Task<IEnumerable<Poi>> GetPois()
{
var pois = await _poiService.GetPois();
if (pois.Status == Domain.Enum.StatusCode.Ok)
{
return pois.Data;
}
else { return null; }
}
I have an interface that describes the necessary set of manipulations with the Poi object.
IPoiService is described here:
public interface IPoiService
{
Task<BaseResponse<IEnumerable<Poi>>> GetPois();
Task<BaseResponse<Poi>> GetPoi();
Task<BaseResponse<bool>> DeletePoi();
Task<BaseResponse<Poi>> CreatePoi();
Task<BaseResponse<Poi>> UpdatePoi(Poi entity);
}
The service for working with the Poi object is described here:
public async Task<BaseResponse<IEnumerable<Poi>>> GetPois()
{
try
{
return new BaseResponse<IEnumerable<Poi>>
{
Data = await _poiRepository.GetAll().ToListAsync(),
Status = Domain.Enum.StatusCode.Ok
};
}
catch(Exception ex)
{
return new BaseResponse<IEnumerable<Poi>>
{
Status = Domain.Enum.StatusCode.InternalServerError,
Description = $"[GetPois]: {ex.Message}"
};
}
}
BaseResponse and the corresponding interface represents the response from the database, so it doesn't affect the update problem in any way.
I also have a repository that directly implements instance operations at the database level.
The repository is described here:
public class PoiRepository : IBaseRepository<Poi>
{
private readonly ApplicationDbContext db;
public PoiRepository(ApplicationDbContext db)
{
this.db = db;
db.Database.OpenConnection();
}
public Task Create(Poi entity)
{
throw new NotImplementedException();
}
public Task Delete(Poi entity)
{
throw new NotImplementedException();
}
public IQueryable<Poi> GetAll()
{
return db.Pois;
}
public Poi Update(Poi entity)
{
db.Pois.Update(entity);
db.SaveChanges();
return entity;
}
}
Thus, I get the problem that in order to get the current data, I need to perform two HttpGet requests, and only after that EF Core will return its current value to me.
The reason that Update(entity) sends off warning bells is that you are passing entities between server and client and back. When a controller returns a View(entity) you are sending a reference entity to the view engine to build the view. The view's #Model allows you to apply bindings but it is not a client-side copy of the entity. However, when your form submit or Ajax call etc. calls back with the #model that is NOT an entity, let alone the entity the view engine was given. It will only be a copy of data and only as complete as the view bindings could populate.
So it's hard to deduce what exactly you are witnessing without stepping through the application, but my gut says you are most likely getting confused by what you think is passing entity references around. Think of it this way, in your POST actions you could accept a set of ints, strings, and such for each of the values of the model, or a completely different class definition (DTO/ViewModel) with the same fields as the entity. ASP.Net would attempt to fill in using the data submitted with a Form POST or Ajax call. By accepting an "Entity" you are just telling EF to populate the data into a new untracked entity class. It's not the same instance as a DbContext originally loaded, and the DbContext is a different instance (or should be) than when the entity was originally loaded, it isn't tracking the entity that was originally loaded.
The resulting object will only contain the details that the view happened to have stored in the individual bound controls, pieced back together behind the scenes.
My recommendation is simply to never pass entities to, and especially from a view. Use an explicit ViewModel to represent the state sent to and from a view, then in your Update method:
Fetch the actual entity using the ViewModel ID,
Check a concurrency token (RowVersionNumber / Timestamp) to ensure no changes were made to the DB since you originally fetched the data to populate the View. (optional, but recommended)
Validate the data in your view model
Copy the data from the view model into the Entity. (Automapper can help here)
SaveChanges()
No use of Update or Attach in the DbContext/DbSet.
services.AddDbContextPool<SecurityDBContext>(options =>
options.UseSqlServer(GlobalConfig.Configuration["ConnectionStrings:DefaultConnection"],
b => b.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery))
);
This is how I added dbcontext,I used AddDbContextPool so the instance will be use over and over again for performance.
db.Entry(new AdminBlockClientConfig()
{
ActionId = input.aid.ToLongReturnZiro(),
MaxValue = input.value.ToIntReturnZiro(),
IsActive = input.isActive.ToBooleanReturnFalse(),
SiteSettingId = siteSettingId.ToIntReturnZiro()
}).State = EntityState.Added;
db.SaveChanges();
This is my code for adding new entity.
readonly SecurityDBContext db = null;
static List<AdminBlockClientConfig> AdminBlockClientConfigs = null;
public AdminBlockClientConfigService(SecurityDBContext db)
{
this.db = db;
}
This is my service constructor
services.AddScoped<IAdminBlockClientConfigService, AdminBlockClientConfigService>();
And this is my service injection config
The problem:
I don't have any validation for ActionId inside of my add new entity so if the user posts -1 for ActionId the entity will not be insert into SQL Server (relation problem) the system raises an exception and everything is as planed but the main problem is that one of the instance of SecurityDBContext become corrupted and I am no longer be able to call save change on that instance because the entity instance is still attached to dbcontext.
What is need:
It would be great if I can detach the entity after an error automatically so I can save the context.
I know how to detach entity from dbcontext, I need to its happen automatically (there is so many validation need to be added to project and I can not put time for those validation and if I put that time there will be high change of missing some place and its will be bug that can destroy my application and for performance I don't like to change the way I added dbcontext instance).
Thanks for your time.
edited: AddDbContextPool is not the problem, if one of my services add invalid data to dbcontext the other services can not use that dbcontext
I have 36 fields, aligned in line by 3, so I have 12 lines. For each line I have the need to plot the field3 as the difference between field1 - field2. This operation must be repeated for each row. My difficulty lies in the fact that I am new in the world of CRM and I cannot find a solution or a guide. I attach a small schema and my current code. Furthermore all the fields have different names, the scheme should make the idea. I cannot even look for type because they all have the same type.
Schema:
field1 field2 field3 = field1-field2
field4 field5 field6 = field4-field5
field7 field8 field9 = field7-field8
etc.
The code:
public class BudgetingOnChangeUpdateOffset : 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 entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName != "budgeting")
return;
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
try
{
var budget = (Money)entity.Attributes["budget"];
var consumptive = (Money)entity.Attributes["cons"];
var offset = budget.Value - consumptive.Value;
entity.Attributes["offset"] = offset;
service.Update(entity);
}
catch (Exception ex)
{
tracingService.Trace("MyPlugin: {0}", ex.ToString());
throw;
}
}
}
}
A few thoughts:
(1) When a plugin triggers on an Update step, it will send only the field that triggered the plugin. To get additional fields you can either pass them in via an Image which you configure on the plugin step, or do a retrieve to get the columns you want.
In the code you have here, it seems that you need to get the values of the fields on which you're doing the calculations.
If you're running this plugin on Create, you can either use a Post-image with all the fields or do a seperate Retrieve operation to get all the fields. (Create steps cannot use a Pre-image because the record has yet to be created in the database)
For more info on pre- and post-images see:
Utilising Pre/Post Entity Images in a Dynamics CRM Plugin
Pre image and Post image in Dynamics crm Plugins : Advanced Plugin concepts Part 1
(2) I like to build and debug my plugin logic in a Console App before using it in a plugin. To connect a console app to CRM use an instance of the CrmServiceClient class, which is in the XrmTooling NuGet package.
The way I generally handle this is to put all the logic into a Shared Project, which reference from both the Console App and the Plugin. But, be aware that this approach relies on using a Retrieve to get the necessary data from the target entity, rather than an Image from the Plugin.
(3) If you are going to develop it straight as a plugin you might want to familiarize yourself with the Tracing Service and plugin trace logs.
(4) It is beyond the scope of this answer, but you may also want to research the concepts of "early-bound" vs. "late-bound" development in Dynamics 365. This page has some info about that.
It looks like straight forward.
1.Register this plugin step on Pre-Operation stage, remove the line service.Update(entity);. Below lines of code will take care of calculation of difference between two fields & put it in third field as needed in the same execution pipeline (database update). No need of extra update call.
var budget = (Money)entity.Attributes["budget"];
var consumptive = (Money)entity.Attributes["cons"];
var offset = budget.Value - consumptive.Value;
entity.Attributes["offset"] = offset;
var field4 = (Money)entity.Attributes["field4"];
var field5 = (Money)entity.Attributes["field5"];
var field6 = field4.Value - field5.Value;
entity.Attributes["field6"] = field6;
2.Choose only field1, field2, field4, field5 as filtering attributes in plugin step - so plugin trigger only on update of those fields & not on all fields
Like Aron explained, register the PreImage values so that you will get all the unupdated fields to calculate in plugin.
I am using ASP.NET Entity Framework and I am trying to update a single column with the following code:
[HttpGet]
public void MarkOffline(string online)
{
Users user = new Users { email = online, isOnline = false };
db.Entry(user).Property("isOnline").IsModified = true;
db.SaveChanges();
}
But I get this error:
Member 'IsModified' cannot be called for property 'isOnline' because
the entity of type 'Users' does not exist in the context. To add an
entity to the context call the Add or Attach method of DbSet<Users>.
The part I don't know how to do:
To add an entity to the context call the Add or Attach method of
DbSet<Users>.
How do I fix my problem?
If you want to update like this, you'll need to Attach the entity where the entity has its primary key set.
Given you don't have its primary key but only one of its (unique, hopefully) fields, you need to query the record first and then update it:
var existing = db.Users.FirstOrDefault(u => u.email == email);
existing.IsOnline = true;
db.SaveChanges();
That being said, this is not how you record a user's online status in a web application. You rather update a user's LastAction timestamp with each action they perform, and mark a user as offline at runtime when their LastAction is more than N seconds or minutes ago.
I have been dealing with this for days
Summary
I am creating a Social site that will be the back bone for another web application. The hangup is when I submit a request to create a group all goes well, but if I attempt to submit this form again with different data I get a DbEntityValidationException. The exception is related to the ApplicationUser entry.
Details
When I start the Application in Debug mode and submit the Group creation form for the first time it will succeed, adding all the entities into the database as excepted. I have verified this and all looks good. While in the same Debug session, I change the information in the form, to create another group, and submit the form, which leads to the DbEntityValidationException.
The error is related the when I try to insert a SocialGroupMemberModel which contains a reference to the User, and other details related to the users status in the group. The User entry is being marked as added and EntityFramework is trying to insert the User instead of updating. I have attempted to set the Navigation (User) and set the ForeignKey (UserId), both lead to the same error.
I am using HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>();
In the Controller I use ApplicationUserManager to get the User Entity, I then pass this to the Repository to create the group (in either case, either passing the ID, or Entity itself, doesn't work the second time)
Group Creation Code:
var groupInfo = new SocialGroupInfo
{
Created = DateTime.Now,
Description = model.Description,
ShortDescription = model.ShortDescription,
Name = model.Name,
Tags = TagRepo.GetTags(),
Members = new List<SocialGroupMember>()// { member }
};
var groupModel = new SocialGroupModel
{
Slug = model.Slug,
Info = groupInfo
};
Context.SocialGroups.Add(groupModel);
var member = new SocialGroupOwnerModel
{
Joined = DateTime.Now,
UserId = creator
//User = null
//Group = groupInfo
};
groupInfo.Members.Add(member);
//creator.SocialGroups.Add(member);
SaveChanges();
The Validation Error is: "User name ** is already taken" so this leads me to believe that on the second attempt to add the new group, it is attempting to add a new user.
Please ask for any additional information needed, thanks.
This issue was caused by the IoC holding a reference to the previous DbContext, unsure as to why, but removing all usage of Autofac fixed the issue.
Very anticlimactic solution, but issue fixed...
Now the issue is to figure out why Autofac was behaving this way, all Debugging showed that the classes were created each request... but that is another question.