Home > front end >  how to do an inner join and populate property efficiently in Linq?
how to do an inner join and populate property efficiently in Linq?

Time:06-07

Let's say we have the following code:

Category[] categoriesOne = new Category[] {
// each instance contains a lot of properties but without Desc property being set
// the content of Desc property is only available in categoriesTwo
   new Category { IdCategory = 1, Name = "Pasta", ...},  
   new Category { IdCategory = 2, Name = "Beverages", ...},
   new Category { IdCategory = 3, Name = "Other food", ...},
};

Category[] categoriesTwo = new Category[] {  // only two properties in each instance
   new Category { IdCategory = 1, Desc = "made in Italy"},
   new Category { IdCategory = 2, Desc = "made in Germany"},
};

public class Category 
{
   public int IdCategory { get; set; }
   public String Name { get; set; }
   public String Desc{ get; set; }
   // ... many more properties such as barcode, supplier, country, countrycode ... etc
}

and I want to do an inner join and populate the Desc property, so the return instances as IEnumerable should be:

{
   new Category { IdCategory = 1, Name = "Pasta", Desc = "made in Italy"},
   new Category { IdCategory = 2, Name = "Beverages", Desc = "made in Germany"}
}

it looks a very simple task which we can normally do:

var query = categoriesOne.Join(categoriesTwo, c1 => c1.IdCategory, c2 => c2.IdCategory, (c1, c2) => new { c1, c2 });

foreach (var item in query)
{
   item.c1.Desc = item.c2.Desc;
}

but it doesn't look like a good/efficient approach, do we have a more elegant approach so we can do it in one go as:

var query = from c1 in categoriesOne
            join c2 in categoriesTwo on c1.IdCategory equals c2.IdCategory into groups
            ???

Note that I can't do:

var result = (from c1 in categoriesOne
    join c2 in categoriesTwo on c1.IdCategory equals c2.IdCategory
    select new Category
    {
        IdCategory = c1.IdCategory,
        Name = c1.Name,
        Desc = c2.Desc  
        // ...   too many properties from categoriesOne, this approach is tedious 
    }
).ToList();

because Category class containes a lot of properties, so basically I want to keep all the properties in categoriesOne (has all the properties available except Desc property whose content is only available in categoriesTwo

CodePudding user response:

The question that comes to my mind about merging the two objects would be, who is source and who is target or are both entities sources and a new target should be created? Within the select clause you have to make this decision. To avoid tedious lines of codes you could use some reflection stuff and read all properties from one or multiple sources, taking the first non-default value and write it into the desired target.

public static class Helper
{
    public static T Merge<T>(T target, params T[] sources) where T : class, new()
    {
        if (target == null)
            target = new T();

        var props = typeof(T).GetProperties()
            .Where(prop => prop.CanRead && prop.CanWrite);

        foreach (var prop in props)
        {
            var value = sources.Select(source => prop.GetValue(source))
                .FirstOrDefault(value => value != default);

            prop.SetValue(target, value);
        }

        return target;
    }
}

With this helper you should be able to write something like:

var result = (from c1 in categoriesOne
    join c2 in categoriesTwo on c1.IdCategory equals c2.IdCategory
    select Helper.Merge(null, c1, c2)
).ToList();

or

var result = (from c1 in categoriesOne
    join c2 in categoriesTwo on c1.IdCategory equals c2.IdCategory
    select Helper.Merge(c1, c2)
).ToList();

depending, if you like to create a fresh instance out of the two or manipulating the first instance.

  • Related