dotnet / dotnet/efcore

Offer workflow for reusable projections that access nested navigation properties

Open
#31,982 0 comments 1 reaction 0 assignees View on GitHub
area-query customer-reported needs-design
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

I'd love to have the ability to store static methods or constructors that project an entity to another concrete type which can be reused in queries and subqueries. The main issue I find now is that the query generated doesn't always include all the necessary fields when nested navigation properties are involved. It seems like we're stuck using third-party libraries like Automapper.

Simple example: assume you have the following schema.

```
public class MyContext : DbContext
{
public DbSet Employers { get; set; }
public DbSet Persons { get; set; }
}

public class Employer
{
public Guid Id { get; set; }
public string CompanyName { get; set; }
}

public class Person
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }

public Guid EmployerId { get; set; }
public Employer Employer { get; set; }

public Guid FatherId { get; set; }
public Person Father { get; set; }

public Guid MotherId { get; set; }
public Person Mother { get; set; }
}
```

Now assume you want to project a Person to a model like the following.

```
public class PersonDto
{
public string FirstName { get; set; }
public string LastName { get; set; }

public ParentDto Mother { get; set; }
public ParentDto Father { get; set; }
}

public class ParentDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmployerName { get; set; }
}
```

Currently, to make a PersonDto I would have to write something like the following, repeating the same logic for the mother and father dtos.

```
ctx.Persons.Select(e => new PersonDto
{
FirstName = e.FirstName,
LastName = e.LastName,
Father = new ParentDto
{
EmployerName = e.Father.Employer.CompanyName,
FirstName = e.Father.FirstName,
LastName = e.Father.LastName
},
Mother = new ParentDto
{
EmployerName = e.Mother.Employer.CompanyName,
FirstName = e.Mother.FirstName,
LastName = e.Mother.LastName
}
})
```

The following approach looks nice but doesn't work. Because the select statement above cannot see into the dto constructor it does not include the employer name from the database, leaving the navigation property null. It also includes all fields from the Person class being passed into the constructor (i.e. Id & Age in this example), regardless if the final object requires that value.

```
public class ParentDto
{
public ParentDto(Person person)
{
EmployerName = person.Employer.CompanyName; // person.Employer will be null!
FirstName = person.FirstName;
LastName = person.LastName;
}

// ...
}

// projection altered to use dto constructor
ctx.Persons.Select(e => new PersonDto
{
FirstName = e.FirstName,
LastName = e.LastName,
Father = new ParentDto(e.Father),
Mother = new ParentDto(e.Mother)
})
```

I'd love to see EF support using constructors in this way for reusable queries and subqueries. Another great solution would be to leverage something like compiled queries and inject them into other queries. Maybe something like the following.

```
static readonly Func _parentMapper
= EF.CompileQuery (Person p) => new ParentDto
{
// ..
};

// ..

ctx.Persons.Select(e => new PersonDto
{
FirstName = e.FirstName,
LastName = e.LastName,
Father =_parentMapper(e.Father),
Mother = _parentMapper(e.Mother)
})
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.