J.J
J.J

Reputation: 1001

Use linq to map parent child structure

I have the domain classes separated from the ones I use in the views, so when retrieving data I have to map the domain classes to the view ones. Until now this have been straight forward, but now I have a case in which I need to map parent-child classes from the domain to parent-child classes on the view.

Using foreach structures works fine, but I have quite a few linq methods that do the mapping between domain and view classes, that need to be refactored to accomodated to the new requirements and would be faster if I knew how to do it with linq. Thanks in advance.

As an example of what I'm trying to accomplish see code below:

In the repository I have the classes:

    public class Parent
{
    public int ParentId { get; set; }
    public string ParentName { get; set; }
};

public class ChildA : Parent
{
    public string ChildPropertyA { get; set; }
};

public class ChildB : Parent
{
    public string ChildPropertyB { get; set; }
};

Then in the UI I have the classes:

    public class ParentVM
{
    public int ParentIdVM { get; set; }
    public string ParentNameVM { get; set; }
};

public class ChildAVM : ParentVM
{
    public string ChildPropertyAVM { get; set; }
};

public class ChildBVM : ParentVM
{
    public string ChildPropertyBVM { get; set; }
};

Now I will have a service class in which the methods will look like the one below:

        public GetParentVMs()
    {
        var parents = initializeRepositoryClass();

        var parentsVM = MapRepositoryToViewClasses(parents);

        ShowResult(parentsVM);
    }

Where:

        public List<Parent> initializeRepositoryClass()
    {
        var parents = new List<Parent>(){
            new ChildA(){ParentId=1, ParentName="Parent 1", ChildPropertyA="A"},
            new Parent(){ParentId=2, ParentName="Parent 2"},
            new ChildB(){ParentId=3, ParentName="Parent 3", ChildPropertyB="B"},
        };
        return parents;
    }
    private List<ParentVM> MapRepositoryToViewClasses(List<Parent> parents)
    {
        var parentsVM = new List<ParentVM>();
        foreach (var item in parents)
        {
            if (item is ChildA)
            {
                var itemVM = item as ChildA;
                parentsVM.Add(
                    new ChildAVM() { ParentIdVM = itemVM.ParentId, ParentNameVM = itemVM.ParentName, ChildPropertyAVM = itemVM.ChildPropertyA }
                );
            }
            else if (item is ChildB)
            {
                var itemVM = item as ChildB;
                parentsVM.Add(
                new ChildBVM() { ParentIdVM = itemVM.ParentId, ParentNameVM = itemVM.ParentName, ChildPropertyBVM = itemVM.ChildPropertyB }
                );
            }
            else
            {
                var itemVM = item as Parent;
                parentsVM.Add(
                new ParentVM() { ParentIdVM = itemVM.ParentId, ParentNameVM = itemVM.ParentName }
                );
            }
        }
        return parentsVM;
    }
    private void ShowResult(List<ParentVM> parentsVM)
    {
        foreach (var item in parentsVM)
        {
            if (item is ChildAVM)
            {
                var ca = (ChildAVM)item;
                Console.WriteLine("Child A " + ca.ChildPropertyAVM);
            }
            else if (item is ChildBVM)
            {
                var cb = (ChildBVM)item;
                Console.WriteLine("Child B " + cb.ChildPropertyBVM);
            }
            else
            {
                Console.WriteLine("Parent ");
            }
        }
    }

The code above will work, but I like to change the method MapRepositoryToViewClasses to another that uses linq, and looks a like the one below:

        private List<ParentVM> MapRepositoryToViewClassesLinq(List<Parent> parents)
    {
        var parentsVM =
            from p in parents 
                case
                    p is ChildA then select new ChildAVM() {ChildPropertyAVM = p.ChildPropertyA, ...};
                else
                    p is ChildB then select new ChildBVM() {ChildPropertyBVM = p.ChildPropertyB, ...};
                else
                    select new ParentVM() {ParentIdVM = p.ParentId};


    return parentsVM.ToList();
    }

Any ideas? Thanks.

Upvotes: 0

Views: 372

Answers (2)

Viacheslav Smityukh
Viacheslav Smityukh

Reputation: 5833

You need some changes in your code to make it better

1) You have to introduce a factory to create VM's instances.

class VMFactory
{
    public ParentVM Create(Parent obj)
    {
        var childA = obj as ChildA;
        if (childA != null)
        {
            return new ChildAVM() { ParentIdVM = childA.ParentId, ParentNameVM = childA.ParentName, ChildPropertyAVM = childA .ChildPropertyA };
        }

        var childB = obj as ChildB;
        if(childB != null)
        {
                return new ChildBVM() { ParentIdVM = childB.ParentId, ParentNameVM = childB.ParentName, ChildPropertyBVM = childB.ChildPropertyB };
        }

        return new ParentVM() { ParentIdVM = obj.ParentId, ParentNameVM = obj.ParentName };
    }
}

2) Now you can simplify your code at MapRepositoryToViewClasses method

private List<ParentVM> MapRepositoryToViewClasses(List<Parent> parents)
{
    // Factory instance can be provided by the outer scope
    var factory = new VMFactory();
    var parentsVM = new List<ParentVM>();
    foreach (var item in parents)
    {
        parentsVM.Add(factory.Create(item));
    }

    return parentsVM;
}

3) Final step, let's use Linq to map

private List<ParentVM> MapRepositoryToViewClasses(List<Parent> parents)
{
    // Factory instance can be provided by the outer scope
    var factory = new VMFactory();

    return parents.Select(factory.Create).ToList();
}

It's done


Yet another attempt to solve it

1) Create the extensions to solve common tasks.

static class Ext
{
    public static ParentVM Map<TIn>(this TIn obj, Func<TIn, ParentVM> func)
        where TIn : Parent
    {
        var source = obj as TIn;
        return source != null
            ? func(obj)
            : null;
    }
}

2) Use the extension method to get VMs

private List<ParentVM> MapRepositoryToViewClassesLinq(List<Parent> parents)
{
    var tmp = from p in parents
              select
                  p.Map<ChildA>(c => new ChildAVM() { ParentIdVM = c.ParentId, ParentNameVM = c.ParentName, ChildPropertyAVM = c.ChildPropertyA }) ??
                  p.Map<ChildB>(c => new ChildBVM() { ParentIdVM = c.ParentId, ParentNameVM = c.ParentName, ChildPropertyBVM = c.ChildPropertyB }) ??
                  new ParentVM() { ParentIdVM = obj.ParentId, ParentNameVM = obj.ParentName };

    return tmp.ToList();
}

Upvotes: 1

Jason W
Jason W

Reputation: 13179

If you setup mapping extensions for each type, then the mapping process becomes trivial.

SQL Fiddle: https://dotnetfiddle.net/a4eQ6S

How to map (GetParentsVM())

var parents = initializeRepositoryClass();
var parentsVM = parents.Map();

Mapping Extensions

public static class ParentMappings
{
    public static ChildAVM Map(this ChildA model)
    {
        return new ChildAVM()
            {
                ParentIdVM = model.ParentId,
                ParentNameVM = model.ParentName,
                ChildPropertyAVM = model.ChildPropertyA,
            };
    }
    public static ChildBVM Map(this ChildB model)
    {
        return new ChildBVM()
            {
                ParentIdVM = model.ParentId,
                ParentNameVM = model.ParentName,
                ChildPropertyBVM = model.ChildPropertyB,
            };
    }
    public static ParentVM Map(this Parent model)
    {
        if (model is ChildA)
            return ((ChildA)model).Map();
        else if (model is ChildB)
            return ((ChildB)model).Map();
        else
            return new ParentVM()
                {
                    ParentIdVM = model.ParentId,
                    ParentNameVM = model.ParentName,
                };
    }
    public static List<ParentVM> Map(this List<Parent> parents)
    {
        return parents.Select(p => p.Map()).ToList();
    }
}

Upvotes: 1

Related Questions