JonPSmith / EfCore.GenericServices

A library to help you quickly code CRUD accesses for a web/mobile/desktop application using EF Core.
https://www.thereformedprogrammer.net/genericservices-a-library-to-provide-crud-front-end-services-from-a-ef-core-database/
MIT License
601 stars 94 forks source link

GenericService throws an error sometimes while using UpdateAndSaveAsync with method name #49

Closed boradakash closed 4 years ago

boradakash commented 4 years ago

This issue for the further investigation for the same issue we discussed via Disqus . I'm using GenericService library and using DDD aproach and I'm trying to delete review using webapi.

But while calling method of GenericService as following: await service.UpdateAndSaveAsync(item, nameof(Book.RemoveReview)); it throws

Message: 
    System.InvalidOperationException : Could not find a method of name RemoveReview. The method that fit the properties in the DTO/VM are:
    Match: RemoveReviewWithInclude(Int32 ReviewId)
    Match: RemovePromotion()
  Stack Trace: 
    DecodedDto.FindMethodCtorByName(DecodeName nameInfo, List`1 listToScan, String errorString)
    DecodedDto.GetMethodToRun(DecodeName nameInfo, DecodedEntityClass entityInfo)
    EntityUpdateHandler`1.ReadEntityAndUpdateViaDto(TDto dto, String methodName)
    CrudServicesAsync`1.UpdateAndSaveAsync[T](T entityOrDto, String methodName)
    BookController.DeleteReview(RemoveReviewDto item, ICrudServicesAsync service) line 65
    IntegrationTestBookController.TestDeleteReviewOk() line 73
    --- End of stack trace from previous location where exception was thrown ---

I've BookController.RemoveReview method and I'm explicitly defining to use it by passing second parameter to UpdateAndSaveAsync() but it still throw that error.

I'm not sure what I'm doing wrong. Perhaps some issue with DTO or not sure? My guess is if we define the method name, it shouldn't try to match other methods. but by looking at the above error it looks like it still try to match the method.

Here's repo to reproduce that issue : Repo I've also added test for it. you can find it here: Test class and I'm using this BookController

JonPSmith commented 4 years ago

I'm not going to look at your code until the weekend, but that is a very specific error message which says either it couldn't find your RemoveReview method (which I assume is there) OR the properties in your DTO doesn't match the RemoveReview parameters.

Have a look at that yourself first. If you think it does match then add the RemoveReview method to the issue

boradakash commented 4 years ago

Hi Jon, Thanks for your quick reply.

I think you're right. I'm passing the following DTO to UpdateAndSaveAsync

 public class RemoveReviewDto : ILinkToEntity<Book>
    {
        public int BookId { get; set; }
        public int ReviewId { get; set; }
    }

But RemoveReview method is expecting

  public void RemoveReview(Review review, DbContext context = null)
  {
       ---- 
       ----
  }

I thought GenericSerivce will fetch Review by ReviewId(from DTO) similar to IncludeThen but perhaps I was wrong.

So in this case I'll need to fetch the Review from item.ReviewId and then I need to pass it to UpdateAndSaveAsync(), right? is it the right approach or any other you think? BookController

[HttpPost]
[Route("review/delete")]
public async Task<ActionResult<WebApiMessageOnly>> DeleteReview(RemoveReviewDto item, [FromServices]ICrudServicesAsync service)
{
     //I need to fetch Review by item.ReviewId here and then pass it to UpdateAndSaveAsync, right?
      await service.UpdateAndSaveAsync(item, nameof(Book.RemoveReview));
      return service.Response();
 }
JonPSmith commented 4 years ago

OK.

A GenericService Update will, by default, only load the specific entity class using the primary key(s) in your DTO. If you are using a DDD approach and want to handle relationships (DDD aggregates) from the entity class you have two options:

  1. Pass in your application's DbContext into your DDD method and use to change a relationship - see this example of that approach.
  2. Add the [IncludeThen(nameof(relational property)] to your DTO, which make GenericServices load that relationship before calling your method. This makes the code inside your DDD method much simpler - see this example.

All of this in the documentation but I agree it takes some time to understand how it works.