Search code examples
c#.netasp.net-coreentity-framework-coreautomapper

AutoMapper DTO Not Serializing Correctly for Derived Class Insertion


I'm facing an issue with inserting a derived type of alert in my application. I have a base class, Alert, and its corresponding DTO, along with a derived class, OperationalAlert, which extends Alert, and an OperationalAlertDto. The insertion is recognized by the AlertTypeId. However, when I attempt to insert an instance of OperationalAlert, the values are coming through as null. I have confirmed that all properties are properly mapped in my AutoMapper configurations, and the necessary fields in both the DTO and OperationalAlertDto are populated. Despite this, the insertion still fails. Has anyone encountered a similar issue or can provide guidance on how to resolve this?

public async Task<Alert> CreateAlertAsync(AlertDTO alertDto)
{
    if (alertDto == null)
    {
        throw new ArgumentNullException(nameof(alertDto), "Alert DTO cannot be null.");
    }

    Alert alert = alertDto.AlertTypeId switch
    {
       1 => _mapper.Map<AlertInformational>(alertDto) as Alert,
       2 => _mapper.Map<AlertIncident>(alertDto) as Alert,
       3 => _mapper.Map<OperationalAlert>(alertDto) as Alert,
       4 => _mapper.Map<OperationalAlert>(alertDto) as Alert,
       _ => throw new ArgumentOutOfRangeException(nameof(alertDto.AlertTypesId), "Invalid alert type"),
    };

    var addedAlertType = await _alertRepository.AddAsync(alert);
    return addedAlertType;
}

Here is the error trace :

public class Alert
{
    public int Id { get; set; }
    public int AlertTypeId{ get; set; }
    public int CreatorId { get; set; }
    public string Name { get; set; }
    public string Title { get; set; }
   
    public int TemplateRefId { get; set; }
    public int DepartmentId { get; set; }
    public bool IsProcessChanged { get; set; }
    public bool? IsPermanent { get; set; }
    public virtual Department? Department { get; set; }
    public virtual User? Creator { get; set; }
    public virtual AlertType? Type { get; set; }
    public virtual Template? Template { get; set; }
    public int ChangeDetailId { get; set; }
    public virtual ChangeDetail? ChangeDetail { get; set; }
    public virtual ICollection<Recipient>? AlertRecipients { get; set; }
    public virtual ICollection<Attachment>? AlertAttachments { get; set; }
    public virtual ICollection<Update>? AlertUpdates { get; set; }
    public virtual ICollection<CustomLog>? LogEntries { get; set; }
    public virtual ICollection<OperationDetail>? OperationDetails { get; set; }
}
using System.Collections.Generic;

namespace YourNamespace.Alerting.Domain.Model.Dto
{
    public class AlertDTO
    {
        public int Id { get; set; } 
        public int AlertTypeId { get; set; } 
        public int CreatorId { get; set; } 
        public string UserName { get; set; } 
        public string Title { get; set; } 
        public string Content { get; set; } 
        public int TemplateRefId { get; set; } 
        public int DepartmentId { get; set; } 
        public bool IsProcessChanged { get; set; } 
        public bool? IsPermanent { get; set; } 
        public virtual ICollection<RecipientDto>? Recipients { get; set; } 
        public virtual ICollection<UpdateDto>? Updates { get; set; } 
        public virtual ICollection<AttachmentDto>? Attachments { get; set; } 
        public virtual ICollection<OperationDto>? Operations { get; set; } 
        public virtual ICollection<LogDto>? Logs { get; set; } 
        public int ChangeDetailId { get; set; } 
    }

    public class ChangeDetailDto
    {
        public int Id { get; set; } 
        public string Name { get; set; } 
    }

    public class RecipientDto
    {
        public int AlertId { get; set; } 
        public int RecipientId { get; set; } 
    }

    public class UpdateDto
    {
        public int Id { get; set; } 
        public string Message { get; set; } 
        public int AlertId { get; set; } 
        public int TypeId { get; set; } 
    }
}
public class OperationalAlert : Alert
{
    public AlertState State { get; set; }
    public UrgencyLevel Urgency { get; set; }
    public bool IsEnabled { get; set; }
    public DateTime CreatedOn { get; set; }
    public string Content { get; set; }
    public DateTime ExpiresOn { get; set; }
    public string Location { get; set; }
    public string MessageContent { get; set; }
    public string Description { get; set; }
    public DateTime OccurrenceTime { get; set; }
    public string Method { get; set; }
    public string Reason { get; set; }
    public virtual ICollection<OperationalAlertRevision>? Revisions { get; set; }
}

public enum AlertState
{
    Active,
    Inactive,
    Expired,
    Pending,
    Resolved
}

public enum UrgencyLevel
{
    Low,
    Medium,
    High,
    Critical
}

