0

Let there be four kinds of classes present for me:

public class Employee
{
    public int EmployeeID {get; set;}
    public string name {get; set;}
}

public class EmployeesList
{
    public List<Employee> Employees {get; set;}
}

public class Member
{
    public int MemberId {get; set;}
    public int MemberPassNumber {get; set;}
}

public class MembersList
{
    public List<Member> Members {get; set;}
}

I want to create a logic (single logic) for any of the object type to find out in the EmployeeList or MembersList that if I have given an ID value "I", and property name "P" and property value "V". Then my logic checks in the Employee/member lists and figures out that one of the object has a some property with ID value "I" and then if found , so in the same object it should update the given property name "P" to the given property value "V".

Example 1:

Employees: [{
         id: 1,
         name: "ABC"
     }, {
         id: 2,
         name: "XYZ"
     } ]

Inputs: id: 2, property: "name", value: "MNO"

Output:

Employees: [{
         id: 1,
         name: "ABC"
     }, {
         id: 2,
         name: "MNO"
     } ]

Example 2:

Members: [{
         MemberId: 1,
         MemberPassNumber: 1111
     }, {
         MemberId: 2,
         MemberPassNumber: 2222
     } ]

Inputs: id: 2, property: "MemberPassNumber", value: 3333

Output:

Members: [{
         MemberId: 1,
         MemberPassNumber: 1111
     }, {
         MemberId: 2,
         MemberPassNumber: 3333
     } ]

Note: the ids are not normal integer values, they are guids and will be unique from other property values

Shailesh Prajapati
  • 458
  • 2
  • 7
  • 20
  • The question is unclear. In memory, there are no IDs - they simply aren't needed. You can use any search operator, eg `FirstOrDefault`, `SingleOrDefault`, `Where` etc to find the object you want. After that, just modify it. If you mean LINQ over *EF Core*, it's EF Core that deals with keys, not LIN. In that case `DbSet<>.Find()` *does* work with Primary Key values. Although in EF Core 7 you can use `ExecuteUpdate` to essentially generate an UPDATE clause that doesn't have to load objects in memory before modifying them – Panagiotis Kanavos Jun 06 '23 at 19:29
  • What actual problem are you trying to solve? It matters. You may be trying to solve the wrong thing, or there may be a solution already, just not the one you thought. – Panagiotis Kanavos Jun 06 '23 at 19:33

2 Answers2

0

You can use generics to create a function that takes anonymous compare and assignment functions. This way you can reuse the logic for your Employee and Member class. The meat of the answer is in the FindAndUpdate() function:

public bool FindAndUpdate<Titem, Tid, Tvalue>(
    List<Titem> list, 
    Tid id,
    Func<Titem, Tid, bool> compare, 
    Tvalue value,
    Action<Titem, Tvalue> assign)
{
    var item = list.FirstOrDefault(e => compare(e, id));
    if (item != null)
    {
        assign(item, value);
        return true;
    }
    return false;
}

The following code will find the right element, then make the assignment. Remember these lists are passed by reference so we can make these changes in the function and it will be affected outside the function.

Here's the output of the example code:

59d135c3-31e6-474a-bce2-256bfd251a2e::ABC
d652af76-f4ab-4af1-9226-0c82130f2776::DEF <<-- Original Value
59d135c3-31e6-474a-bce2-256bfd251a2e::ABC
d652af76-f4ab-4af1-9226-0c82130f2776::XYZ <<-- Updated Value

