jasontaylordev / CleanArchitecture

Clean Architecture Solution Template for ASP.NET Core
MIT License
16.87k stars 3.63k forks source link

[Question] Should we install Microsoft.EntityFrameworkCore in application layer? #238

Closed trungcaot closed 4 years ago

trungcaot commented 4 years ago

I thought that if we install ef core in the application layer then it will make this layer depend on ef core once we probably change ORM to Dapper or something like that, we must update the code of the application layer.

Should we do handle it on the infrastructure layer as the best practice on onion architecture?

Pls look at my idea and give me your idea on this question. Thanks

neman commented 4 years ago

I also do not like application layer to reference EF Core. Maybe IDbContext interface should expose IQuaryable instead of DbSet?

art-developer commented 4 years ago

Agree, we should avoid DbSet in IApplicationDbContext

trungcaot commented 4 years ago

I have also updated the code for this case by implementing a generic repository and unit of work to remove ef core in the application layer. You can refer to an example project MsCoreOne with link to look at it. Hope everyone likes it and gives me some feedback. Thanks.

carloswbarros commented 4 years ago

I have also updated the code for this case by implementing a generic repository and unit of work to remove ef core in the application layer. You can refer to an example project MsCoreOne with link to look at it. Hope everyone likes it and gives me some feedback. Thanks.

That's what I had in mind to do. But how would you do custom queries? In the repository and put the DTO's in the Application?

trungcaot commented 4 years ago

Hi @carloswbarros,

  1. But how would you do custom queries? If you use dapper to connect to the database, you probably define a generic function as below, and it will execute your custom query. public async Task<IEnumerable> QueryAsync(RawQuery rawQuery) where TEntity : class { return await MonitorTracingFunctionAsync(async query => await Connection.QueryAsync(query.Sql, query.Param), rawQuery); }
  2. With the second question, I haven't got your mean yet. If you have time pls explain more detail.

Thanks for your comment!

carloswbarros commented 4 years ago

@trungcaot

  1. Can I do a custom select in "ProductRepository.cs" for example, that returns a "ExampleProductDTO"? Ex (ofcourse this is a simple query, but can bem more complex with joins, etc):

    public List<ExampleProductDTO> GetExampleProducts()
    {
           var products = _context.Set<Product>()
                .Select(p => new ExampleProductDTO(p.Id, p.Name))
                .ToList();
           return products;
    }
  2. If your answer is 'yes', than where should I put the "MyCustomProductDTO" file? Should I put it in the Application layer?

  3. Should I call "GetExampleProducts" in a "QueryHandler"? Won't that get too repetitive? Maybe I'm just overthinking it..

Thanks

trungcaot commented 4 years ago

@carloswbarros

  1. No, In my opinion, I think you should create a file as ProductQueryResult and put it in the Domain project. (Domain/Entities/Custom/ProductQueryResult)
    • And then in query handler you use ProductRepository.GetExampleProducts and handle your business then map them to your own DTO and return to the controller.

Thanks.

carloswbarros commented 4 years ago

@trungcaot Thanks for the answer!

So if you want to create a query you need to do something like this:

  1. Create ProductQueryResult.cs
  2. Create the function in the IProductRepositry.cs
  3. Implement the function in ProductRepositry
    • Create the query
    • Map the select to the ProductQueryResult
  4. Create the ProductQueryDTO
  5. Create the GetProductQuery & GetProductQueryHandler
    • Call the function in ProductRepositry
    • Map the result to a ProductQueryDTO
  6. Call GetProductQuery

I like it, my only problem is having to map 2 times just for a query.

trungcaot commented 4 years ago

@carloswbarros

In my point of view, current you probably see mapping 2 times but it will have a benefit in the following case.

  1. Define a function and return ProductQueryResult in ProductRepositry

    • ProductQueryResult will mapping the fields that defined in domains.
  2. You can use these function to many handlers

    • handler 1
      1. get GetExampleProducts
      2. handle logic A
      3. return dto A
    • handler 2
      1. get GetExampleProducts
      2. handle logic B
      3. return dto B

if we return a specific DTO for this function, we will probably define 2 functions. So I prefer we should separate DTO with class mapping to the domain. It's clearer and we can reuse this function in many handlers.

Thanks.

carloswbarros commented 4 years ago

@trungcaot Ye it makes sense. I like this idea

TGrannen commented 4 years ago

@trungcaot @carloswbarros While this is a viable option to remove the EF Core dependency, I think this ends up adding too much boilerplate code to be worth it in a bigger real-world project.

Imagine working in a solution with over a hundred different Repository concrete and interface classes. The solution just becomes more and more bloated with very similar code and becomes more difficult for new developers to get in and understand the codebase. Also, doing this will add significant complexity due to the scenarios mentioned above that don't currently exist.

For those reasons, I'd like to see EF Core remain the Application layer of this template. If you see your specific project needing to divert from EF Core, then the pattern you've outlined should work.