-1

How can I make a single generic method from the 2 methods ConvertToDictionary that can handle both collections?

EDIT: I rewrote the code, so it can be run as it is.

DgDictHosp differs from DgDictOper in type of property Data IOrderedEnumerable<Hospitalization> vs IOrderedEnumerable<Operation> where Hospitalization and Operation are classes generated by Entity Framework.

using System;
using System.Collections.Generic;
using System.Linq;
    public class DictItem
    {
        public long Id { get; set; }
        public List<string> List { get; set; }
    }
    public class Hospitalization
    {
        public int ID { get; set; }
        public long IDHospitalization { get; set; }
        public int IDDiagnosis { get; set; }
    }
    public class Operation
    {
        public int ID { get; set; }
        public long IDOperation { get; set; }
        public int IDDiagnosis { get; set; }
    }
    public class DgDictOper
    {
        public long Key { get; set; }
        public IOrderedEnumerable<Operation> Data { get; set; }
        public List<string> List { get; set; }
    }
    public class DgDictHosp
    {
        public long Key { get; set; }
        public IOrderedEnumerable<Hospitalization> Data { get; set; }
        public List<string> List { get; set; }
    }
    public class Program
    {
        public static string ToLiteral(int dgid)
        {
            return "dictionary phrase for " + dgid;
        }
        public static Dictionary<long, DictItem> ConvertToDictionary(List<DgDictHosp> list_g)
        {
            var res_dict = list_g.Select(q => new DictItem
            {
                Id = q.Key,
                List = q.Data.Select(t => ToLiteral(t.IDDiagnosis)).ToList()
            }).ToDictionary(k => k.Id);
            return res_dict;
        }
        public static Dictionary<long, DictItem> ConvertToDictionary(List<DgDictOper> list_g)
        {
            var res_dict = list_g.Select(q => new DictItem
            {
                Id = q.Key,
                List = q.Data.Select(t => ToLiteral(t.IDDiagnosis)).ToList()
            }).ToDictionary(k => k.Id);
            return res_dict;
        }
        public static List<Hospitalization> InitHospList()
        {
            List<Hospitalization> list = new List<Hospitalization>();
            list.Add(new Hospitalization() { ID = 1, IDHospitalization = 11, IDDiagnosis = 10 });
            list.Add(new Hospitalization() { ID = 2, IDHospitalization = 11, IDDiagnosis = 20 });
            return list;            
        }
        public static List<Operation> InitOperList()
        {
            List<Operation> list = new List<Operation>();
            list.Add(new Operation() { ID = 1, IDOperation = 22, IDDiagnosis = 30 });
            list.Add(new Operation() { ID = 2, IDOperation = 22, IDDiagnosis = 40 });
            return list;
        }
        public static Dictionary<long, DictItem> GetHospDict(List<Hospitalization> list)
        {
            var res_g =
                (from dg in list
                 group dg by dg.IDHospitalization
                 into dg_group
                 select new DgDictHosp
                 {
                     Key = dg_group.Key,
                     Data = dg_group.OrderBy(g => g.ID)
                 }).ToList();
            var res = ConvertToDictionary(res_g);
            return res;
        }
        public static Dictionary<long, DictItem> GetOperDict(List<Operation> list)
        {
            var res_g =
                (from dg in list
                 group dg by dg.IDOperation
                 into dg_group
                 select new DgDictOper
                 {
                     Key = dg_group.Key,
                     Data = dg_group.OrderBy(g => g.ID)
                 }).ToList();
            var res = ConvertToDictionary(res_g);
            return res;
        }
        public void Main()
        {                        
            foreach (var x in GetHospDict(InitHospList())) 
            {
                Console.WriteLine("Hosp ID: " + x.Key);
                foreach (var y in x.Value.List)
                {
                    Console.WriteLine("Diagosis: " + y);  ;
                }
            }
            foreach (var x in GetOperDict(InitOperList()))
            {
                Console.WriteLine("Operation ID: " + x.Key);
                foreach (var y in x.Value.List)
                {
                    Console.WriteLine("Diagosis: " + y); ;
                }
            }
        }
    }

