EntityFramework.Docs icon indicating copy to clipboard operation
EntityFramework.Docs copied to clipboard

Document need for property access with protobuf 'Optional Int32' field

Open SteepT opened this issue 3 years ago • 2 comments

File a bug

I discovered while writing unit tests for one of my services that when I persist an 'optional int32' field from a Prototype model thru my DbContext it is not returned when queried.

A non-optional int32 is always returned, as are the other optional fields in the ExampleRecord (all strings currently).

However a non-optional int32 doesn't have the field persistence tracking that I am looking for to ensure I can update (See ExampleRecordService.Update) only the changed fields on that ExampleRecord.

I checked the schema in the DbContext and the field 'ExampleId' is marked as required and exists in the Schema.

When debugging the generated code for the ExampleRecord the Set operation on the ExampleId property is never invoked when EF goes to deserialize the data into the Prototype.

Thus I am under the assumption that the EF framework is potentially passing data into the model somehow incorrectly. If that is not the case, I would appreciate guidance on who/where to submit this bug next.

Include your code

I have attached an example project here: Example Project.zip

If you go into Tests and execute the test "CreateExampleRecord_Success" you will see a failure as the data sent in the CreateAsync does not match the response data from the ReadAsync call.

The ExampleId = 42 appears to be missing from the model when it is returned: var exists = await this.context.FindAsync<ExampleRecord>(request.Email);

I debugged as much as I had access too, but I couldn't find an exact reason the data is missing when it get deserialized from the DB.

Include stack traces

This is what the testing stack trace gives me, I don't see any other exceptions: Tests.Integration_Tests.ExampleRecordServiceIntegrationTests.CreateExampleRecord_Success  Source: ExampleRecordServiceIntegrationTests.cs line 26  Duration: 315 ms

Message:  Assert.Equal() Failure Expected: { "Email": "[email protected]", "FirstName": "Someone", "LastName": "Somewhere", "ExampleId": 42 } Actual: { "Email": "[email protected]", "FirstName": "Someone", "LastName": "Somewhere" }

Stack Trace:  ExampleRecordServiceIntegrationTests.CreateExampleRecord_Success(ExampleRecord request) line 39 --- End of stack trace from previous location ---

Include provider and version information

EF Core version: 6.0.7 Database provider: Microsoft.EntityFrameworkCore.InMemory (for example), Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 6.0 Operating system: Microsoft Windows 10 Pro - 10.0.18363 Build 18363 IDE: Microsoft Visual Studio Professional 2022 (64-bit) - Version 17.1.4

SteepT avatar Aug 05 '22 21:08 SteepT

@SteepT The problem is that EF Core reads and writes directly from/to fields by default. This means that even though the exampleId_ backing field is initialized, the bit indicating it has been initialized has not been set.

image

You can instead tell EF to always read and write using properties:

modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Property);

ajcvickers avatar Aug 08 '22 10:08 ajcvickers

I understand now what's happening, and it is working on my end!

Thank you very much! I sincerely appreciate your response!

SteepT avatar Aug 08 '22 17:08 SteepT