DapperLib / DapperAOT

Build time tools in the flavor of Dapper
Other
349 stars 19 forks source link

Generated code results in compile error for types that have required properties #71

Closed DamianEdwards closed 8 months ago

DamianEdwards commented 8 months ago

Describe the bug

If I have a type like:

public class Todo
{
    public int Id { get; set; }
    public required string Title { get; set; }
    public bool IsComplete { get; set; }
}

And a method like:

[DapperAot]
public async Task<Todo> DapperAot()
{
    const string sql = """
        INSERT INTO Todos(Title, IsComplete)
        Values(@Title, @IsComplete)
        RETURNING *
        """;
    await using var connection = _dataSource.CreateConnection();
    IDbConnection dbConnection = connection;
    var createdTodo = await dbConnection.QuerySingleAsync<Todo>(sql, _todo);

    return createdTodo;
}

I get a compile error: CS9035 Required member 'Todo.Title' must be set in the object initializer or attribute constructor.

Snippet of generated code that causes the error:

public override global::Todo Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan<int> tokens, int columnOffset, object? state)
{
    global::Todo result = new();
    ...
mgravell commented 8 months ago

great catch!

fixing, but temporary workaround (edit: not required if you update to 1.0.8):

    // don't erase the default .ctor
    public SomeType() {}

    // tell Dapper to use a custom .ctor
    [ExplicitConstructor, System.Diagnostics.CodeAnalysis.SetsRequiredMembers]
    internal SomeType(string title)
    {
        this.Title = title;
    }

we then use deferred construction:

                int value0 = default;
                string? value1 = default;
                bool value2 = default;
                foreach (var token in tokens)
                {
                    // actual parse logic not shown here
                }
                return new global::SomeType(value1)
                {
                    Id = value0,
                    IsComplete = value2,
                };
mgravell commented 8 months ago

https://github.com/DapperLib/DapperAOT/releases/tag/1.0.8

mgravell commented 8 months ago

output from 1.0.8:

                int value0 = default;
                string? value1 = default;
                bool value2 = default;
                foreach (var token in tokens)
                {
                    // actual parse logic not shown here
                }
                return new global::SomeType
                {
                    Id = value0,
                    Title = value1,
                    IsComplete = value2,
                };
mgravell commented 8 months ago

@DamianEdwards entirely unrelated, but: is that RETURNING * npgsql? presumably that's the same as OUTPUT inserted.* on SQL Server?

DamianEdwards commented 8 months ago

Yep 👍

mgravell commented 8 months ago

@DamianEdwards are we doing the same thing in parallel? https://github.com/aspnet/Benchmarks/pull/1930

DamianEdwards commented 8 months ago

Ah! No, I was updating benchmarks on Nanorm 😁

mgravell commented 8 months ago

@DamianEdwards ah, k; tip: you might also want to try using the [CacheCommand] hint - may reduce allocs a little