Base solution for your next web application

Activities of "fvdh"

Hi maliming,

While we were working out a test project to reproduce the problem we found a solution for our problem.

We were able to use IRepository.InsertAndGetId. The problem with the HardDelete method is that is didn't start a UnitOfWork.

Following code in our test method works:

            // Act hard delete
            WithUnitOfWork(() =>
            {
                _projectRepository.HardDelete(p => p.Id == projectId);
            });

When we do not start a unit of work, we get a NullReference exception.

Hello,

Is there a way to execute a hard delete on a mocked database in a unit test? In an appservice we can excecute IRepository.HardDelete().

We want to execute the hard delete in the unit test (class derived from AppTestBase), we can't resolve the IRepository there.

Following example explains what we are trying to do:

        [Fact]
        public async Task Should_Delete_Entity_With_LocalizedString()
        {
            // Create a new test entity
            await _testLocalizedResourceAppService.CreateTestEntityA();

            // Test for the create entity
            UsingDbContext(context =>
            {
                var resources = context.TestLocalizedResources.Include(x => x.Values).ToList();
                resources.Count.ShouldBe(1);
                resources.First().Values.Count.ShouldBe(2);
            });

            // Delete the created entity
            var entityAList = await _testLocalizedResourceAppService.GetList();
            await _testLocalizedResourceAppService.Delete(entityAList.Items.First().Id);

            // The entity is soft deleted, the resources still exists
            UsingDbContext(context =>
            {
                context.TestEntitiesA.Count().ShouldBe(1);
                context.TestEntitiesA.First().IsDeleted.ShouldBeTrue();

                var resources = context.TestLocalizedResources.Include(x => x.Values).ToList();
                resources.Count.ShouldBe(1);
                resources.First().Values.Count.ShouldBe(2);

            });

            // Execute hard delete
            // How to execute hard delete here?

            // The rows should be deleted from the database
            UsingDbContext(context =>
            {
                context.TestEntitiesA.Count().ShouldBe(0);

                context.LocalizedResources.Count().ShouldBe(0);
                context.LocalizedResourceValues.Count().ShouldBe(0);
            });
        }

We already tried variants of following code without success:

            var uowOptions = new UnitOfWorkOptions();
            uowOptions.FilterOverrides.Add(new DataFilterConfiguration(AbpDataFilters.SoftDelete, false));

            await WithUnitOfWorkAsync(() =>
            {
                return _testLocalizedResourceAppService.Delete(entityAList.Items.First().Id);
            }, uowOptions);

I just tested it in our app which is also published on Azure and the download functionality works fine for both host and tenant.

Can you try it with another user or another tenant? Maybe it's related to the exported data. Please search the logs for some exceptions to get some more details about the problem.

@hi maliming

I just want to inform you that the problem is solved based on the sent project. The code you've sent was a good base to start from. I found some other issues in the code which were also responsible for not finding the right solution.

Thanks for helping me out!

hi @maliming,

You can send it to [email protected] .

Thx.

Hi adudley,

Are you able to view the latests log in the webpage? Can you tell me how you published the app to Azure?

hi @maliming

I've just send you the example via WeTransfer. A new application service 'ProductAppService' is added with 2 services.

The service that failes:

  1. Get the product and map it to a DTO
  2. Change the specifications in DTO object
  3. Maps it back to the product object

This fails with following error:

The instance of entity type 'ProductSpecification' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
System.InvalidOperationException: The instance of entity type 'ProductSpecification' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node, Boolean force)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode node, TState state, Func`3 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.NavigationCollectionChanged(InternalEntityEntry entry, INavigation navigation, IEnumerable`1 added, IEnumerable`1 removed)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationCollectionChanged(InternalEntityEntry entry, INavigation navigation, IEnumerable`1 added, IEnumerable`1 removed)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigation navigation)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
   at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.Entries()
   at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext`3.SaveChangesAsync(CancellationToken cancellationToken)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContextAsync(DbContext dbContext) in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 167
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesAsync() in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 68
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.CompleteUowAsync() in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 83
   at Abp.Domain.Uow.UnitOfWorkBase.CompleteAsync() in D:\Github\aspnetboilerplate\src\Abp\Domain\Uow\UnitOfWorkBase.cs:line 273
   at Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) in D:\Github\aspnetboilerplate\src\Abp.AspNetCore\AspNetCore\Mvc\Uow\AbpUowActionFilter.cs:line 49
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()

The second service succeed without error when changing the Specifications directly in the Product object instead of mapping it to a DTO object.

This scenario tells me that something is wrong with the mapping from ProductDTO to Product. I hope you guys can point me to the right direction.

Sorry for the late answer, I haven't got any notice of the reply.

The code you provided runs fine but doesn't proves anything. The problem is that AutoMapper creates new entities for the DTO's instead of updating the current DTO objects.

I've changed your Entity and DTO classes to FullAudited and checkes for the creation time. When you run that test you will see that the CreationTime get changed.

        public class Thing : FullAuditedEntity
        {
            public string Title { get; set; }

            public string Code { get; set; }
        }

        public class ThingDto : FullAuditedEntityDto
        {
            public string Title { get; set; }
        }

        [Fact]
        public async Task Should_Update_Collections()
        {
            Mapper.Initialize(cfg =>
            {
                cfg.AddCollectionMappers();
                cfg.CreateMap<ThingDto, Thing>().EqualityComparison((dto, entity) => dto.Id == entity.Id).ReverseMap();
            });

            var dtos = new List<ThingDto>
            {
                new ThingDto { Id = 1, Title = "test1" },
                new ThingDto { Id = 2, Title = "test2" }
            };

            var entities = new List<Thing>
            {
                new Thing { Id = 1, Title = "" , Code = "1"},
                new Thing { Id = 2, Title = "" , Code = "2"}
            };

            DateTime tCreationTime = entities[0].CreationTime;

            Mapper.Map(dtos, entities);

            entities[0].Id.ShouldBe(1);
            entities[0].Title.ShouldBe("test1");
            entities[0].Code.ShouldBe("1");

            entities[0].CreationTime.ShouldBe(tCreationTime);

            entities[1].Id.ShouldBe(2);
            entities[1].Title.ShouldBe("test2");
            entities[1].Code.ShouldBe("2");
        }
    }

The product type is ASP.NET CORE & Angular.

That's a way to do it but it feels to much like a workaround. In the example I simplified the entity, in the original code we have 5 more collections in the enitty. If we need to handle each collection with a separately handling we get a lot of messy code.

The AutoMapper.Collection is specially made for supporting collections and is also implemented in Abp. So i guess it's only matter of finding out the correct configuration.

Showing 1 to 10 of 13 entries