AutoQuery's AutoCrud meta.RemoveDtoProps causing DTO property to be removed

I have an issue with AutoQuery’s AutoCrud that is causing a DTO property to be incorrectly removed because of a value in meta.RemoveDtoProps.

The model definition for the CreateLocationGroup service endpoint is

[Tag("Internal.LocationGroup")]
[Api("internal only")]
[ValidateIsAuthenticated]
[ValidateHasRole(UserRole.ManageReference)]
[Route("/api/internal/location-group", "POST")]
public class CreateLocationGroup : ICreateDb<LocationGroup>, IReturn<CodeResponse>, IPost
{
    [ValidateNotEmpty, ValidateLength(1, 8)]
    public string PkLocationCode { get; set; } = null!;

    [ValidateNotEmpty, ValidateLength(1, 8)]
    public string PkLocationTypeCode { get; set; } = null!;

    [ValidateNotEmpty]
    public int TransId { get; set; }

    [ValidateNotEmpty, ValidateLength(1, 8)]
    public string PkParentLocationCode { get; set; } = Constant.ParentLocationCode; // "/"

    [ValidateNotEmpty, ValidateExactLength(1)]
    public string VirtualIndicator { get; set; } = Constant.StatusNo; // "N"
}

The DTO, LocationGroup, is defined as follows (it is an external vendor database)

[Schema("dbo")]
[Alias("location_group")]
[NamedConnection(DbConstant.Trade)]
[DataContract]
[UniqueConstraint(nameof(PkParentLocationCode), nameof(PkLocationCode), nameof(PkLocationCodeType))] // compound PK
public class LocationGroup
{
    [Alias("parent_loc_code")]
    [DataMember, PrimaryKey, Required]
    [CustomField("CHAR(8)")]
    [ValidateNotEmpty, ValidateLength(1, 8)]
    public string PkParentLocationCode { get; set; } = Constant.ParentLocationCode; // "/"

    [Alias("loc_code")]
    [DataMember, PrimaryKey, Required]
    [CustomField("CHAR(8)")]
    [ValidateNotEmpty, ValidateLength(1, 8)]
    public string PkLocationCode { get; set; } = null!;

    [Alias("loc_type_code")]
    [DataMember, PrimaryKey, Required]
    [CustomField("CHAR(8)")]
    [ValidateNotEmpty, ValidateLength(1, 8)]
    public string PkLocationCodeType { get; set; } = null!;

    [Alias("virtual_ind")]
    [DataMember, Required]
    [ValidateNotEmpty, ValidateExactLength(1)]
    public string VirtualIndicator { get; } = Constant.StatusNo; // "N"

    [Alias("trans_id")]
    [DataMember, Required]
    [ValidateNotEmpty]
    public int TransId { get; set; }
}

The create service implementation in ServiceInterface is:

    public CodeResponse Post(CreateLocationGroup request)
    {
        ParamValidator.CheckNotNullOrWhiteSpaceEx(request.PkLocationCode);
        ParamValidator.CheckNotNullOrWhiteSpaceEx(request.PkLocationTypeCode);

        using var db = AutoQuery!.GetDb<LocationGroup>(Request);
        var req = FromCreateLocationGroup(request);

        try
        {
            var unused = (CodeResponse)AutoQuery.Create(req, Request);
        }
        catch (OptimisticConcurrencyException e)
        {
            // triggers on the db table may cause an OptimisticConcurrencyException on insert
            _log.Error(e.Message);
        }
        finally
        {
            using var auditService = base.ResolveService<AuditService>();
            auditService.Post(new CreateDataTrack
            {
                ReferenceCode = RefCode,
                PkCode = request.PkLocationCode,
                ReferenceData = req.ToJson(),
                AuditEventId = AuditEvent.Created
            });
        }

        return Read(request.PkLocationCode, request.PkLocationTypeCode);
    }

An error is thrown because a NULL value is being inserted in the loc_type_code column (i.e. PkLocationCodeType in the DTO).

Tracing through the problem, it is being caused by the following ServiceStack AutoQueryFeature.cs code:

namespace ServiceStack
{
    public partial class AutoQueryFeature
    {

        // code hidden

        private Dictionary<string, object> ResolveDtoValues(AutoCrudMetadata meta, IRequest req, object dto, bool skipDefaults=false)
        {
            ILog log = null;
            var dtoValues = dto.ToObjectDictionary();

            foreach (var entry in meta.MapAttrs)
            {
                if (dtoValues.TryRemove(entry.Key, out var value))
                {
                    dtoValues[entry.Value.To] = value;
                }
            }

            // code hidden

            List<string> removeKeys = null;
            foreach (var removeDtoProp in meta.RemoveDtoProps)
            {
                removeKeys ??= new List<string>();
                removeKeys.Add(removeDtoProp);
            }

            // code hidden

            if (removeKeys != null)
            {
                foreach (var key in removeKeys)
                {
                    dtoValues.RemoveKey(key);                // *** this causes DTO's PkLocationCodeType to be removed WHY?
                }
            }

            // code hidden

            return dtoValues;
        }
    }
}

Where or how is meta.RemoveDtoProps getting populated?

I am using ServiceStack 8.0.0, NET8, Windows.

It gets added here:

  1. If the property doesn’t exist on the data model
  2. If it’s an ignored “Reset” or “RowVersion” property that’s not on the data model
  3. If it has the [AutoIgnore] attribute

Thank you for the response. After double-checking (for the nth time) my code, I found I had a typo between the service and the DTO that was causing the property to be removed (PkLocationCodeType vs PkLocationTypeCode).

Fixed the typo and it works like a charm.

thank you very much for the quick support.

1 Like