Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
DamianEdwards opened this issue Nov 15, 2023 · 8 comments

Comments

@DamianEdwards
Copy link

DamianEdwards commented Nov 15, 2023

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
Copy link
Member

mgravell commented Nov 15, 2023

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
Copy link
Member

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

@mgravell
Copy link
Member

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
Copy link
Member

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

@DamianEdwards
Copy link
Author

Yep 👍

@mgravell
Copy link
Member

@DamianEdwards are we doing the same thing in parallel? aspnet/Benchmarks#1930

@DamianEdwards
Copy link
Author

Ah! No, I was updating benchmarks on Nanorm 😁

@mgravell
Copy link
Member

mgravell commented Nov 15, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants