简体   繁体   中英

How can I fetch child entities as DTO in parent using reusable queries/Expression's with EF code first?

I am having issues finding a good way to convert Entities with their children to DTO objects. For this post I've created pseudo code which is a simplified example that leaves out the database context, DTO objects. Assuming I have a parent Entity and child Entity:

public class Parent {
    int Id;
    string Name;
    List<Child> Children;
}

public class Child {
    int Id;
    string Name;
    Parent Parent;
    int ParentId;
}

I've looked at two possibilities, but I haven't been able to find a good solution. Please have a look at the two examples and where I got stuck.

1. Example using select queries

To retreive all the parent entities as DTO's, I could then in a Controller do:

public IHttpActionResult GetParents()
{
    var children = from c in _context.Children
    select new ChildDTO()
    {
        Id = c.Id,
        Name= c.Name
    };

    var parents = from p in _context.Parents
    select new ParentDTO()
    {
        Id = p.Id,
        Name = p.Name       
        Children = children.ToList(),
    };

    return parents;
}

This will return the parent DTO object with all its children as DTO objects. If I wanted to create a new function to get just Parent with id '1', I would at the moment have to duplicate the select statement to add a where clause:

public IHttpActionResult GetParent(int parentId)
{
    var parents = from p in _context.Parents where p.id == parentId
...

And there might also be cases where I do not want the child objects back if I just want to display a list of parents. Which would mean that I would basically have to duplicate the code and change the select to this:

select new ParentDTO()
{
    Id = p.Id,
    Name = p.Name       
    //Removed the Children
    //Children = children.ToList(), 
};

In this example I do not see a good way to reuse code as much as possible, so that I don't end up writing the same basic select statements over and over.

2. Example using Expressions

I could also create Expressions for the parent and child, but I would not know

private static readonly Expression<Func<Child, ChildDTO>> AsChildDTO =
p => new ChildDTO()
{
    Id = p.Id,
    Name = p.Name
};

private static readonly Expression<Func<Parent, ParentDTO>> AsParentDTO =
p => new ParentDTO()
{
    Id = p.Id,
    Name = p.Name
};

To get the parents I could then in my controller do:

...
//Get list of parents
var parents = _context.Parents.Select(AsParentDTO);

//Or: Get only parent with Id
var specificParent= _context.Parents
.Select(AsParentDTO)
.Where(p => p.Id == 1);

return parents;
...

This solution seems good to me since I can reuse the Epressions and extend them if I want. I only do not seem to be able to Include the children to the parent this way:

...
var parents = _context.Parents
.Include(p => p.Children)
//I have no idea if it is possible to Invoke the child Expression here...
.Select(p => p.Children= AsChildDTO.Invoke()) //<-- this does not work
.Select(AsParentDTO)
...

As I wrote in the comment above; I have no idea if it is possible to somehow invoke the Child Expression here.

Outro

These are the two things I tried but got stuck with. But it could also be that I am missing a very obvious solution. My Question is how do I solve this issue in a way that I can reuse as much code as possible?

I think you are vastly over complicating it.

var results=_context.Parents
  .Include(p=>p.Children);

will return your EF objects. That's what you should be working with. If you want to convert the EF objects to DTO objects, save that for the final projection (I rarely use DTO objects as the POCO objects from EF are usually just fine).

var parents=results.Select(p=>new ParentDTO
  { id=p.id,name=p.name,children=p.Children.ToList()}
);

If you just want parent 1, then:

var parent=results.Where(p=>p.id==1);

if you want it as parentDTO:

var parent=results.Where(p=>p.id==1).Select(p=>new ParentDTO {
  { id=p.id,name=p.name,children=p.Children.ToList()}
);

You could use things like AsParentDto, but doesn't that imply that you are going to be copying the entire Parent properties? (In your simple case -- id and name). And if you are copying the entire property list, why are you creating a new object with all the same properties as the EF object instead of just reusing the EF object? The only time I'd use a Dto object is if I wanted to pass around a parent that only has some of the properties and I wanted to save myself from retrieving the additional properties from the database, in which case, I'd still use the original database query and just project into it as the final step.

var slimparent=results.Where(p=>p.id==1).Select(p=>new SlimParentDto {
  id=p.id });

Of course, if all I wanted was the parent id's then I'd just use an even simplier IQueryable<int> like:

var parentids=results.Where(p=>p.id==1).Select(p=>p.id);

--- TL;DR ---

Create a single method to retrieve your object will all the properties included. Everything then should use that as it's base, and attach further refinements in your controller to filter it down to just the data subset you want. Then as a last step, project the result into whatever DTO you want. Never use any methods to cause the IQueryable to be enumerated until you've done the projection. EF/LINQ will then generate an optimal query for you, just retrieving the properties required to fill your DTO.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM