yanpitangui/dotnet-api-boilerplate

[FEATURE] DB Context Factory / EFCore 6 Change Tracking challenges

rhysjtevans opened this issue · 12 comments

Is your feature request related to a problem? Please describe.
As context, I've always been a fan of accessing and creating dbcontexts when I've needed to do multiple db related actions. I feel with this project I should give in and simply adopt the context factory method but no idea how I'd go about doing that.
Problem I'm running into is I've added an ICollection<Hero> Heroes to the user class but when trying to create it, I continuously getting change tracking problems.

Describe the solution you'd like
Add ICollection<Hero> Heroes to the User class and adopt (if it's the right solution) a db context factory

Describe alternatives you've considered
I did wonder about scoped vs transient but I feel the context factory might fit better

Additional context
None

Forgot to say, also love the boilerplate!

Hello, Rhys, can you please post the error you are getting?
That would be very helpful.

Forgot to say, also love the boilerplate!

thank you! I hope it helps you and more people

Apologies, I got round the issue, took me a while to reproduce it. I've also pivot'd from the heroes example but the classes are basically the same.

Error is

InvalidOperationException: The instance of entity type 'Tenant' cannot be tracked because another instance with the key value '{Id: d9aabb38-b372-444d-dfbf-08da008e2903}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached

In a controller I have:

string domain = "mydomain.com";
GetTenantDto tenant = await _tenantService.GetTenantByDomain(domain);
          if(tenant == null){
            CreateTenantDto t = new CreateTenantDto(){
              Name = tokenS.Claims.First(claim => claim.Type == "tid").Value,
              Domain = domain
            };
            tenant = await _tenantService.CreateTenant(t);
          }

          GetUserDto userx = await _userService.GetUserByEmail(tokenS.Claims.First(claim => claim.Type == "preferred_username").Value);
          if (userx == null) {
            userx = await _userService.CreateUser(new CreateUserDto(){
              Email = tokenS.Claims.First(claim => claim.Type == "preferred_username").Value,
              IsAdmin = false,
              Tenant = tenant,
            });
          }

Any thoughts would be greatly appreciated,
Here's a screenshot of the stack trace;
image

I've just got round the problem by adding
Db.Entry(entity).State = EntityState.Detached; to Update and Create in the Repository.cs not sure if this is the right thing to do though,

Update: This didn't actually get round the problem it consequently stopped the tenant entity from being created in the first place.

I've just got round the problem by adding Db.Entry(entity).State = EntityState.Detached; to Update and Create in the Repository.cs not sure if this is the right thing to do though,

Update: This didn't actually get round the problem it consequently stopped the tenant entity from being created in the first place.

Yeah, unattaching the entity will not fix it.
Can you create a simple repo or something so that I can take a look at the request and help you on this?

That would be awesome, will add you on now

I've added you as a collaborator and branched onto 'first'

@yanpitangui Did some more testing and I replaced the Dto objects and the tenantService I had in what is effectively the HeroController with the repository and that works,
I noticed where I had Tenant property on the user class and a GetTenantDto on the CreateUserDto it wasn't mapping correctly and It looks like it was trying to create both the user and a new Tenant (that already exists)

Do note the "Tenant" is completely interchangeable with "Hero".

GetTenantDto tenant = await _tenantRepository.GetByDomain(domain);
if(tenant == null){
            CreateTenantDto ds = new CreateTenantDto(){
              Name = tokenS.Claims.First(claim => claim.Type == "tid").Value,
              Domain = domain
            };
            tenant = await _tenantService.CreateTenant(ds); // (TenantService is a duplicate of HeroService)
}
....
GetUserDto userx = await _userService.GetUserByEmail(tokenS.Claims.First(claim => claim.Type == "preferred_username").Value);
          if (userx == null) {
            userx = await _userService.CreateUser(new CreateUserDto(){
              Email = tokenS.Claims.First(claim => claim.Type == "preferred_username").Value,
              IsAdmin = false,
              Tenant = tenant,   // Tenant here is GetTenantDto from above
            });
          }

Oh, that's great! So did you solve your problem?
I did not have time to take a look on your code yet.

So it's probably my lack of dotnet aspnet knowledge but I think what I'm going to do moving forward is use the repository if I'm interacting with the database entities in any way and then the service to map the entity to a Dto to transfer to the client,

So it's probably my lack of dotnet aspnet knowledge but I think what I'm going to do moving forward is use the repository if I'm interacting with the database entities in any way and then the service to map the entity to a Dto to transfer to the client,

Yeah, that sounds ok. In the future, I will revisit the use of repositories, maybe remove them completely. But that is another talk. For now, I will close the issue, feel free to open others if you have any other questions