petr.f77
  • 29
  • 5
  • Please share a [mcve]. – mjwills Jan 28 '21 at 11:29
  • 2
    you can't (without reflection) , unless `DgDictOper` and `DgDictHosp` has common interface defined ... then changing `List` to `IEnumerable` should do the thing – Selvin Jan 28 '21 at 11:30
  • Could you please write shortly how should common interface looks like if type of Data differs? – petr.f77 Jan 28 '21 at 11:50
  • https://dotnetfiddle.net/tQBHpS might get you started. – mjwills Jan 28 '21 at 22:40
  • @mjwills Thanks, I rewrote it all, it is quite long now, but works independently, https://dotnetfiddle.net/aCA2WR – petr.f77 Jan 29 '21 at 10:29
  • @petr.f77 after the last edit this is a perfect reproducible copy paste example that we can easily work with. congrats. A lot of people cannot manage to provide such a standalone thing – Mong Zhu Jan 29 '21 at 16:30

1 Answers1

0

Basically your data structures differ in a "generic" parameter. So you would need a generic interface to accomodate for this, or you could use a generic class and get rid of DgDictHosp and DgDictOper altogether because the generic class could account for all properties:

public class DgDictBaseClass<T> where T : HospOpBase
{
    public long Key { get; set; }
    public IOrderedEnumerable<T> Data { get; set; }
    public List<string> List { get; set; }
}

I restricted the generic parameter to a baseclass that I introduced to simplify the code of the classes Hospitalization and Operation:

public abstract class HospOpBase
{
    public int ID { get; set; }
    public int IDDiagnosis { get; set; }
}

public class Hospitalization : HospOpBase
{
    public long IDHospitalization { get; set; }
}
public class Operation : HospOpBase
{
    public long IDOperation { get; set; }
}

With these tools in your backback you can now start to make your mapping method generic to accomodate for both types of collections. As a parameter you would now pass a List<DgDictBaseClass<T>> list_g and you will return Dictionary<long, DictItem>:

public static Dictionary<long, DictItem> ConvertToDictionary<T>(List<DgDictBaseClass<T>> list_g) where T : HospOpBase
{
    var res_dict = list_g.Select(q => new DictItem
    {
        Id = q.Key,
        List = q.Data.Select(t => ToLiteral(t.IDDiagnosis)).ToList()

    }).ToDictionary(k => k.Id);
    return res_dict;
}

Here again it is usefull to restrict the generic parameter T to be of type HospOpBase. This way the compiler will know that it has the property IDDiagnosis and you can use it in your linq statement.

Now there is only 1 last change to be made to your current code. Since I got rid of the classes DgDictOper and DgDictHosp entirely you need to exchange them with the new type in the methods: GetHospDict and GetOperDict in the select statement :

public static Dictionary<long, DictItem> GetHospDict(List<Hospitalization> list)
{
    var res_g =
        (from dg in list
         group dg by dg.IDHospitalization
         into dg_group
         select new DgDictBaseClass<Hospitalization> // <= change here
         {
             Key = dg_group.Key,
             Data = dg_group.OrderBy(g => g.ID)
         }).ToList();
    var res = ConvertToDictionary(res_g);
    return res;
}

public static Dictionary<long, DictItem> GetOperDict(List<Operation> list)
{
    var res_g =
        (from dg in list
         group dg by dg.IDOperation
         into dg_group
         select new DgDictBaseClass<Operation> // <= change here
         {
             Key = dg_group.Key,
             Data = dg_group.OrderBy(g => g.ID)
         }).ToList();
    var res = ConvertToDictionary(res_g);
    return res;
}

EDIT:

