C# Entity Framework – Should Data Access Layer Return DTOs or EF Models?

centity-framework

So say I have a controller Method

    // GET: TimeEntries
    [HttpGet("[action]")]
    public IEnumerable<TimeEntryDTO> GetLast(int value)
    {
        return dal.GetLast(value);
    }

And a data access layer method of (using AutoMapper to convert TimeEntry to TimeEntryDTO)

    public IEnumerable<TimeEntryDTO> GetLast(int value)
    {
        return db.TimeEntries.TakeLast(value).ProjectTo<TimeEntryDTO>();
    }

My Question is should I do this, and have the DAL (Data Access Layer) return the DTO (Data Transfer Object) or should I have the DAL return an IQuerable of the EF Model, and then convert it to a DTO in the controller method?

EDIT:

After doing some more reading, isn't the DBsets exposed by an EF context, in fact, the repository surface? So you wouldn't really ever see the actual DAL right, because it is a layer inside EF? So I should not call a layer that is on top of the EF Context, a DAL?

Best Answer

...should I have the DAL return an IQuerable of the EF Model...

Please don't do this. As Mark Seemann explains in IQueryable is Tight Coupling, this interface suffers from a number of problems.

  1. It's what's often referred to as a "header interface". This term refers to the bad practice in languages like C whereby a .h file simply details all the public functions found in the corresponding .c file. No consideration is given to the needs of the consumer, they simply get everything in one big lump. This means that IQuerable is a huge interface that is incredibly difficult to mock when writing unit tests around methods that return it. Have a look at the 16 part series on how to write an implementation of IQuerable to get an idea of how hard this can be.

  2. It's a leaky abstraction. It's effectively an interface written to reflect the design of the Entity Framework ORM. As such, it exposes much of the functional design of that ORM in the interface, ie those implementation details "leak out" through the interface. So by exposing IQuerable in your DAL API, you couple your DAL to it and you couple consumers of that API to it.

The whole purpose of abstractions is to decouple parts of your code. Using an interface that actually couples things together defeats the whole purpose of an abstraction. So don't do this.

Instead, stick to your other suggestion: convert those implementation details of IQuerable into DTOs that are focused on supplying just what the caller needs. This requires a bit more work as you have to write the DTOs and the conversion code, but it pays dividends in keeping things decoupled and easier to understand and test.