How to filter the list by using LINQ - c#

I have a list mentioned below.
var fakedata = new Dictionary<Gateway, List<FeMeasurementValues>>()
{
{
new Gateway { SiteId = 1, FirmwareVersion = "1.1.1", ConnectivityStatus = GatewayConnectivityStatus.ReadyToConnect },
new List<FeMeasurementValues>() { new FeMeasurementValues { MeasurementName = "MsgLRRID", Values = new List<FeValue> { new FeValue { Value = "FFFF123", Horodate = DateTime.Now } } } }
},
{
new Gateway { SiteId = 2, FirmwareVersion = "1.1.2", ConnectivityStatus = GatewayConnectivityStatus.Connected },
new List<FeMeasurementValues>() { new FeMeasurementValues { MeasurementName = "MsgLRRID", Values = new List<FeValue> { new FeValue { Value = "GH67123", Horodate = DateTime.Now } } } }
},
{
new Gateway { SiteId = 3, FirmwareVersion = "1.1.3", ConnectivityStatus = GatewayConnectivityStatus.Disconnected },
new List<FeMeasurementValues>() { new FeMeasurementValues { MeasurementName = "MsgLRRID", Values = new List<FeValue> { new FeValue { Value = " ", Horodate = DateTime.Now } } } }
},
{
new Gateway { SiteId = 4, FirmwareVersion = "1.1.1", ConnectivityStatus = GatewayConnectivityStatus.Connected },
new List<FeMeasurementValues>() { new FeMeasurementValues { MeasurementName = "MsgLRRID", Values = new List<FeValue> { new FeValue { Value = "SA67123", Horodate = DateTime.Now } } } }
}
};
I have two methods
"GetPublicNetworkUsedCount()" which needs to return the count of Value which starts with "FFFF"
So, In this case output should be 1.
"GetPrivateNetworkUsedCount()" which needs to return the count of Value which does not starts with "FFFF" and which includes empty values.
So, In this case output should be 3.
Below is what i have tried:
private static string GetPublicNetworkUsedCount(List<FeValue> values)
{
var countofPublicNetwork = values.Where(x => x.Value.Any(f => x.Value.StartsWith("FFFF")));
return countofPublicNetwork.Count().ToString();
}
private static string GetPrivateNetworkUsedCount(List<FeValue> values)
{
var countofPrivateNetwork = values.Where(x => x.Value.Any(f => !x.Value.StartsWith("FFFF")));
return countofPrivateNetwork.Count().ToString();
}
I'm getting the wrong output as 0 for GetPublicNetworkUsedCount and 1 for GetPrivateNetworkUsedCount.
Please help me.

x.Value.Any() will return true as soon as the condintion inside is true. which leads to return 1 convertet to a number.
to get alll entries starting with FFFF remove the Any part like:
var countofPublicNetwork = values.Where(x =>x.Value.StartsWith("FFFF"));
you can get the count directly if you substitute .Where() with .Count() like Mark mentioned in his comment.
var countofPublicNetwork = values.Count(x =>x.Value.StartsWith("FFFF"));

