Let see an example:
Given the classes as follow:
class Person
{
public string FullName { get; set; }
public string ClinicAddress { get; set; }
public string City { get; set; }
public string Gendre { get; set; }
}
class Clinic
{
public string Address { get; set; }
public string City { get; set; }
public List<Treatment> AcceptedTreatments { get; set; }
}
Then I have a List<Person> and a List<Clinic> and here is my problem:
For each Person I want to find a clinic on same city accepting its medical records and then fullfill the property person.ClinicAddress.
Basically, we need to find matching elements and then join them.
Of course there is a Linq operator for that: IEnumerable.Join(), it works based on keys on both lists to do the match and executing a Func<> receiving both matching elements to do the join.
But it was not exactly what I was looking for. In most of my cases I don't have evident keys to match on both sides but a complex decision criteria. Also, in some cases, if no match found, I want to assign a default value.
var persons = new List<Person>();
var clinics = new List<Clinic>();
foreach (var person in persons)
{
var mh = GetMedicalHistory(person);
var match = clinics.FirstOrDefault(a => CanAssignClinic(mh, a);
if (match != null)
{
person.ClinicAddress = $"{match.Address}, {match.City}";
}
else
{
person.ClinicAddress = "Hospital on the Capital";
}
}
Lets just say that methods GetMedicalHistory() and CanAssignClinic() are not so simple to represent with a match of properties, which makes difficult to pass matching values to keySelector parameters of IEnumerable.Join().
I found the same pattern in a lot of places on my code, the pattern is as follow:
For each element of target list, apply some criteria over all elements of second list, if found a match, then do whatever I want with matching elements, if no match, then do something else.
Event if IEnumerable.Join() was possible to apply in some cases I decided to go with a custom implementation more readable and simple to represent, also to have the possibility to deal with no matching found situation, so I ended up with this:
public static void JoinByCriteria<T1, T2>(this IEnumerable<T1> target,
IEnumerable<T2> source,
Func<T1, T2, bool> condition,
Action<T1, T2> whenMatchFoundOperation,
Action<T1> whenMatchNotFoundOperation = null)
{
foreach (var t in target)
{
var match = source.FirstOrDefault(s => condition(t, s));
if (match != null)
{
whenMatchFoundOperation(t, match);
}
else if (whenMatchNotFoundOperation != null)
{
whenMatchNotFoundOperation(t);
}
}
}
It is an extension method for IEnumerable, and here is a way to use it:
persons.JoinByCriteria(clinics, (person, clinic) =>
{
var mh = GetMedicalHistory(person);
return CanAssignClinic(mh, clinic);
},
(person, agency) =>
{
person.ClinicAddress = agency.Address;
},
(person) =>
{
person.ClinicAddress = "City Hospital";
});
Aucun commentaire:
Enregistrer un commentaire