Here is the dto of prod alert :

public class OperationalAlertDto : AlertDTO
{
    public int Id { get; set; }
    public string Title { get; set; }
    public AlertState State { get; set; }
    public UrgencyLevel Urgency { get; set; }
    public bool IsEnabled { get; set; }
    public string Content { get; set; }
    public DateTime CreatedOn { get; set; }
    public DateTime ExpiresOn { get; set; }
    public string Description { get; set; }
    public DateTime OccurrenceTime { get; set; }
    public string Method { get; set; }
    public string Reason { get; set; }
    public string Location { get; set; }
    public string MessageContent { get; set; }

    public List<OperationalAlertRevisionDto>? Revisions { get; set; }
}

public class OperationalAlertRevisionDto
{
    public int RevisionId { get; set; }
    public bool IsCurrent { get; set; }
    public int AlertId { get; set; }
    public int AlertTypeId { get; set; }
}

public AutoMapperProfile()
{
        CreateMap<Department, DepartmentDTO>().ReverseMap();
        CreateMap<TemplateType, TemplateTypeDTO>().ReverseMap();
        CreateMap<Template, TemplateDTO>().ReverseMap();
        CreateMap<User, UserDTO>();

        CreateMap<OperationalAlertDto, OperationalAlert>()
            .IncludeBase<AlertDTO, Alert>();

        CreateMap<AlertDTO, AlertInformational>();
        CreateMap<AlertDTO, AlertIncident>();
        CreateMap<AlertDTO, OperationalAlert>().ReverseMap();
        CreateMap<OperationalAlertDto, OperationalAlert>().ReverseMap();
        CreateMap<OperationalAlertDto, OperationalAlert>().ReverseMap();

        CreateMap<Alert, AlertDTO>()
            .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.user != null ? src.user.UserName : null))
            .Include<OperationalAlert, OperationalAlertDto>()
            .ReverseMap();

       
        CreateMap<AlertRecipient, AlertRecipientDto>().ReverseMap();
        CreateMap<AlertUpdate, AlertUpdateDto>().ReverseMap();
}

Here is my discriminator definition:

builder.Services.AddControllers()
    .AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        options.SerializerSettings.Converters.Add(
            JsonSubtypesConverterBuilder
                .Of(typeof(Alert), nameof(Alert.AlertTypeId I))
                .RegisterSubtype(typeof(AlertInformational), 1)
                .RegisterSubtype(typeof(AlertIncident), 2)
                .RegisterSubtype(typeof(OperationalAlert), 4) 
                .SerializeDiscriminatorProperty()
                .Build());
    });
options.UseAllOfToExtendReferenceSchemas();
options.UseAllOfForInheritance();
options.UseOneOfForPolymorphism();
options.SelectDiscriminatorNameUsing(type =>
{
    return type.Name switch
    {
        nameof(Alert) => "AlertTypeId",
        _ => null
    };
});

Here is the error trace indicating that values cannot be inserted as null, suggesting that the mapping of data is not being recognized correctly, resulting in the values not being inserted.

