Search code examples
c#asp.net-coreentity-framework-coreaspnetboilerplateef-core-2.0

Implementation of Hard Delete for Entity which is being referred in other entity


I'm trying to implement hard delete for FullyAuditedEntity Entity Test. The primary key of Test is Id, which is being referred to a TestTest2 entity as a foreign key. When I'm trying to delete a record from Test entity, it gives the below error.

I have followed this question for implementation.

TestAppService

public async Task DeleteTest(EntityDto<string> input)
{
    using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.SoftDelete))
    {
        await _TestRepository.DeleteTest(input);
        CurrentUnitOfWork.SaveChanges();
    }
}

TestRepository

public async Task DeleteArticle(EntityDto input)
{
    await DeleteAsync(x => x.Id == input.Id);
}

TestTest2

[Table("TestTest2")]
public class TestTest2 : FullAuditedEntity
{
    [ForeignKey("TestId")]
    public virtual Test Test { get; set; }
    public virtual string TestId { get; set; }

    [ForeignKey("Test2Id")]
    public virtual Test2Details Test2s { get; set; }
    public virtual int Test2Id { get; set; }
}

MyProjectDbContextModelSnapshot

modelBuilder.Entity("MyCompany.MyProject.Business.Model.Tests.TestTest2Association", b =>
{
    b.HasOne("MyCompany.MyProject.Business.Model.Tests.Test", "Test")
        .WithMany()
        .HasForeignKey("TestId");

    b.HasOne("MyCompany.MyProject.Business.Model.Test2s.Test2Details", "Test2s")
        .WithMany()
        .HasForeignKey("Test2Id")
        .OnDelete(DeleteBehavior.Cascade);
});

ERROR 2018-02-28 18:10:09,840 [26 ] Mvc.ExceptionHandling.AbpExceptionFilter - An error occurred while updating the entries. See the inner exception for details. Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "FK_TestTest2_Test_TestId". The conflict occurred in database "MyProjectDb", table "dbo.TestTest2", column 'TestId'. The statement has been terminated. at System.Data.SqlClient.SqlConMyCompanytion.OnError(SqlException exception, Boolean breakConMyCompanytion, Action1 wrapCloseInAction) at System.Data.SqlClient.SqlInternalConMyCompanytion.OnError(SqlException exception, Boolean breakConMyCompanytion, Action1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConMyCompanytionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData() at System.Data.SqlClient.SqlDataReader.get_MetaData() at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString) at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method) at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior) at System.Data.Common.DbCommand.ExecuteReader() at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConMyCompanytion conMyCompanytion, DbCommandMethod executeMethod, IReadOnlyDictionary2 parameterValues) at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteReader(IRelationalConMyCompanytion conMyCompanytion, IReadOnlyDictionary2 parameterValues) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConMyCompanytion conMyCompanytion) --- End of inner exception stack trace --- at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConMyCompanytion conMyCompanytion) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(Tuple2 parameters) at Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func3 operation, Func3 verifySucceeded) at Microsoft.EntityFrameworkCore.ExecutionStrategyExtensions.Execute[TState,TResult](IExecutionStrategy strategy, TState state, Func2 operation) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConMyCompanytion conMyCompanytion) at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IReadOnlyList1 entries) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList1 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) at Abp.EntityFrameworkCore.AbpDbContext.SaveChanges() in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\AbpDbContext.cs:line 198 at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext`3.SaveChanges() in D:\Github\aspnetboilerplate\src\Abp.ZeroCore.EntityFrameworkCore\Zero\EntityFrameworkCore\AbpZeroCommonDbContext.cs:line 154 at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContext(DbContext dbContext) in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 159 at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChanges() in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 60 at MyCompany.MyProject.Business.Services.Tests.TestAppService.d__12.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 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.d__14.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__23.MoveNext() INFO 2018-02-28 18:10:09,873 [26 ] etCore.Mvc.Internal.ObjectResultExecutor - Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext. INFO 2018-02-28 18:10:09,922 [26 ] ore.Mvc.Internal.ControllerActionInvoker - Executed action MyCompany.MyProject.Business.Services.Tests.TestAppService.DeleteTest (MyCompany.MyProject.Business.Services) in 3940.4091ms INFO 2018-02-28 18:10:10,158 [26 ] soft.AspNetCore.Hosting.Internal.WebHost - Request finished in 4037.4507ms 500 application/json; charset=utf-8

Note: It should delete the entry from Test and Test2 tables.

Update

The below anwer works pretty well but I have some specific requirement. Test Entity Id is being referred in other tables like TestTest2, TestTest3, TestTest4, TestTest5. When I delete a record from Test table, it should delete from all tables. But I also need to call the other dependent tables ( for example TestTest2, TestTest3, TestTest4, TestTest5) Delete method to do some extra cleanup specific to that entity (for example TestTest2, TestTest3, TestTest4, TestTest5).

TestRepository

public async Task DeleteTest(EntityDto input)
{
    await DeleteAsync(input.Id);

    _TestTest2Repository.Delete(x => x.TestId == input.Id);
    _TestTest3Repository.Delete(x => x.TestId == input.Id);
    _TestTest4Repository.Delete(x => x.TestId == input.Id);
    _TestTest5Repository.Delete(x => x.TestId == input.Id);
}

Solution

  • From the documentation on Cascade Delete:

    For optional relationships: ClientSetNull (default)
    - Effect on dependent/child in database: None

    You have to configure the delete behavior using Fluent API in your DbContext:

    modelBuilder.Entity<TestTest2>()
                .HasOne(t => t.Test)
                .WithMany()
                .OnDelete(DeleteBehavior.Cascade);