dj-nitehawk / MongoDB.Entities

A data access library for MongoDB with an elegant api, LINQ support and built-in entity relationship management
https://mongodb-entities.com
MIT License
544 stars 70 forks source link

Bson deserializing of objectid error #225

Closed cphillips83 closed 2 months ago

cphillips83 commented 3 months ago

I was just going through the fast endpoints example that used monogdb entities and can't get past this error

MongoDB.Bson.BsonSerializationException: 'Array' values are not valid on properties decorated with an [AsObjectId] attribute!
   at MongoDB.Entities.AsObjectIdAttribute.ObjectIdSerializer.Deserialize(BsonDeserializationContext ctx, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
   at MongoDB.Bson.Serialization.Serializers.ValueTupleSerializer`2.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
   at MongoDB.Driver.Linq.Linq3Implementation.Serializers.WrappedValueSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
   at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
   at MongoDB.Driver.Core.Operations.CursorBatchDeserializationHelper.DeserializeBatch[TDocument](RawBsonArray batch, IBsonSerializer`1 documentSerializer, MessageEncoderSettings messageEncoderSettings)
   at MongoDB.Driver.Core.Operations.FindOperation`1.CreateFirstCursorBatch(BsonDocument cursorDocument)
   at MongoDB.Driver.Core.Operations.FindOperation`1.CreateCursor(IChannelSourceHandle channelSource, IChannelHandle channel, BsonDocument commandResult)
   at MongoDB.Driver.Core.Operations.FindOperation`1.ExecuteAsync(RetryableReadContext context, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Operations.FindOperation`1.ExecuteAsync(IReadBinding binding, CancellationToken cancellationToken)
   at MongoDB.Driver.OperationExecutor.ExecuteReadOperationAsync[TResult](IReadBinding binding, IReadOperation`1 operation, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.ExecuteReadOperationAsync[TResult](IClientSessionHandle session, IReadOperation`1 operation, ReadPreference readPreference, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
   at MongoDB.Entities.Find`2.ExecuteSingleAsync(CancellationToken cancellation)
   at Users.Login.Endpoint.ExecuteAsync(Request req, CancellationToken ct) in C:\Users\cphillips83\Documents\GitHub\AMP\Applications\MindMatrix.Applications.AdminPortal\Features\Users\Login\Endpoint.cs:line 16
   at FastEndpoints.Endpoint`2.ExecAsync(CancellationToken ct)
   at FastEndpoints.Endpoint`2.ExecAsync(CancellationToken ct)
   at NSwag.AspNetCore.Middlewares.SwaggerUiIndexMiddleware.Invoke(HttpContext context)
   at NSwag.AspNetCore.Middlewares.RedirectToIndexMiddleware.Invoke(HttpContext context)
   at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

This is the class in question and the error comes from AsObjectId deserialization, it hits and else and complains that the data type is an array.


public class User :  Entity
{
    public record Create(string UserName, string Email, string PasswordHash)
    {
        public bool IsAdmin { get; set; }
    }

    public User(Create create)
    {
        UserName = create.UserName;
        Email = create.Email;
        PasswordHash = create.PasswordHash;
        IsAdmin = create.IsAdmin;
    }

    public User()
    {

    }

    public string UserName { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
    public bool IsAdmin { get; set; } = false;

}

I switched to ObjectIdEntity and no longer have this issue.

Here is the example of the mongo 7 db data...

admin_portal> db.User.find()
[
  {
    _id: ObjectId("66b2b0e1867a2c4b82dbcfce"),
    UserName: 'test',
    Email: 'test@test.com',
    PasswordHash: '$2a$11$5.QQ4wAfVuCBitm0EwQS4uS/P.vxjsY.Q.1R2W6ramYhME6gSCq7S',
    IsAdmin: true
  }
]

Error is thrown from default:

        public override string Deserialize(BsonDeserializationContext ctx, BsonDeserializationArgs args)
        {
            switch (ctx.Reader.CurrentBsonType)
            {
                case BsonType.String:
                    return ctx.Reader.ReadString();
                case BsonType.ObjectId:
                    return ctx.Reader.ReadObjectId().ToString();
                case BsonType.Null:
                    ctx.Reader.ReadNull();
                    return null;
                default:
                    throw new BsonSerializationException($"'{ctx.Reader.CurrentBsonType}' values are not valid on properties decorated with an [AsObjectId] attribute!");
            }
        }
dj-nitehawk commented 3 months ago

can't seem to reproduce it. can you update the following project and send back pls? Repro.zip

dj-nitehawk commented 3 months ago

could it be possible that your database has some entity which has an array value for the _id field?

cphillips83 commented 3 months ago

I haven't had time to review this further because someone decided to destroy a 9 node k8s cluster.

There really isn't anything special here as its boiler plate code, the code that creates this single record is done from _001_seed_initial_admin_account.cs with

namespace Migrations;

public class _001_seed_initial_admin_account : IMigration
{
    internal static string SuperAdminPassword { get; set; }

    public async Task UpgradeAsync()
    {
        Console.WriteLine("Creating default super admin");
        var user = new Entities.User.Create("test",
                                            "test@test.com",
                                            BCrypt.Net.BCrypt.HashPassword(SuperAdminPassword))
        {
            IsAdmin = true
        };
        await new Entities.User(user).SaveAsync();
    }
}

Here is the csproj file as well

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
    <!-- <PublishAot>true</PublishAot> -->
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
    <PackageReference Include="FastEndpoints" Version="5.28.0" />
    <PackageReference Include="FastEndpoints.Security" Version="5.28.0" />
    <PackageReference Include="FastEndpoints.Swagger" Version="5.28.0" />
    <PackageReference Include="FluentValidation" Version="11.9.2" />
    <PackageReference Include="MongoDB.Entities" Version="23.0.0" />
  </ItemGroup>

</Project>

Tomorrow I will try to take your sample project and reproduce it.

Note: I have tried this code repeatedly with a drop database, this is the only collection and record.