ASP .NET Core + Entity Framework - Zmiana atrybutu modelu nie wpływa na prace aplikacji

0

Witam.
Zauważyłem pewien problem z modelem w aplikacji. Mam model menadżera, który posiada datę rozpoczęcia i zakończenia umowy. Z czego zakończenie umowy jest Nullable i w bazie również jest zaznaczone Allow null, a mimo to nie da się wykonać inserta.

        [Required]
        public DateTime AgreementStartDate { get; set; }
        public DateTime? AgreementEndDate { get; set; }

Błąd:
"The JSON value could not be converted to System.Nullable1[System.DateTime]. Path: $.agreementEndDate | LineNumber: 0 | BytePositionInLine: 222."

Bardzo uciążliwe, ponieważ ten błąd pojawia się dopiero po publikacji aplikacji. W trybie debug pod VS wszystko działa.

0

Jeżeli dobrze pamiętam to dodany w Dotnet 3.0 serializer JSONa nie radził sobie zbyt dobrze z nullem w jakimś polu (miałem problemy jak przesyłałem wartość nullable w API i przychodziło coś w stylu "field": null). Może masz różne wersje frameworka w trakcie debuga i po publikacji? I np. debug ma jakiś poprawiony release.

0

Jak szybko pochwaliłem, tak szybko się popsuło, nawet w VS w trybie debug. Te same błędy. Problem w tym, że podglądając request przeglądarką moje agreementEndDate nie jest NULL tylko jest pustym stringiem:

agreementStartDate: "2019-12-09T00:00:00.000Z"
agreementEndDate: ""

Mam jeszcze kilka innych klas i jedna z nich ma int? DeviceId, które też sypie tym samym błędem. Nie rozumiem co się dzieje... Dopisałem jeszcze w Startup.cs dla testu:

services.AddControllersWithViews()
    .AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true);

Niestety, to też nic nie daje...

1

Potwierdzam, System.Text.Json.Json.JsonSerializer nie traktuje pustego stringa jako null, więc nie daje rady tego zdeserializować.

Ciężko mi to powiedzieć, czy to błąd, bo w zasadzie niezbyt wiem, czy faktycznie "" powinno być traktowane jako null, ale na zdrowy rozsądek to nie.

W każdym razie możesz sobie zbudować konwerter, na wzór tego:

private class Test
{
    [JsonConverter(typeof(NullableInt32ConverterFromEmptyString))]
    public int? test { get; set; }
}

private class NullableInt32ConverterFromEmptyString : JsonConverter<int?>
{
    public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.String)
        {
            string stringValue = reader.GetString();
            if (string.IsNullOrEmpty(stringValue))
                return null;

            if (int.TryParse(stringValue, out int value))
            {
                return value;
            }
        }
        else if (reader.TokenType == JsonTokenType.Number)
        {
            return reader.GetInt32();
        }
        else if (reader.TokenType == JsonTokenType.Null)
        {
            return null;
        }

        throw new JsonException();
    }

    public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
    {
        if (value.HasValue)
            writer.WriteNumberValue(value.Value);
        else
            writer.WriteNullValue();
    }
}

private static void Main(string[] args)
{
    var json = "{ \"test\": \"\" }";

    var data = JsonSerializer.Deserialize<Test>(json);
}

Alternatywnie możesz przerzucić się na Newtonsoft.Json zamiast System.Text.Json, bo on nie ma tego problemu.

0

GH wrzucony
The JSON value could not be converted - Model with nullable DateTime #434

@Ktos
Wykorzystałem twój konwerter do innego modelu, w którym właśnie int? się sypał. Niestety ale teraz dostaje

System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.

Zmieniłem MaxDepth na 64 to pokazuje ten sam błąd ale na końcu jest 64... Co w tym wszystkim najciekawsze? Dane dodają się do bazy, DeviceId jest w bazie NULL, ale teraz przeglądarka rzuca błędem

error:
   type: "https://tools.ietf.org/html/rfc7231#section-6.5.1"
   title: "One or more validation errors occurred."
   status: 400
   traceId: "|2d8246de-48882ab75c7cf487."
   errors:
      id: Array(1)
      0: "The value 'undefined' is not valid."
      length: 1
1

System.Text.Json.JsonException: A possible object cycle (...)

To się pojawia przy serializacji czy deserializacji? Jak przy de- to pokaż tego JSONa. Względnie też może chodzić o model, patrz https://github.com/dotnet/corefx/issues/41002.

Może spróbuj wrócić do Newtonsoft.Json? ;)

0

System.Text.Json jest o wiele bardziej restrykcyjny i nie jest "userfriendly" jak Newtonsoft i to są te przypadki typu "" gdzie można się naciąć przy przejściu z Newtona na Texta.

A jeżeli chodzi o { get; set; } vs field, to:

Deserialization behavior

By default, property name matching is case-sensitive. You can specify case-insensitivity.
If the JSON contains a value for a read-only property, the value is ignored and no exception is thrown.
Deserialization to reference types without a parameterless constructor isn't supported.
Deserialization to immutable objects or read-only properties isn't supported. For more information, see GitHub issue 38569 on immutable object support and issue 38163 on read-only property support in the dotnet/corefx repository on GitHub.
By default, enums are supported as numbers. You can serialize enum names as strings.
Fields aren't supported.
By default, comments or trailing commas in the JSON throw exceptions. You can allow comments and trailing commas.
The default maximum depth is 64.

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to

0

@Ktos:
Jak wrócić do Newtonsoft.Json jeśli to wszystko dzieje się automatycznie przez System.Text?

Stacktrace do błędu z cyklicznością:

System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected(Int32 maxDepth)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

InnerException

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Agreements_Devices_DeviceId". The conflict occurred in database "TRON_JakarSystem", table "dbo.Devices", column 'ID'.
The statement has been terminated.
1

Dzięki bardzo za pomoc. Nie wiem dlaczego, ale od wczoraj czuje jakąś wewnętrzną satysfakcję w wytykaniu błędów w .NET Core firmie Microsoft :D

1 użytkowników online, w tym zalogowanych: 0, gości: 1