Updating a field through workflows, better approach? - c#

I have been asked to create a view that includes entities that fit within a date range. So if the entity's new_date1 field is lesser than today, and its new_date2 field is greater than today, the entity should appear in the subgrid on the form.
Unfortunately, you can't do this with simple views as FetchXML doesn't support calculations and operators that could return today's date.
I have come up with the idea of creating an Active field on the entity, then have javascript rules set that field depending on the date range entered.
A view could then use the Active field for a filter criteria.
The problem is that if the entity's form is not opened in a while, the entity might become inactive (today's date is now beyond both date1 and date2 for example) but if the users are not opening the entity's form, the field won't update itself and the view will show inactive entities as active ones.
So I thought of have a scheduled workflow gather all entities that should be active, or inactive then this workflow launches a child workflows, that either sets the Active flag to yes or no.
Here's a bit of the code involved:
private void LaunchUpdateOpportunityWorkflow(IOrganizationService service, ITracingService tracingService, DataCollection<Entity> collection, bool active)
{
foreach (Entity entity in collection)
{
//launch a different workflow, depending on whether we want it active or inactive...
Guid wfId = (active) ? setActiveWorkflowId : setInactiveWorkflowId;
ExecuteWorkflowRequest execRequest = new ExecuteWorkflowRequest();
execRequest.WorkflowId = wfId;
execRequest.EntityId = (Guid)entity["opportunityid"];
try
{
CrmServiceExtensions.ExecuteWithRetry<ExecuteWorkflowResponse>(service, execRequest);
}
catch (Exception ex)
{
tracingService.Trace(string.Format("Error executing workflow for opportunity {0}: {1}", entity["opportunityid"], ex.Message));
}
}
}
The process of gathering the relevant DataCollection is done through simple RetrieveMultipleRequest requests.
The problem with that approach is that if the server reboots, someone has to go and start the workflow that runs the code above.
Is there better a approach to this ? I am using MS CRM 2016.