You have a List<FeValue>, where each object has a string Value. You are treating the string as a collection and going one step to deep, the .Any( is not needed.
So the check should just be
values.Where(x => x.Value.StartsWith("FFFF")).Count();
Or just
values.Count(x => x.Value.StartsWith("FFFF"));

See comments:
// vv I'd recommend to return int
private static string GetPublicNetworkUsedCount(List<FeValue> values)
{ // vv Any doesn't make sense here: this is a string
var countofPublicNetwork = values.Where(x => x.Value.Any(f => x.Value.StartsWith("FFFF")));
return countofPublicNetwork.Count().ToString();
}
private static string GetPrivateNetworkUsedCount(List<FeValue> values)
{
var countofPrivateNetwork = values.Where(x => x.Value.Any(f => !x.Value.StartsWith("FFFF")));
return countofPrivateNetwork.Count().ToString();
}
So, I'd do something like this:
private static int GetPublicNetworkUsedCount(List<FeValue> values)
=> values.Count(x => x.Value.StartsWith("FFFF"));
private static int GetPrivateNetworkUsedCount(List<FeValue> values)
=> values.Count(x => !x.Value.StartsWith("FFFF"));
The returned int can then be stringyfied if need be.
Maybe I'd even do
public static class FeValueListExtensions
{
public static int GetPublicNetworkUsedCount(this List<FeValue> values)
=> values.Count(x => x.Value.StartsWith("FFFF"));
public static int GetPrivateNetworkUsedCount(this List<FeValue> values)
=> values.Count(x => !x.Value.StartsWith("FFFF"));
}
which can then be used as
// Assume we have a List<FeValue> defined as
List<FeValue> feValues = ...
var publicCount = feValues.GetPublicNetworkUsedCount();

Related

How to filter a list using another list in LINQ C#

The list needs to filter is having data like: '1000', '1000A', '1000B', '2000', '2000C', '2003', '2006A'
The list by which I am filtering having data like: '1000', '2000', '2003'
Expected output: 1000', '1000A', '1000B', '2000', '2000C', '2003'
(output is expected like we do in SQL server LIKE operator)
Suppose you are having two class like below,
public class MainClass
{
public string ActualValue { get; set; }
}
public class FilterClass
{
public string Description { get; set; }
}
I am loading some dummy data like this,
List<MainClass> mainList = new List<MainClass>();
mainList.Add(new MainClass() { ActualValue = "1000" });
mainList.Add(new MainClass() { ActualValue = "1000A" });
mainList.Add(new MainClass() { ActualValue = "1002F" });
mainList.Add(new MainClass() { ActualValue = "1002A" });
mainList.Add(new MainClass() { ActualValue = "1003" });
List<FilterClass> filterList = new List<FilterClass>();
filterList.Add(new FilterClass() { Description = "1003" });
filterList.Add(new FilterClass() { Description = "1002" });
The O/P will be given as per your requirement by,
var output1 = mainList.Where(x => filterList.Any(y => x.ActualValue.Contains(y.Description))).ToList();
Try with regex, like this:
var list1 = new List<string>{"1000", "1000A", "1000B","2000","2000A","3000BV"};
var list2 = new List<string>{"1000","2000"};
var result = list1.Where(x => list2.Any(y => Regex.IsMatch(x, $".*{y}.*"))).ToList();
Note: .* are the equivalent of % in SQL.
you could use linq in this way :
var filterList = new List<string>(){"1000", "1000A", "1000B", "2000", "2000C", "2003", "2006A"};
var filterLikeList = new List<string>(){"1000", "2000", "2003"};
var results = filterList.Where(x=> filterLikeList.Any(y=>x.Contains(y)));

Xunit - unit test with list or object as parameter

I am currently working at the unit tests of my REST API. I have a problem when I have a list or custom object as a parameter.
The first controller method has several parameters including a string List. I don't know how to add the values in the inline data for the List.
private static List<string> TestData()
{
var testcase = new List<string>();
testcase.Add("DEV");
testcase.Add("IT");
return testcase;
}
[Theory]
[InlineData(0, 10, TestData, "", 3)]
public async Task TestGetPersonItems(int pageNumber, int pageSize, List<string> departments, string filterText, int resultCount)
{
using (API_DB_Context context = new API_DB_Context(_options))
{
// Arrange
//List<string> departments = new List<string>();
//departments.Add("DEV");
List<string> locations = new List<string>();
PersonenController controller = new PersonenController(context, _mapper);
// Act
var controllerResponse = await controller.Get(pageNumber, pageSize, departments, locations, filterText);
// Assert
if (resultCount > 0)
{
var objectResult = Assert.IsAssignableFrom<ObjectResult>(controllerResponse);
ICollection<PersonDTO> model = Assert.IsAssignableFrom<ICollection<PersonDTO>>(objectResult.Value);
Assert.Equal(resultCount, model.Count);
}
else
{
Assert.IsType<NotFoundObjectResult>(controllerResponse);
var objectResponse = controllerResponse as ObjectResult;
Assert.Equal(404, objectResponse.StatusCode);
}
}
}
For the second controller method I have an custom object and an integer as parameters. When I start test second test, i get the error "Could not find public static member (property, field, or method) named 'SearchData'"
public static IEnumerable<object[]> SearchData()
{
yield return new object[] {
new SearchDTO
{
searchText = "",
page = 0,
pageSize = 10
}
};
}
[Theory]
[MemberData(nameof(SearchData), 3)]
public async Task TestSearchPersonItems(SearchDTO searchDTO, int resultCount)
{
using (API_DB_Context context = new API_DB_Context(_options))
{
// Arrange
PersonenController controller = new PersonenController(context, _mapper);
// Act
var controllerResponse = await controller.SearchPersons(searchDTO);
// Assert
if (resultCount > 0)
{
var objectResult = Assert.IsAssignableFrom<ObjectResult>(controllerResponse);
ICollection<PersonDTO> model = Assert.IsAssignableFrom<ICollection<PersonDTO>>(objectResult.Value);
Assert.Equal(resultCount, model.Count);
}
else
{
Assert.IsType<NotFoundObjectResult>(controllerResponse);
var objectResponse = controllerResponse as ObjectResult;
Assert.Equal(404, objectResponse.StatusCode);
}
}
}
What do I have to do differently or what can I do better?
Thank you in advance!
For the first UnitTest:
public static IEnumerable<object[]> TestGetPersonItemsData =>
new List<object[]>
{
new object[] { 0, 10, new List<string> { "DEV", "IT" }, "", 3 }
};
[Theory]
[MemberData(nameof(TestGetPersonItemsData))]
public async Task TestGetPersonItems(int pageNumber, int pageSize, List<string> departments, string filterText, int resultCount)
{
// Test
}
And for the second write it like this:
public static IEnumerable<object[]> TestSearchPersonItemsData =>
new List<object[]>
{
new object[] { new SearchDTO { searchText = "", page = 0, pageSize = 10 }, 3 },
new object[] { new SearchDTO { searchText = "test", page = 1, pageSize = 10 }, 1 }
};
[Theory]
[MemberData(nameof(TestSearchPersonItemsData))]
public async Task TestSearchPersonItems(SearchDTO searchDTO, int resultCount)
{
// Test
}
The member data needs to return all the parameters for the parameterized test
public static IEnumerable<object[]> SearchData() {
yield return new object[] { new SearchDTO { searchText = "", page = 0, pageSize = 10 }, 3 };
yield return new object[] { new SearchDTO { searchText = "", page = 0, pageSize = 5 }, 6 };
}
[Theory]
[MemberData(nameof(SearchData))]
public async Task TestSearchPersonItems(SearchDTO searchDTO, int resultCount) {
//...
}
Reference xUnit Theory: Working With InlineData, MemberData, ClassData

How to invoke Method immediately after instantiating

I have a controller that calls an api to get a list of Positions and Employees . First it puts the api results into a model class - IDMSElements (1). Then the controller takes the IDMSElements object and converts it to a PositionSlots object (2). Then the PositionSlots object needs to be updated with additional data from a database (3). So in simplified version of my controller I have:
(1) IDMSElements elements = getslots.CopyDocToElements(doc);
(2) PositionSlots myslots = (PositionSlots)elements;
(3) myslots.UpdateDetails(db);
I am concerned about the myslots.UpdateDetails(db) because additional code in the controller depends on UpdateDetails having been run. I would like the UpdateDetails to be run by default when creating the PositionSlots object. But I think multiple database calls in a constructor probably should not be done. How can I make it so the UpdateDetails is automatically invoked after the PositionSlots object is instantiated?
Thank you very much!
Controller:
[Authorize]
public class PSListController : Controller
{
private static readonly log4net.ILog _logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private PositionControlContext db = new PositionControlContext();
private GetPositionSlots getslots = new GetPositionSlots();
...
public async Task<ActionResult> ByCostCenter(int costCenter)
{
string ssapiuri = getslots.BuildAPIuri($"/current/?UDAKunit={costCenter.ToString()}");
_logger.Debug($"{ssapiuri.ToString()}");
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
HttpResponseMessage result = await getslots.GetDataFromIDMSapi(ssapiuri);
stopWatch.Stop();
_logger.Debug($"Response received. Milliseconds elapsed: {stopWatch.Elapsed.TotalMilliseconds.ToString()}");
if (result.StatusCode != HttpStatusCode.OK)
{
_logger.Debug("Error retrieving data from API. Milliseconds elapsed: " + stopWatch.Elapsed.TotalMilliseconds.ToString());
throw new HttpException(404, "NotFound");
}
stopWatch.Restart();
XDocument doc = XDocument.Load(result.Content.ReadAsStreamAsync().Result);
stopWatch.Stop();
_logger.Debug($"API result loaded into XDocument. Milliseconds elapsed: {stopWatch.Elapsed.TotalMilliseconds.ToString()}\n");
_logger.Debug(doc.ToString());
IDMSElements elements = getslots.CopyDocToElements(doc);
XMLDocStats docstats = new XMLDocStats(elements);
_logger.Debug(docstats.ToString());
PositionSlots myslots = (PositionSlots)elements;
myslots.UpdateDetails(db);
//because this is dependent upon UpdatePositionSlotId having been run
//need to find a way to force UpdateDetails to run other than calling from Controller??
var mainPositionSlots = myslots.PositionsCurrentAndActive;
var budget = db.Database.SqlQuery<Budget>("GetBudgetForCostCenter #costCenter = {0}", costCenter);
List<Budget> budgetRows = budget.ToList();
JobClassGroups jobClassGroups = new JobClassGroups(mainPositionSlots, budgetRows);
Department dept = db.Departments.AsNoTracking().Where(d => d.CostCenter == costCenter).SingleOrDefault();
var model = new ByCostCenter_vm(dept, myslots, jobClassGroups);
ViewBag.stats = docstats.ToString();
return View("ByCostCenter", model);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
IDMSElements Class:
public class IDMSElements
{
//properties
public ICollection<IDMSElementData> Elements { get; set; }
//constructors
public IDMSElements() { }
public IDMSElements(ICollection<IDMSElementData> elList)
{
Elements = elList;
}
//methods
public static explicit operator PositionSlots(IDMSElements obj)
{
//this is assuming we are looking at a list of elements
//that contains the "current" positions
Dictionary<string, PositionSlotDetail> positionSlots = new Dictionary<string, PositionSlotDetail>();
var sorted = from element in obj.Elements
orderby element.positionNbr
select element;
foreach (IDMSElementData el in sorted)
{
if (!positionSlots.ContainsKey(el.positionNbr))
{
PositionSlotDetail psd = new PositionSlotDetail
{
CostCenter = Int32.Parse(el.UDAKunit),
CostCenter_7Char = el.UDAKunit,
PositionNumber = el.positionNbr,
PositionSlotId = 0,
JobClassId = el.jobClassCode.Replace("-", ""),
JobClassFullDisplayCd = string.Empty,
JobTitle = string.Empty,
SalaryGradeCd = string.Empty,
FTE_percent = el.FTEpercent / 100,
EmployeeId = el.employeeID,
EmployeeName = String.Empty,
PositionEffDate = el.positionEffDate,
PositionEffDateNext = el.positionEffDateNext,
PositionBeginDate = el.positionBeginDate,
PositionEndDate = el.positionEndDate,
DirectLeaderID = string.Empty,
DirectLeaderName = string.Empty,
DirectLeaderNetID = string.Empty,
FLSAstatusCode = el.FLSAstatusCode,
FLSAstatusDesc = el.FLSAstatusDesc,
EmploymentTypeCode = el.employmentTypeCode,
EmploymentTypeDesc = el.employmentTypeDesc,
IsOverloaded = false,
};
positionSlots[el.positionNbr] = psd;
}
Assignment newAssignment = new Assignment
{
PvID = el.employeeID,
AssignBeginDate = el.assignBeginDate,
AssignEndDate = el.assignEndDate,
AssignEffDate = el.assignEffDate,
AssignEffDateNext = el.assignEffDateNext,
};
PositionSlotDetail thePSD = positionSlots[el.positionNbr];
thePSD.Assignments.Add(newAssignment);
if (thePSD.Assignments.Any(assignment => Regex.IsMatch(assignment.PvID, #"^\d+$")))
{
thePSD.Status = "FILLED";
if (thePSD.Assignments.Where(assignment => Regex.IsMatch(assignment.PvID, #"^\d+$")).Count() > 1)
{
thePSD.IsOverloaded = true;
}
}
else
{
thePSD.Status = "VACANT";
}
}
var output = new PositionSlots(positionSlots.Values.ToList());
return output;
}
...
}
PositionSlots class:
public class PositionSlots
{
//Constructor
public PositionSlots(ICollection<PositionSlotDetail> pslist)
{
Positions = pslist;
}
//properites
public ICollection<PositionSlotDetail> Positions { get; }
private bool DetailsUpdated { get; set; } = false;
public IEnumerable<PositionSlotDetail> PositionsCurrentAndActive
{
get
{
return from position in Positions
where position.PositionSlotId > 0 && position.PositionEndDate >= DateTime.Today
select position;
}
}
public IEnumerable<PositionSlotDetail> PositionsNotFoundInPositionControl
{
get
{
return from position in Positions
where position.PositionSlotId == 0
select position;
}
}
public IEnumerable<PositionSlotDetail> PositionsClosed
{
get
{
return from psd in Positions
where psd.PositionEndDate < DateTime.Today
&& psd.PositionSlotId > 0
select psd;
}
}
public decimal ActualFTETotal
{
get
{
return (from position in PositionsCurrentAndActive
from assignment in position.Assignments
where position.Assignments.Count() >= 1 && (!assignment.PvID.Equals("VACANT"))
select position.FTE_percent).Sum();
}
}
public int FilledTotal
{
get
{
return PositionsCurrentAndActive.Where(x => x.Status == "FILLED").Count();
}
}
public int VacantTotal
{
get
{
return PositionsCurrentAndActive.Where(x => x.Status == "VACANT").Count();
}
}
public int OverloadedTotal
{
get
{
return PositionsCurrentAndActive.Where(x => x.IsOverloaded).Count();
}
}
//methods
public void UpdateDetails(PositionControlContext db)
{
if (!DetailsUpdated)
{
UpdateJobClassificationInfo(db);
UpdateEmployeeName(db);
UpdatePositionSlotId(db); //if not found, PositionSlotId = 0
//UpdateIsBudgeted(db);
UpdateDirectLeader(db);
DetailsUpdated = true;
}
else
{
return;
}
}
private void UpdateJobClassificationInfo(PositionControlContext db)
{
string[] jobClassIds = (from x in Positions select x.JobClassId).Distinct().ToArray();
var JobClassList = (from jc in db.JobClassifications where jobClassIds.Contains(jc.JobClassId) select jc).ToDictionary(jc => jc.JobClassId, jc => jc, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
if (!string.IsNullOrWhiteSpace(psd.JobClassId) && !psd.JobClassId.Equals("MISSING"))
{
JobClassification jobClassification;
if (JobClassList.TryGetValue(psd.JobClassId, out jobClassification))
{
psd.JobClassFullDisplayCd = jobClassification.JobClassFullDisplayCd;
psd.JobTitle = jobClassification.JobTitle;
psd.SalaryGradeCd = jobClassification.SalaryGradeCd;
}
else
{
psd.JobClassFullDisplayCd = ($"{psd.JobClassId} not found in view V_JobClassifications.");
psd.JobTitle = ($"{psd.JobClassId} not found in view V_JobClassifications.");
psd.SalaryGradeCd = "--";
}
}
else
{
psd.JobClassFullDisplayCd = "MISSING";
psd.JobTitle = "MISSING";
}
}
return;
}
private void UpdateEmployeeName(PositionControlContext db)
{
string[] empIdsStr = (from position in Positions
from assignment in position.Assignments
where (!assignment.PvID.Equals("VACANT"))
select assignment.PvID).Distinct().ToArray();
// Positions.SelectMany(x => x.Assignments).SelectMany(x => x.PvID).ToArray();
//string[] empIdsStr = (from x in Positions where (!x.EmployeeId.Contains("VACANT")) select x.EmployeeId).Distinct().ToArray();
//int[] empIdsInt = Array.ConvertAll(empIdsStr, int.Parse);
var EmployeeList = (from emp in db.IdAM_personLU where empIdsStr.Contains(emp.pvID) select emp).ToDictionary(emp => emp.pvID,
emp => emp.EmployeeFullName, StringComparer.OrdinalIgnoreCase);
EmployeeList["VACANT"] = "VACANT";
foreach (PositionSlotDetail psd in Positions)
{
string empName;
if (EmployeeList.TryGetValue(psd.EmployeeId, out empName))
{
psd.EmployeeName = empName;
}
else
{
psd.EmployeeName = ($"{psd.EmployeeId} not found in Employee table.");
}
foreach (Assignment emp in psd.Assignments)
{
string empName2;
if (EmployeeList.TryGetValue(emp.PvID, out empName2))
{
emp.EmpDisplayName = empName2;
}
else
{
emp.EmpDisplayName = ($"{psd.EmployeeId} not found in Employee table.");
}
}
}
return;
}
private void UpdateDirectLeader(PositionControlContext db)
{
string[] empIdsStr = (from x in Positions where (!x.EmployeeId.Contains("VACANT")) select x.EmployeeId).Distinct().ToArray();
//int[] empIdsInt = Array.ConvertAll(empIdsStr, int.Parse);
Dictionary<string, IdAM_arborLU> DirectLeader = new Dictionary<string, IdAM_arborLU>();
var EmployeeDirectLeaderList = (from emp in db.IdAM_arborLU where empIdsStr.Contains(emp.emp_pvID) select emp).ToDictionary(emp => emp.emp_pvID,
emp => emp, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
if (psd.EmployeeId != "VACANT") //will just leave DirectLeaderId and DirectLeaderName as string.Empty
{
IdAM_arborLU supervisor;
if (EmployeeDirectLeaderList.TryGetValue(psd.EmployeeId, out supervisor))
{
psd.DirectLeaderName = supervisor.sup_name;
psd.DirectLeaderID = supervisor.sup_pvID;
psd.DirectLeaderNetID = supervisor.sup_netID;
}
else
{
psd.DirectLeaderName = ($"{psd.EmployeeId} not found in Arbor table.");
}
}
foreach (Assignment emp in psd.Assignments)
{
if (psd.EmployeeId != "VACANT")
{
IdAM_arborLU supervisor2;
if (EmployeeDirectLeaderList.TryGetValue(psd.EmployeeId, out supervisor2))
{
emp.DirectLeaderName = supervisor2.sup_name;
emp.DirectLeaderID = supervisor2.sup_pvID;
emp.DirectLeaderNetID = supervisor2.sup_netID;
}
else
{
emp.DirectLeaderName = ($"{psd.EmployeeId} not found in Arbor table.");
emp.DirectLeaderID = "--";
emp.DirectLeaderNetID = "--";
}
}
}
}
return;
}
private void UpdatePositionSlotId(PositionControlContext db)
{
string[] posnumber = (from x in Positions
select x.PositionNumber).ToArray();
var slots = (from ps1 in db.PositionSlots
where posnumber.Contains(ps1.PositionNumber)
select ps1).ToDictionary(ps => ps.PositionNumber.Trim(), ps => ps.PositionSlotId, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
int posSlotId = 0;
if (slots.TryGetValue(psd.PositionNumber, out posSlotId))
{
psd.PositionSlotId = posSlotId;
}
}
return;
}
private void UpdateIsBudgeted(PositionControlContext db)
{
string[] posnumber = (from x in Positions
select x.PositionNumber).ToArray();
var slots = (from ps1 in db.PositionSlots
where posnumber.Contains(ps1.PositionNumber)
select ps1).ToDictionary(ps => ps.PositionNumber.Trim(), ps => ps.IsBudgeted, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
bool isbudgeted = false;
if (slots.TryGetValue(psd.PositionNumber, out isbudgeted))
{
psd.IsBudgeted = isbudgeted;
}
}
return;
}
}
you can achieve this by writing a method:
IDMSElement.ToPositionSlot(db)
and then use it as follow:
PositionSlots myslots = elements.Select(x => x.ToPositionSlot(db))

Use workflow to evaluate dynamic expression

I would like to pass an object and expression into a dynamically created workflow to mimic the Eval function found in many languages. Can anyone help me out with what I am doing wrong? The code below is a very simple example if taking in a Policy object, multiple its premium by 1.05, then return the result. It throws the exception:
Additional information: The following errors were encountered while processing the workflow tree:
'DynamicActivity': The private implementation of activity '1: DynamicActivity' has the following validation error: Value for a required activity argument 'To' was not supplied.
And the code:
using System.Activities;
using System.Activities.Statements;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Policy p = new Policy() { Premium = 100, Year = 2016 };
var inputPolicy = new InArgument<Policy>();
var theOutput = new OutArgument<object>();
Activity dynamicWorkflow = new DynamicActivity()
{
Properties =
{
new DynamicActivityProperty
{
Name="Policy",
Type=typeof(InArgument<Policy>),
Value=inputPolicy
}
},
Implementation = () => new Sequence()
{
Activities =
{
new Assign()
{
To = theOutput,
Value=new InArgument<string>() { Expression = "Policy.Premium * 1.05" }
}
}
}
};
WorkflowInvoker.Invoke(dynamicWorkflow);
}
}
public class Policy
{
public int Premium { get; set; }
public int Year { get; set; }
}
}
You can use Workflow Foundation to evaluate expressions, but it is far easier to use almost any other option.
The key issue at play with your code was that you were not trying to evaluate the expression (with either VisualBasicValue or CSharpValue). Assigning InArgument`1.Expression is an attempt to set the value - not to set the value to the result of an expression.
Keep in mind that compiling expressions is fairly slow (>10ms), but the resultant compiled expression can be cached for quick executions.
Using Workflow:
class Program
{
static void Main(string[] args)
{
// this is slow, only do this once per expression
var evaluator = new PolicyExpressionEvaluator("Policy.Premium * 1.05");
// this is fairly fast
var policy1 = new Policy() { Premium = 100, Year = 2016 };
var result1 = evaluator.Evaluate(policy1);
var policy2 = new Policy() { Premium = 150, Year = 2016 };
var result2 = evaluator.Evaluate(policy2);
Console.WriteLine($"Policy 1: {result1}, Policy 2: {result2}");
}
}
public class Policy
{
public double Premium, Year;
}
class PolicyExpressionEvaluator
{
const string
ParamName = "Policy",
ResultName = "result";
public PolicyExpressionEvaluator(string expression)
{
var paramVariable = new Variable<Policy>(ParamName);
var resultVariable = new Variable<double>(ResultName);
var daRoot = new DynamicActivity()
{
Name = "DemoExpressionActivity",
Properties =
{
new DynamicActivityProperty() { Name = ParamName, Type = typeof(InArgument<Policy>) },
new DynamicActivityProperty() { Name = ResultName, Type = typeof(OutArgument<double>) }
},
Implementation = () => new Assign<double>()
{
To = new ArgumentReference<double>() { ArgumentName = ResultName },
Value = new InArgument<double>(new CSharpValue<double>(expression))
}
};
CSharpExpressionTools.CompileExpressions(daRoot, typeof(Policy).Assembly);
this.Activity = daRoot;
}
public DynamicActivity Activity { get; }
public double Evaluate(Policy p)
{
var results = WorkflowInvoker.Invoke(this.Activity,
new Dictionary<string, object>() { { ParamName, p } });
return (double)results[ResultName];
}
}
internal static class CSharpExpressionTools
{
public static void CompileExpressions(DynamicActivity dynamicActivity, params Assembly[] references)
{
// See https://learn.microsoft.com/en-us/dotnet/framework/windows-workflow-foundation/csharp-expressions
string activityName = dynamicActivity.Name;
string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings
{
Activity = dynamicActivity,
Language = "C#",
ActivityName = activityType,
ActivityNamespace = activityNamespace,
RootNamespace = null,
GenerateAsPartialClass = false,
AlwaysGenerateSource = true,
ForImplementation = true
};
// add assembly references
TextExpression.SetReferencesForImplementation(dynamicActivity, references.Select(a => (AssemblyReference)a).ToList());
// Compile the C# expression.
var results = new TextExpressionCompiler(settings).Compile();
if (results.HasErrors)
{
throw new Exception("Compilation failed.");
}
// attach compilation result to live activity
var compiledExpression = (ICompiledExpressionRoot)Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity });
CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpression);
}
}
Compare to the equivalent Roslyn code - most of which is fluff that is not really needed:
public class PolicyEvaluatorGlobals
{
public Policy Policy { get; }
public PolicyEvaluatorGlobals(Policy p)
{
this.Policy = p;
}
}
internal class PolicyExpressionEvaluator
{
private readonly ScriptRunner<double> EvaluateInternal;
public PolicyExpressionEvaluator(string expression)
{
var usings = new[]
{
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks"
};
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
.ToArray();
var options = ScriptOptions.Default
.AddImports(usings)
.AddReferences(references);
this.EvaluateInternal = CSharpScript.Create<double>(expression, options, globalsType: typeof(PolicyEvaluatorGlobals))
.CreateDelegate();
}
internal double Evaluate(Policy policy)
{
return EvaluateInternal(new PolicyEvaluatorGlobals(policy)).Result;
}
}
Roslyn is fully documented, and has the helpful Scripting API Samples page with examples.

Linq - Get items from the list based on "weight" and "order"

I am trying to create a class which compute the completeness of a profile of a user and I am trying to get the next steps (Evaluators) in order for the user to achieve the next completeness level.
public class Evaluator
{
public Evaluator(string name, int value, Func<CompletionStatus, bool> rule)
{
Name = name;
Value = value;
Action = rule;
}
public int Value { get; private set; }
public Func<Completion, bool> Rule { get; private set; }
public bool IsCompleted { get;set; }
public int Run(CompletionStatus status)
{
IsCompleted = Rule(status);
return IsCompleted ? Value : 0;
}
}
class CompletionManager
{
public List<Evaluator> Evaluators { get;set; }
public CompletionManager() {
Evaluators = new List<Evaluator>() {
new Evaluator("Email", 10, status => !String.IsNullOrWhiteSpace(status.Email)),
new Evaluator("DOB", 10, status => status.DateOfBirth.HasValue),
new Evaluator("Mobile", 20, status => !String.IsNullOrWhiteSpace(status.Mobile)),
new Evaluator("Address", 20, status => !String.IsNullOrWhiteSpace( status.Address)),
new Evaluator("Profession", 40, status => status.HasProfession),
};
}
Usage
Main() {
var status = new CompletionStatus
{
Email = "dummy#gmail.com",
DateOfBirth = null,
Mobile = "",
Address = "",
HasProfession = false,
};
CompletionManager cm = new CompletionManager();
var profileCompletion = (byte) cm.Evaluators
.Select(evaluator => evaluator.Run(status))
.Sum();
profileCompletion.Dump(); // result= 10% complete
var nextEvaluators = cm.EvaluatorsToTheNextLevel(profileCompletion); // get the next 40%
}
Problem: In this example - how do I get the list of Evaluator that correspond to the next 40% that the user have to complete so that the profile completion is >= 50%;
In the example above, I want to get the Evaluator("DOB"), Evaluator("Mobile") and Evaluator("Address")
class CompletionManager {
....
public List<Evaluator> EvaluatorsToTheNextLevel(int completionStatus) {
// assuming completionStatus = 10%, then we have to get the next 40% worth of evaluators
var percentBeforeNxtLevel = 50 - completionStatus;
return Evaluators.Where(e => e.IsCompleted == false && ???);
}
}
Note: order of Evaluators is also considered so if we are getting the next 40%, we dont want the Evaluator("Profession") as it is in the bottom of the stack
And Also: THIS should be flexible; if I do
var status = new CompletionStatus
{
Email = "",
DateOfBirth = null,
Mobile = "091920",
Address = "",
HasProfession = false,
};
then we should get Evaluator("Email"), Evaluator("DOB"), Evaluator("Address")
You could do something like that :
public List<Evaluator> EvaluatorsToTheNextLevel(int completionStatus) {
// assuming completionStatus = 10%, then we have to get the next 40% worth of evaluators
var percentBeforeNxtLevel = 50 - completionStatus;
var tmp = 0;
return Evaluators
.Where(e => e.IsCompleted == false)
.TakeWhile(x => {
tmp +=x.Value;
return tmp <= percentBeforeNxtLevel;
})
.ToList();
}
Of course, this could be also easily achieved with a simple while loop...
The line below will not work
.Select(evaluator => evaluator.Run(status))
​
You are enumerating through the evaluators one at a time in the order that they go into the List<>. Your function needs all the evaluators to you need to pass the entire list 'Evaluators'.

Categories

Resources