Microsoft.EntityFrameworkCore.Update[10000]
      An exception occurred in the database while saving changes for context type 'alertapp_Alerting.Persistence.Context.alertappAlertingDbContext'.
      Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
       ---> MySqlConnector.MySqlException (0x80004005): Column 'Content' cannot be null
         at MySqlConnector.Core.ServerSession.ReceiveReplyAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 894
         at MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) in /_/src/MySqlConnector/Core/ResultSet.cs:line 37
         at MySqlConnector.MySqlDataReader.ActivateResultSet(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 130
         at MySqlConnector.MySqlDataReader.NextResultAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 90
         at MySqlConnector.MySqlDataReader.NextResultAsync(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 49
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at Pomelo.EntityFrameworkCore.MySql.Storage.Internal.MySqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
      Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
       ---> MySqlConnector.MySqlException (0x80004005): Column 'Content' cannot be null
         at MySqlConnector.Core.ServerSession.ReceiveReplyAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 894
         at MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) in /_/src/MySqlConnector/Core/ResultSet.cs:line 37
         at MySqlConnector.MySqlDataReader.ActivateResultSet(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 130
         at MySqlConnector.MySqlDataReader.NextResultAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 90
         at MySqlConnector.MySqlDataReader.NextResultAsync(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 49
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at Pomelo.EntityFrameworkCore.MySql.Storage.Internal.MySqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: An error occurred while adding the alert and sending the email.
       ---> Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
       ---> MySqlConnector.MySqlException (0x80004005): Column 'Content' cannot be null
         at MySqlConnector.Core.ServerSession.ReceiveReplyAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 894
         at MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) in /_/src/MySqlConnector/Core/ResultSet.cs:line 37
         at MySqlConnector.MySqlDataReader.ActivateResultSet(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 130
         at MySqlConnector.MySqlDataReader.NextResultAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 90
         at MySqlConnector.MySqlDataReader.NextResultAsync(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 49
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at Pomelo.EntityFrameworkCore.MySql.Storage.Internal.MySqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at alertapp_Alerting.Persistence.Repositories.AlertRepository.AddAsync(Alert alert) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Persistence\Repositories\AlertRepository.cs:line 47
         --- End of inner exception stack trace ---
         at alertapp_Alerting.Persistence.Repositories.AlertRepository.AddAsync(Alert alert) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Persistence\Repositories\AlertRepository.cs:line 54
         at alertapp_Alerting.Services.AlertService.CreateAlertAsync(AlertDTO alertDto) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Services\AlertService.cs:line 83
         at alertapp_Alerting.Controllers.AlertController.CreateAlertWithFile(AlertDTO alert) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Controllers\AlertController.cs:line 52
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
fail: alertapp_Alerting.Exceptions.GeneralExceptionHandler[0]
      Something went wrong..
      System.InvalidOperationException: An error occurred while adding the alert and sending the email.
       ---> Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
       ---> MySqlConnector.MySqlException (0x80004005): Column 'Content' cannot be null
         at MySqlConnector.Core.ServerSession.ReceiveReplyAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 894
         at MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) in /_/src/MySqlConnector/Core/ResultSet.cs:line 37
         at MySqlConnector.MySqlDataReader.ActivateResultSet(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 130
         at MySqlConnector.MySqlDataReader.NextResultAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 90
         at MySqlConnector.MySqlDataReader.NextResultAsync(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 49
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at Pomelo.EntityFrameworkCore.MySql.Storage.Internal.MySqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at alertapp_Alerting.Persistence.Repositories.AlertRepository.AddAsync(Alert alert) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Persistence\Repositories\AlertRepository.cs:line 47
         --- End of inner exception stack trace ---
         at alertapp_Alerting.Persistence.Repositories.AlertRepository.AddAsync(Alert alert) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Persistence\Repositories\AlertRepository.cs:line 54
         at alertapp_Alerting.Services.AlertService.CreateAlertAsync(AlertDTO alertDto) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Services\AlertService.cs:line 83
         at alertapp_Alerting.Controllers.AlertController.CreateAlertWithFile(AlertDTO alert) in C:\Users\e10133604\Desktop\alertappalertingapi\alertapp-Alerting\Controllers\AlertController.cs:line 52
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)

Solution

  • The issue will be how controller actions populate models from the request when you want to have a polymorphic argument. Normally if you have a function that accepts a base class AlertDTO and your calling code constructs either an OperationalAlertDTO, ProductionAlertDTO, or any of the other sub-classes and you call your method accepting the argument as AlertDTO, the code would work as you expect since the underlying object "is an" OperationalAlertDTO, etc. So conditional code that re-casts to the subclass will work. It is not a good implementation for a polymorphic method to have conditional code to work with specific sub-classes, but the code would work as you expect.

    With a controller action however, this will not work. When you create a controller action that accepts an ActionDTO, it doesn't matter whether the view (form or Ajax/Fetch) call to the controller populates enough fields to create an OperationalAlertDTO or any of the other sub-classes, if the action accepts an ActionDTO the controller will solely attempt to create and populate an ActionDTO, not any of the sub-classes.

    There are two options with controller actions depending on how you structure your application. If your views etc. are specific to the type of Alert, so you have views that will want to work with OperationalAlertDTOs vs. other views that work with ProductionAlertDTOs, then a simple solution is to split the controller actions by concrete sub-class type since you are conditionally handling each subclass in your example. For instance:

    public async Task<Alert> CreateOperationalAlertAsync(OperationalAlertDTO alertDto)
    
    public async Task<Alert> CreateProductionAlertAsync(ProductionAlertDTO alertDto)
    

    These methods can then use the proper mappings from OperationalAlertDTO -> OperationalAlert etc. without issue because MVC will resolve whatever data is passed from the view/client into the correct concrete DTO type.

    If there is common behavior you want to take with all instances of these alerts that come in, you can have a shared private function that accepts alertDto from each as AlertDTO, and these methods can all call that shared method. Just avoid putting conditional re-casting within common base-class operations. Methods that accept a base class (or interface) should only care that each calls argument is that base type.

    The other alternative is to wire a custom model binder and expose the concrete type within the view data as outlined: Polymorphic model binding and ViewModel with List<BaseClass> and editor templates

    This approach allows a view to be populated with an OperationalAlertDTO or ProductionAlertDTO which exposes a hidden input for the concrete type. A custom model binder is registered with ASP.Net MVC so that when it has a controller method expecting an AlertDTO type, it looks for a ModelType in the view data and uses that to construct the appropriate concrete sub-class.