Adding to Jame's answer, if the filter criterias get complicated where it cannot be achieved using fetchxml, you can always use a plugin.
Register a plugin on "RetrieveMultiple" message.
var queryExpression = PluginExecutionContext.InputParameters["Query"];
if(queryExpression == null || !queryExpression.EntityName.equals("yourentityname", StringComparison.InvariantCultureIgnoreCase) return;
Add a condition which is unique to the advanced find, because there is no way to filter down on which advanced find is triggering the plugin on your entity, easiest way to achieve this would be to add an attribute and use it in the advanced find query.
Check for the condition, if found, the user is trying to run the advanced find you have set up:
if (queryExpression.Criteria == null || queryExpression.Criteria.Conditions == null ||
!queryExpression.Criteria.Conditions.Any()) return;
Find the matching condition, so you can remove it and add conditions you'd like to filter the data by:
var matchContidion = queryExpression.Criteria.Conditions.FirstOrDefault(c => c.AttributeName == "yourflagattribute");
if (matchContidion == null) return;
Remove the dummy match criteria and add your own criterias:
queryExpression.Criteria.Conditions.Remove(matchContidion);
queryExpression.Criteria.Conditions.Add(new ConditionExpression("new_date1", ConditionOperator.LessThan, DateTime.Now));
queryExpression.Criteria.Conditions.Add(new ConditionExpression("new_field2", ConditionOperator.Equals, "Some complex value which cannot be set using fetchxml")); //for example, based on certain values, you might want to call a webservice to get the filter value.

I think you can probably achieve this with FetchXML.
new_date1 field is lesser than today
This is older than 24 hours.
new_date2 field is greater than today
This is any date in the future, so assuming your dates are no further than 100 years in the future you can use Next X Years.
As Darren Lewis pointed out, older than 24 hours might not be yesterday depending on your definition of yesterday. In which case try using Last X Years.

Related

C# ef-core -> what is the end result of calling savechanges for multiple instances with potenial DbUpdateConcurrencyException for one instance?

I have a ef-core + sql application with a model named "indicator". It has a ConcurrencyToken set to property "TimeStampUpdated".
In a long running task I read all indicators that must be marked for repeat with code:
var indicatorsToRepeat = await careOrganisationMessagingDbContext.Indicators
.Where(ind => ind.StateRepeat == true &&
ind.TimestampRepeat != null &&
EF.Functions.DateDiffSecond(ind.TimestampRepeat, timeStampNow) >= 0)
.ToListAsync();
if (indicatorsToRepeat != null && indicatorsToRepeat.Any())
{
foreach (Indicator indicator in indicatorsToRepeat)
{
indicator.MustRepeatNow = true;
indicator.TimestampRepeat = DateTime.UtcNow.AddSeconds((int)indicator.RepeatInterval);
}
try
{
await careOrganisationMessagingDbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
// Do nothing
}
}
This task runs the repeat detection every 2 seconds and never changes "TimeStampUpdated". In potention one or more of the list of "indicatorsToRepeat" could be outdated by the shoot and forget tasks that do update indicators "State" as well as "TimeStampUpdated".
I'm not interested in detecting if indicator.DetectMustRepeatNow() saving fails. The Update must overrule and detectRepeats may not overwrite changes made by Update tasks. To achieve this I first imagined sql query with Where clause including "TimeStampUpdate" matching the old value. But this is not possible in ef-core. So I decided to use the ConcurrencyToken for "TimeStampUpdated". So update would fail if the value changed.
The real question:
I was wondering what would happen if I have 10 indicators to savechanges to where the third could not be updated due to concurrency (TimeStampUpdated changed between read and save).
Would the others (1..2 and 4..10) be updated or does the exception occure right after the third? I red this article below among others but cannot find the answer.
How to update row with concurrency control using EF Core?
Thanks in advance for answering my question,
Edwin

Entity framework core get single child from of parent child collection

I am using entity framework core and have simple table structure where system has many logs, but for my certain functionality I want to have last child of that collection so i tried:
Models.Entities.System system = await Context.Systems
.Include(log => log.MaintenanceLogs)
.FirstOrDefaultAsync(system=> system.Id == system);
But as i know I can not add
.Include(log => log.MaintenanceLogs).Last()
Well it will not work, I could create manual query and get what I want, but that is not the point.
Also I can get all logs and get last after query materializes but still there can be whole lot of that logs db records and I need only last so why waste resources.
Reason is that I need to get one value from last log and base on that value update system ( of course all in transaction ).
Can it be done ?
Can you do
Models.Entities.MaintenanceLog log = await Context.MaintenanceLogs
.Include(log => log.System)
.LastOrDefaultAsync(log => log.System.Id == system);
instead? You know, switch it around.
This would only fetch a last entry in MaintenanceLogs table where the System.Id is equal to the value of system variable.

Odata - New Hire Detection

I need to find New Hires that have been entered into Success Factors and will be starting in the future via OData.
The following C# based query is what I'm using:
DateTime tomorrow = DateTime.UtcNow.AddDays(1);
var newHires = EmpEmployment
.Expand("userNav")
.Where (e => e.startDate >= tomorrow);
The above query returns staff who have a startDate in the future however the User expansion does not find any matching entries and is null.
The issue appears to be that the User table is only populated with entries for the person when the start date is reached. This is a problem because I want to create them in other systems in advance.
Does anyone know if this is normal or thoughts around how I can obtain details like firstname, lastname of future employees who have been entered in the system?
You are trying to use EmpEmployment entity from Employee Central. The required date could be retrieved from RCM (Recruitment Management).
There is no clear answer to your question because customizing depends on the project; you should ask functional consultant responsible for EC what the way to get New hires startDate.

Linq to SQL Multiple Conditions Based On Array

I am trying to use LINQ in C# to perform a SQL query which selects multiple records based on a complex query.
I have a List of Person classes that have the following members that i want to search by:
First Name and City.
Normally in SQL I would iterate over this List and generate the following where statement
Where (Name="Name1" and City="City1") or (Name="Name2" and City="City2") or ...
I am wondering how I can achieve a similar result using LINQ.
I could do this using multiple queries but I want to use less queries due to the latency of the network and to let the database self optimize. The List will contain no more than 100 items at any time.
EDIT
As stated in the below comments the Person class was just an example to try and keep things simple. I will try and go into full detail and explain what I am trying to accomplish and see if you can help me or provide a better solution.
I have 2 database table of interest
Components
Placements
Components contains 8 fields plus an ComponentID. Every component should be unique.
Placements contains a list of fields plus a FK back to Components. There will be several Placements referencing one Component record in the database.
In C# I want to add Placements to the database with one transaction using a DataContext. I will be adding upto 1000 Placement rows to the database. If the Component does not exist I will have to create it. If it does exist I need to get its ComponentID so that I can link the Placement to the Component.
I have recently tried the following code:
The placementGroup has several members of which only 8 are needed to verify uniqueness. It should be noted that the 8 members listed below are unique between all of the objects stored in the list.
List<DBProductionComponent> existingComponents = context.DBProductionComponents.Where(dbc => placementGroup.Any(p =>
p.ComponentName == dbc.ComponentName &&
p.LotNumber == dbc.LotNumber &&
p.Manufacturer == dbc.Manufacturer &&
p.Package == dbc.PackageName &&
p.PartNumber == dbc.PartNumber &&
p.Supplier == dbc.Supplier &&
p.SupplierPartNumber == dbc.SupplierPartNumber &&
p.Value == dbc.Value
)).ToList();
But get the following error: "Local sequence cannot be used in LINQ to SQL implementations of queries except the Contains operator.

foreach loop returns only first record after Linq query with multiple records

I have a Linq2SQL query running against a database provided by a third party. The main part of the query looks like this:
var valuationQuery =
from v in context.Valuations
where v.ModelId == QualifiedModelId.ModelId
&& v.QualifyModelId == QualifiedModelId.Qualifier
&& v.LanguageCode == QualifiedModelId.LanguageCode
&& v.Edition == Data.Meta.Edition.CurrentEdition.Date
&& v.RegDate == yearReg.RegistrationDate
&& v.ValTypeDescription == "Normal"
&& v.MileageBandID == MileageBand
When I loop around it with a foreach loop, it works or fails depending on the select at the end. When the select specifies all the fields like this...
select new
{
v.Value1,
v.Value2,
v.Value3,
... snip ...
v.Value14,
v.Value15,
v.ValueTypeID
};
... it works normally. When I do the following, the loop iterates the correct amount of times, but brings back the first record every time:
select v;
I want to be able to select all the fields without specifying the names in case the vendor adds more fields (they really are called "Value1" upto "Value15"), which means I can change one constant in my code and update the DBML and all the relevant code will lookup from the correct amount of fields. This query (and similar ones) are used in various places, so I'm looking for the least-effort in the future!
I have run SQL Profiler to ensure the query being run returns the correct results, and it does.
The foreach loop is this (the Convert's are due to the very badly designed database having very inconsistent datatypes):
foreach (var record in valuationQuery)
{
int CurrentValType = Convert.ToInt32(record.ValueTypeID);
string FieldName = "Value";
if (MPLower.MileagePointID >= 1 && MPLower.MileagePointID <= MaxMileagePoints)
{
FieldName = "Value" + MPLower.MileagePointID.ToString().Trim();
MPLower.MileagePointPounds[CurrentValType] = Convert.ToInt32(record.GetType().GetProperty(FieldName).GetValue(record, null));
}
if (MPHigher.MileagePointID >= 1 && MPHigher.MileagePointID <= MaxMileagePoints)
{
FieldName = "Value" + MPHigher.MileagePointID.ToString().Trim();
MPHigher.MileagePointPounds[CurrentValType] = Convert.ToInt32(record.GetType().GetProperty(FieldName).GetValue(record, null));
}
}
I'm new to C# (I've inherited some code), so I realise it's probably something stupid I've done or not done!! Please can anyone help?
Identity Maps
A common problem when using ORMs (ok, maybe not exactly common, but it can happen) is that a query which you expect to return distinct records ends up returning multiple copies of the same record. This is often caused by the ORM's Identity Map. Here's a quick overview of how it normally works.
The identity map is essentially an object cache, based on the primary key of each object.
When you ask the ORM for a record with a certain primary key, it'll check to see if the record already exists in the identity map. If it does already exist, it'll return the existing record.
This is usually pretty handy, but sometimes things go wrong. If two objects have the same primary key, or if you haven't specified what the primary keys are (forcing the ORM to guess), the identity map can't distinguish between them, and the first one will always be returned.
The moral of the story is, always double-check your primary keys if you are seeing multiple copies of the same record where you shouldn't be!
In this question, the code
select new
{
v.Value1,
v.Value2,
v.Value3,
... snip ...
v.Value14,
v.Value15,
v.ValueTypeID
};
works because you are using the select new{} projection to return an anonymous type, which bypasses the identity map.
select v
selects the objects directly, which does use the identity map, causing the problem you were seeing.

Categories

Resources