0e74a191-3c47-4bbc-b60b-d88ca6261637::1111
65d96e9f-8ba9-4da2-af2f-8a652340bf44::2222 <<-- Original Value
0e74a191-3c47-4bbc-b60b-d88ca6261637::1111
65d96e9f-8ba9-4da2-af2f-8a652340bf44::3333 <<-- Updated Value
void Main()
{
    var toChangeEmpId = Guid.NewGuid();
    
    var employees = 
        new EmployeesList 
        {
            Employees = new List<Employee> 
            { 
                new Employee {EmployeeID = Guid.NewGuid(), Name = "ABC" },
                new Employee {EmployeeID = toChangeEmpId, Name = "DEF" }
            }
        };

    var toChangeMemId = Guid.NewGuid();
    
    var members = 
        new MembersList
        {
            Members = new List<Member>
            {
                new Member { MemberId = Guid.NewGuid(), MemberPassNumber = 1111},
                new Member { MemberId = toChangeMemId, MemberPassNumber = 2222}
            }
        };

    Print(employees.Employees, (item) => $"{item.EmployeeID}::{item.Name}");

    FindAndUpdate<Employee, Guid, string>(
        employees.Employees, 
        toChangeEmpId, (item, id) => item.EmployeeID == id, 
        "XYZ", (item, value) => item.Name = value);
    
    Print(employees.Employees, (item) => $"{item.EmployeeID}::{item.Name}");

    Print(members.Members, (item) => $"{item.MemberId}::{item.MemberPassNumber}");
    
    FindAndUpdate<Member, Guid, int>(
        members.Members,
        toChangeMemId, (item, id) => item.MemberId == id,
        3333, (item, value) => item.MemberPassNumber = value);
        
    Print(members.Members, (item) => $"{item.MemberId}::{item.MemberPassNumber}");
}


public bool FindAndUpdate<Titem, Tid, Tvalue>(
    List<Titem> list, 
    Tid id,
    Func<Titem, Tid, bool> compare, 
    Tvalue value,
    Action<Titem, Tvalue> assign)
{
    var item = list.FirstOrDefault(e => compare(e, id));
    if (item != null)
    {
        assign(item, value);
        return true;
    }
    return false;
}

public void Print<Titem>(List<Titem> list, Func<Titem, string> toString)
{
    foreach (var item in list)
    {
        Console.WriteLine(toString(item));
    }
    
}

public class Employee
{
    public Guid EmployeeID { get; set; }
    public string Name { get; set; }
}

public class EmployeesList
{
    public List<Employee> Employees { get; set; }
}

public class Member
{
    public Guid MemberId { get; set; }
    public int MemberPassNumber { get; set; }
}

public class MembersList
{
    public List<Member> Members { get; set; }
}
Carlo Bos
  • 3,105
  • 2
  • 16
  • 29
  • That's far too much code to say `use the built-in FirstOrDefault, then modify the object`. All LINQ operators are already generic, already works with lambdas - in fact lambdas were introduced to enable LINQ. – Panagiotis Kanavos Jun 06 '23 at 19:24
  • @PanagiotisKanavos The OQ specifically asked for `create a logic (single logic) for any of the object type` So either you use reflection or generics. I choose to give an example of using generics. The solution proposed does use `FirstOrDefault` and a simple assignment, however it also meets the requirement to be usable for any object type with different properties for ID and Value. – Carlo Bos Jun 07 '23 at 13:06
0

Based on docs, and answer on how to get property value with reflection

public static PropType GetValueById<T, IdType, PropType>(this List<T> listOfValues
        , IdType id
        , string idPropName
        , string resultPropName)
{
    foreach (var item in listOfValues)
    {
        var itemId = (IdType)typeof(T).GetProperty(idPropName).GetValue(item);
        if (itemId != null && itemId.Equals(id))
        {
            var type = typeof(T);
            var prop = type.GetProperty(resultPropName);
            var value = prop.GetValue(item, null);
            return (PropType)value;
        }
    }

    return default;
}

The only difference here is that you are going through the list. In this case every time you call method you must know the name of properties and id. For the resulting property and value of id it's logical. But in my opinion, it would be better to annotate property that represents Id with a custom attribute while creating the model, instead of copy/pasting name of ID every time when this method is called:

public class Employee
{
    [Id]
    public int EmployeeID {get; set;}
    public string name {get; set;}
}

public class Id: Attribute
{

}

After that it's possible to remove idPropName and find ID property. So instead of GetProperty(idPropName):

var idProp = typeof(T).GetProperties()
                .Where(x => Attribute.IsDefined(x, typeof(Id)))
                .FirstOrDefault();

Here you can try example. Maybe it's possible to remove IdType in this case, but I'm not sure right now.

Also, I recommend adding interface that represents type of models with single property IDs, and adding generic constraint for this. And I didn't cover cases for models without ID, with multiple IDs, when resulting property doesn't exist, I assumed that IDs are strings or ints, ... but this was my idea, hope this helps.