actually there is a way to combine the last 2 mapping methods into 1. The tricky part here is the grouping variable which is actually the only difference in the two methods. You can provide this method as a method parameter. The type is either Func<Operation, long> or Func<Hospitalization, long>. (Which basically means that it is a method that takes an Operation as input parameter and returns a long, in your case the property: dg.IDOperation). So since the 2 classes have now a common base class we can make the method generic, the grouping function parameter of type: Func<T, long> and restrict the generic parameter to be only of type HospOpBase. Now the function would look like this:

public static Dictionary<long, DictItem> GetOperDictNew<T>(List<T> list, Func<T, long> groupingFilter)  where T : HospOpBase
{
    var res_g =
        (from dg in list
         group dg by groupingFilter(dg) // <= use here the grouping filter
         into dg_group
         select new DgDictBaseClass<T>
         {
             Key = dg_group.Key,
             Data = dg_group.OrderBy(g => g.ID)
         }).ToList();
    var res = ConvertToDictionary(res_g);
    return res;
}

Now you can call the new method in both loops:

foreach (var x in GetOperDictNew(InitHospList(), x => x.IDHospitalization))
{
    Console.WriteLine("Hosp ID: " + x.Key);
    foreach (var y in x.Value.List)
    {
        Console.WriteLine("Diagosis: " + y); ;
    }
}
foreach (var x in GetOperDictNew(InitOperList(), x => x.IDOperation))
{
    Console.WriteLine("Operation ID: " + x.Key);
    foreach (var y in x.Value.List)
    {
        Console.WriteLine("Diagosis: " + y); ;
    }
}

and you will get the same output:

Hosp ID: 11
Diagosis: dictionary phrase for 10
Diagosis: dictionary phrase for 20
Operation ID: 22
Diagosis: dictionary phrase for 30
Diagosis: dictionary phrase for 40

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
  • Thank you a lot. Meanwhile I edited a bit the question code, ```ListLiteral``` is actually a string. Could you please also add a call of ```ConvertToDictionary``` method ? – petr.f77 Jan 28 '21 at 12:44
  • @petr.f77 please edit your post again and add the class implementation of `DictItem`. It was only a guess from your first post. Then I can adjust my answer – Mong Zhu Jan 28 '21 at 12:59
  • @petr.f77 I don't understand how you want to use the `Aggregate` function. It is generic and dependet on the `T` parameter – Mong Zhu Jan 28 '21 at 13:30
  • You are right, I omitted part of code line after ```Agregate```, now it is complete. – petr.f77 Jan 28 '21 at 14:18
  • @petr.f77 ok I cannot solve the Aggregate part without the knowledge of how `table1` and `table2` look internally. The problem here ist, that they have to have a common basis. The generic method depends on it. What is `t.IDDiagnoza1` ? such a property (if in both classes) has to be sourced out in an interface and the `T` parameter of the method has to be restricted to this interface using : `where T : thisSpecialCommonInterface` – Mong Zhu Jan 29 '21 at 08:43
  • 1
    I rewrote it all including tables, now the example code is complete and suitable to run, please take a look at https://dotnetfiddle.net/aCA2WR – petr.f77 Jan 29 '21 at 10:30
  • @petr.f77 I rewrote my entire answer to fit your new edit. Have a look at it. If it solves your problem you might consider to mark it as accepted :) – Mong Zhu Jan 29 '21 at 16:14
  • 1
    Thanks, I am affraid I can't do it this way, because the classes Operation and Hospitalization are generated by Entity Framework, so I can't change them, I'm sorry I didn't mention that – petr.f77 Jan 29 '21 at 17:05
  • @petr.f77 " because the classes Operation and Hospitalization are generated by Entity Framework, so I can't change them" actually this is still possible to do. Have a closer look at the 2 classes. They are declared as `partial` and can be extended as to inherit from the same base class. Have a look at [this post](https://stackoverflow.com/questions/42634184/why-are-entity-framework-entities-partial-classes) – Mong Zhu Feb 02 '21 at 06:56