dapperlib / dapperaot Goto Github PK
View Code? Open in Web Editor NEWBuild time tools in the flavor of Dapper
License: Other
Build time tools in the flavor of Dapper
License: Other
I close this issue
Describe the bug
I am trying to write my code with Dapper.AOT. When a simple query is executed with Oracle provider,
var result = await connection.QueryAsync<string>(
"SELECT DISTINCT MY_ROW FROM MY_VIEW WHERE XN = :arg AND XQM = :xqm",
new { xn, xqm });
...the following exception is thrown:
Unhandled exception. System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
at Oracle.ManagedDataAccess.Client.OracleParameter.set_Size(Int32 value)
at Dapper.AOT.<dapper_aot_repro_231207_generated>F693EEE48F7739B4ABF5C1A3B26A697291B752118758EC20DF1C53DCA2012F7F5__DapperGeneratedInterceptors.CommandFactory0.AddParameters(UnifiedCommand& cmd, Object args) in C:\Users\bs\Code\Tests\dapper-aot-repro-231207\Dapper.AOT.Analyzers\Dapper.CodeAnalysis.DapperInterceptorGenerator\dapper-aot-repro-231207.generated.cs:line 52
at Dapper.CommandFactory`1.Initialize(UnifiedCommand& cmd, String sql, CommandType commandType, T args) in /_/src/Dapper.AOT/CommandFactory.cs:line 195
at Dapper.CommandFactory`1.GetCommand(DbConnection connection, String sql, CommandType commandType, T args) in /_/src/Dapper.AOT/CommandFactory.cs:line 186
at Dapper.AOT.<dapper_aot_repro_231207_generated>F693EEE48F7739B4ABF5C1A3B26A697291B752118758EC20DF1C53DCA2012F7F5__DapperGeneratedInterceptors.CommonCommandFactory`1.GetCommand(DbConnection connection, String sql, CommandType commandType, T args) in C:\Users\bs\Code\Tests\dapper-aot-repro-231207\Dapper.AOT.Analyzers\Dapper.CodeAnalysis.DapperInterceptorGenerator\dapper-aot-repro-231207.generated.cs:line 26
at Dapper.Command`1.GetCommand(TArgs args) in /_/src/Dapper.AOT/CommandT.cs:line 79
at Dapper.Command`1.QueryBufferedAsync[TRow](TArgs args, RowFactory`1 rowFactory, Int32 rowCountHint, CancellationToken cancellationToken) in /_/src/Dapper.AOT/CommandT.Query.cs:line 72
at Dapper.Command`1.QueryBufferedAsync[TRow](TArgs args, RowFactory`1 rowFactory, Int32 rowCountHint, CancellationToken cancellationToken) in /_/src/Dapper.AOT/CommandT.Query.cs:line 98
at Dapper.DapperAotExtensions.AsEnumerableAsync[TValue](Task`1 values) in /_/src/Dapper.AOT/DapperAotExtensions.cs:line 23
at Program.<Main>$(String[] args) in C:\Users\bs\Code\Tests\dapper-aot-repro-231207\Program.cs:line 12
at Program.<Main>$(String[] args) in C:\Users\bs\Code\Tests\dapper-aot-repro-231207\Program.cs:line 12
at Program.<Main>(String[] args)
When I look at the generated code, I see that the Size
property is set to -1
, which caused the exception.
// ...
private sealed class CommandFactory0 : CommonCommandFactory<object?> // <anonymous type: string xn, string xqm>
{
// ...
public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args)
{
var typed = Cast(args, static () => new { xn = default(string), xqm = default(string) }); // expected shape
var ps = cmd.Parameters;
global::System.Data.Common.DbParameter p;
p = cmd.CreateParameter();
p.ParameterName = "xqm";
p.DbType = global::System.Data.DbType.String;
p.Size = -1; // HERE
p.Direction = global::System.Data.ParameterDirection.Input;
p.Value = AsValue(typed.xqm);
ps.Add(p);
// ...
}
// ...
}
// ...
Everything works fine without using Native AOT.
To Reproduce
The reproducible project is located here
Expected behavior
The query works.
Additional context
Note: requires knowing whether we're binding columns via position or name
if binding by name, SELECT columns should be inspected for:
DbString
is a Dapper type that allows metadata to be conveyed for string types.
However:
DbValue]
Suggestions:
[DbValue]
allows the size and type to be conveyed into DbParameter
[DbValue]
insteadDbString
as a recognized type, and configure things correctlyDbString
are encountered) writing a helper method into the output (we have some prior art that copies entire files into the output; that would be fine) with a method that takes a DbParameter
and DbString
(global::
qualified) and does the right things:DbString
is null, just set Value
to DbNull
AsValue
, or a (object)value.Value ?? DbNull
if easierThe reason we can't put this into the library is that we can't ref Dapper directly because of the Dapper/Dapper.StrongName duality - hence we need to implement it in codegen
example
static void Something<T>(...)
{
connection.Query<T>(...)
}
this is not currently supported; there is a plan
I'm getting this warning for query like this:
select HasRows = case when exists (select top 1 1 from MyTable) then 1 else 0 end
I believe the warning tried to prevent other issues, which might not be applicable for this query.
Including async. Caller cannot distinguish no row from empty row. Applies to {First|Single}OrDefault[Async]
examples:
conn.QueryFirst<Customer>($"select * from customers where Id={id}")
conn.QueryFirst<Customer>("select * from customers where Id=" + id)
string
values; I'd like to permit other constant values, but culture rules make that impossible(applies in all SQL scenarios, not just QueryFirst
)
should probably be a warning, category "Security", something like "Data values should not be concatenated into SQL - use parameters instead"; it is hard to offer an auto rewrite here because the specific parameter syntax is provider specific - but we might know the provider (we have some stuff for that already; that's a distant second to spotting it, though)
Describe the bug
When using SqlConnection
and querying data using the built-in MSSQL-function DATEADD
parameter datepart
is falsely identified as a table column causing a false DAP226
warning when used in a query with joins.
Dapper.Advisor: 1.0.10
To Reproduce
// DAP226: FROM expressions with multiple elements should qualify all columns; it is unclear where 'YEAR' is located
public static async Task DateAddWithJoin(SqlConnection connection) =>
await connection.QueryAsync(
"""
SELECT DATEADD(YEAR, 1, t.Year) AS NextYear
FROM MyTable t
JOIN MyOtherTable o ON o.Id = t.Id
"""
);
It's good project, but there's a question : 'Is DapperAOT used in the StackOverflow?'
Or it's experimental tool?
<LangVersion>11</LangVersion>
(or higher)<Features>interceptors</Features>
2 separate diagnostics; also, do not emit anything if langver is too low (currently: we do)
note: it should only emit these if at least one Dapper call is detected - suggest adding a new flag that is AotNotEnabled
, and all (non-zero) are AotNotEnabled
: do the thing; perhaps implement as optional , flags = None
on the simple ctor, and this flags = flags | DoNotGenerate
Is there an update on the status of this project?
context: https://twitter.com/IanStockport/status/1702313925810024716
imagine a "dotnet tool":
dapper schema {conn-key}
which:
conn-key
dbschema.txt
Where dbschema.txt
could be something like (is there prior art here? plain text?):
(note on order: objects are invariant alphabetical; columns/parameters/etc are positional)
default schema dbo
server version 16.0.1000.6
table dbo.Foo
column Id int notnull identity readonly
column Name string
column Label string computed readonly
table someSchema.Bar
column Id int notnull identity readonly
view someView
column Id notnull null
...
we would then load the dbschema.txt
and use it to validate commands at build time, without needing constant SQL access. Schema should be implied via "default schema" when omitted.
Possible checks:
sp_helptext
-etc source and parse that too?)Rewriting the db schema would be a case of re-running the tool, with the changes visible in source control.
Note: advanced SQL analysis is currently limited to SQL Server; we should probably at least not assume that when connecting (use the provider model), but... whatever works.
Describe the bug
When using an aggregation function e.g. MIN/MAX an a full table a false DAP231
warning is given
Dapper.Advisor: 1.0.10
To Reproduce
// DAP231: SELECT for single row without WHERE or (TOP and ORDER BY)
public static async Task<int> SelectMax(SqlConnection connection) =>
await connection.QueryFirstOrDefaultAsync<int>("SELECT MAX(SomeColumn) FROM MyTable");
Describe the bug
When building a sample application using minimal APIs and top level statements (sample linked below), it seems like the source generation isn't working as expected. Even though adding the [module:DapperAot]
, compiling produces Warning DAP005 : 1 candidate Dapper methods detected, but none have Dapper.AOT enabled
, and running the application results in the expected errors, due to not being able to use dynamic code generation under Native AOT.
If I move the code to a static class and map the endpoint, then the warning disappears and everything works as expected.
I imagine this isn't a very relevant issue, as in non-sample code, it's unlikely to have queries in top level statements, but just in case it's an unknown issue (or maybe I'm using it wrong), I thought of reporting.
Where are you seeing this?
To Reproduce
Sample code
Note: out of the box this code works, because the direct route to code is commented out and replaced with a static class, but reverting things, we can see the reported issue.
Expected behavior
Source generation works regardless of where the queries are used.
However, if supporting this scenario is not worth the effort, maybe include it as a known limitation in the docs.
Screenshots
If applicable, add screenshots to help explain your problem.
Additional context
Add any other context about the problem here.
Describe the bug
When using SqlConnection
and selecting data from one table into another using ExecuteAsync
a false DAP025 warning is triggered.
Dapper.Advisor: 1.0.10
To Reproduce
// DAP025: The command has a query that will be ignored
public static async Task SelectInto(SqlConnection connection) =>
await connection.ExecuteAsync("SELECT [Test] INTO #MyTemporaryTable FROM MyTable");
Describe the bug
The AsyncAccessorDataReader never seems to take the Current value from the IAsyncEnumerator and set it's own Current value with it, when ReadAsync is called. This means that as soon as the SqlBulkCopy tries to get a value for the first row, it encounters a null value, triggering a NullReferenceException.
Where are you seeing this?
To Reproduce
IAsyncEnumerable<Customer> customers = ...
var reader = TypeAccessor.CreateDataReader(customers);
using var table = new SqlBulkCopy(connection);
table.EnableStreaming = true;
table.WriteToServer(TypeAccessor.CreateDataReader(customers,
[nameof(Customer.Name), nameof(Customer.CustomerNumber)]));
We already support annotated custom constructors when they're accessible to the generator. We can extend that.
Note: only impacts private
and protected
constructor usage; all others should continue using direct
This can be implemented acceptably using reflection (ideally optimized via Expression
) or [UnsafeAccessor]
(net8+ only); example
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
static class P {
static void Main()
{
var obj = CreateBar(42);
Console.WriteLine(obj.A);
obj = CreateBar(96);
Console.WriteLine(obj.A);
}
#if NET8_0_OR_GREATER
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
static extern Bar CreateBar(int a);
#else
private static Func<int, Bar>? s_CreateBar;
static Bar CreateBar(int a) => SomeUtilityHelper.GetConstructor(ref s_CreateBar)(a);
#endif
}
static class SomeUtilityHelper // in DapperAOT - maybe in RowFactory?
{
public static TDelegate GetConstructor<TDelegate>(ref TDelegate? field) where TDelegate : Delegate
{
return field ?? SlowCreate(ref field);
static TDelegate SlowCreate(ref TDelegate? field)
{
var signature = typeof(TDelegate).GetMethod(nameof(Action.Invoke));
if (signature?.ReturnType is null || signature.ReturnType == typeof(void))
{
throw new InvalidOperationException("No target-type found");
}
var methodArgs = signature.GetParameters();
var argTypes = Array.ConvertAll(methodArgs, p => p.ParameterType);
var ctor = signature.ReturnType.GetConstructor(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, argTypes);
if (ctor is null)
{
throw new InvalidOperationException("No suitable constructor found matching "
+ string.Join<Type>(", ", argTypes));
}
var args = Array.ConvertAll(methodArgs, p => Expression.Parameter(p.ParameterType, p.Name));
field = Expression.Lambda<TDelegate>(Expression.New(ctor, args), args).Compile();
return field;
}
}
}
class Bar
{
private readonly int x;
public int A => x;
private Bar(int a) => x = a;
}
Describe the bug
Code which ran perfectly with RC1 of .NET 8 does not compile any more. Getting CS9206 'An interceptor cannot be declared in the global namespace' compiler errors.
To Reproduce
Download the repository and compile the UsageLinker
and UsageBenchmark
projects. They don't build with RC2
Expected behavior
No error, worked fine with RC1
Additional context
Using Visual Studio 2022 Version 17.8.0 Preview 4.0
Example:
public async Task<List<Symbol>> GetAllSymbolsVariable()
{
var sql = "select * from Symbol";
var results = await Conn().QueryAsync<Symbol>(sql); // no warning
return results.AsList();
}
public async Task<List<Symbol>> GetAllSymbolsInline()
{
var results = await Conn().QueryAsync<Symbol>("select * from Symbol"); // has warning
return results.AsList();
}
Describe the bug
Build error :
"The type 'InterceptsLocationAttribute' exists in both 'test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' and 'test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
is thrown for simple dotnet8 console project.
Where are you seeing this?
To Reproduce
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
using (var tran = await connection.BeginTransactionAsync(IsolationLevel.Snapshot))
{
var returned= await connection.QueryAsync<Class>(Query, transaction: tran);
}
}
Expected behavior
Soultion builds with no error.
Screenshots
Additional context
"InterceptorsPreviewNamespaces" csproj element exists in PropertyGroup
Dotnet version:
dotnet --info
.NET SDK:
Version: 8.0.100
Commit: 57efcf1350
Workload version: 8.0.100-manifests.71b9f198
There are two parameter models in Npgsql - ordinal and nominal; we use nominal, but the ordinal API is much more efficient; we could ingest nominal const sql and rewrite it as ordinal
this should include splitting semi-colon multi-statements into DbBatch
when possible
tasks:
UnifiedBatch
, should work with/without DbBatch
API)The join us effectively a restriction UNLESS it is the "always" side of an outer join
Basic tier: omit for JOIN
Middle tier: omit for INNER JOIN
Top tier: omit if not the weak side of OUTER
I tried to set DbType using DbValue attribute for parameters and looks like it is not use DbType property
[DapperAot(enabled: true)]
public static class UsersSqlQieries
{
public sealed class UserIncrementParams
{
[DbValue(Name = "userId")]
public int UserId { get; set; }
[DbValue(Name = "date", DbType = System.Data.DbType.Date)]
public DateTime Date { get; set; }
}
public static async Task IncrementAsync(DbConnection connection, int userId)
{
var date = DateTime.Today;
await connection.ExecuteAsync("""
UPDATE [dbo].[table]
SET [column] = ([column] + 1)
WHERE [Id] = @userId and [Date] = @date
""", new UserIncrementParams() { UserId = userId, Date = date });
}
}
and generated code:
file static class DapperGeneratedInterceptors
{
[global::System.Runtime.CompilerServices.InterceptsLocationAttribute("....cs", 25, 30)]
internal static global::System.Threading.Tasks.Task<int> ExecuteAsync4(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType)
{
// Execute, Async, HasParameters, Text, KnownParameters
// takes parameter: global::App.Services.CompiledQueries.Sql.UsersSqlQieries.UserIncrementParams
// parameter map: Date UserId
global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql));
global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text);
global::System.Diagnostics.Debug.Assert(param is not null);
return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory2.Instance).ExecuteAsync((global::App.Services.CompiledQueries.Sql.UsersSqlQieries.UserIncrementParams)param!);
}
}
private sealed class CommandFactory2 : global::Dapper.CommandFactory<global::App.Services.CompiledQueries.Sql.UsersSqlQieries.UserIncrementParams>
{
internal static readonly CommandFactory2 Instance = new();
public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::App.Services.CompiledQueries.Sql.UsersSqlQieries.UserIncrementParams args)
{
var ps = cmd.Parameters;
global::System.Data.Common.DbParameter p;
p = cmd.CreateParameter();
p.ParameterName = "userId";
p.DbType = global::System.Data.DbType.Int32;
p.Direction = global::System.Data.ParameterDirection.Input;
p.Value = AsValue(args.UserId);
ps.Add(p);
p = cmd.CreateParameter();
p.ParameterName = "date";
p.DbType = global::System.Data.DbType.DateTime; //problem is here, DbType from DbValueAttribute not used
p.Direction = global::System.Data.ParameterDirection.Input;
p.Value = AsValue(args.Date);
ps.Add(p);
}
public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::App.Services.CompiledQueries.Sql.UsersSqlQieries.UserIncrementParams args)
{
var ps = cmd.Parameters;
ps[0].Value = AsValue(args.UserId);
ps[1].Value = AsValue(args.Date);
}
public override bool CanPrepare => true;
}
As I understood the problem in this method
Should it be?
public DbType? GetDbType(out string? readerMethod)
{
var dbType = IdentifyDbType(CodeType, out readerMethod);
if (TryGetAttributeValue(_dbValue, "DbType", out int explicitType, out bool isNull))
{
var preferredType = isNull ? (DbType?)null : (DbType)explicitType;
if (preferredType != dbType)
{ // only preserve the reader method if this matches
readerMethod = null;
dbType = preferredType; //added line
}
}
return dbType;
}
Hello
I'm trying DapperAOT
with the new interceptor mode and I spotted an issue that occurs only in async mode.
The problem occurs in Command<TArgs>.QueryBufferedAsync
when invoking await state.ExecuteReaderAsync(...)
.
This method is supposed to assign the Reader field of the QueryState
struct. However, it seem that the struct is copied (by value) during the async call, causing the reader to be set on a separate instance. When the execution returns to Command<TArgs>.QueryBufferedAsync
, the original QueryState
remains unchanged, and the Reader field stays null.
I attempted to modify QueryState from a struct to a class (and replaced all instances of QueryState state = default; with QueryState state = new();), and it worked.
Although I'm not expert enough to resolve this issue on my own, I hope my findings will be helpful to you.
Main branch commit 9070118
Sorry for my (ChatGPT enhanced) English
I have seen that the code is obviously not constructed the same as normal dapper
I have tried this with the [DapperAot] attribute over my model
var programs = new List();
var con = new SqlConnection(_connectionString);
con.Open();
var list = new List<Program>();
var test = con.Command<Program>("SELECT Name, Acronym FROM Programs");
var test2 = test.Execute(programs);
but it returns nothing, there is no "Query method off of sql connection like normal Dapper. How doo i get this to work on just very basic simple terms. Can you please give a ver basic code example of getting DapperAot to work, there is nothing in the readme
The properties FirstName
and First_Name
are ambiguous. Dapper (core, haven't checked AOT) prefers properties over fields, but there was a glitch where ambiguous properties could get conflated.
We should warn if:
We should also verify that AOT prefers properties over fields (this isn't a warning: this is a library requirement)
This analyzer applies with+without AOT enabled.
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();
...
Right now, the materializer is akin to:
TheType obj = new();
while (...)
{
switch (...)
{
case 4223423:
obj.Foo = /* read the value */
break;
}
}
return obj;
This only works for trivial construction; it does not support more complex scenarios:
A custom constructor (at most one per type) is identified by:
[DapperAot(false)]
[DapperAot]
/[DapperAot(true)]
[DapperAot]
/[DapperAot(true)]
, a generator error is emitted and no constructor is selected[DapperAot]
/[DapperAot(true)]
, it is selectedIf a custom constructor is selected, the parameters are checked against the member list using normalized / insensitive rules; if any paired members do not match field type (question: nullability?), a generator error is emitted and the constructor is deselected
Init-only properties are any non-static properties that have the init
modifier, for example public string Name { get; init; }
In any of these scenarios, we must collect the values into locals, and defer construction; I would propose simply value0
, value1
, etc where the index is the position of the discovered member; each should be assigned default
, awaiting values from the database; we then collect fields into these variables instead of the object:
int value0 = default;
string? value1 = default;
DateTime value2 = default;
while (...)
{
switch (...)
{
case 4223423:
value1 = /* read the value */
break;
}
}
// construction not shown yet
The construction step depends on whether a custom constructor is selected, and whether the parameters for such custom constructor covers all members; there are 3 possible outcomes:
return new TheType(value0, value2, value1);
(noting that the parameter order does not necessarily match our declaration order, so it is not necessarily strict order)
init
-only properties)return new TheType(value0, value2)
{
Foo = value1,
};
init
-only property)return new TheType
{
Foo = value1,
};
Implementation notes:
DRY:
TheType obj = new()
line separate, for simplicity; see †)obj.TheMember =
vs value42 =
†: the 4th quadrant in that with/without matrix is: simple construction, so: already handled - i.e. without constructor, everything is additional members, none of which are init
-only - the difference being that in that simple scenario, we're not using the stack to gather the values - we're pushing them directly onto the object
I have a Roslyn Source Code Generators examples with code on GitHub - https://github.com/ignatandrei/rsCG_examples
The code is pretty simple , but it gives the error
System.NullReferenceException: Object reference not set to an instance of an object.
The line is
__dapper__result = global::Dapper.TypeReader.TryGetReader<global::DapperExampleDAL.Person[]>()!.Read(__dapper__reader, ref __dapper__tokenBuffer);
You can find the example at https://github.com/ignatandrei/rsCG_examples , folder later_aotdapper\src\dapperexample
Please help
Currently we support names (parameter and column) via [DbValue]
, however there is also a well-known [System.ComponentModel.DataAnnotations.Schema.ColumnAttribute]
that is commonly used.
I'm hesitant to use this blindly, because that would change existing behavior, so this should be triggered via a new UseColumnAttributeAttribute
(yes, the naming is weird - see also XmlAttributeAttribute
) which would apply at all levels (like most DapperAOT attributes):
[UseColumnAttribute]
or [UseColumnAttribute(true)]
is in play[UseColumnAttribute(false)]
explicitly disablesName
would be used; we aren't interested in Order
etc[Column(...)]
with a Name
but don't see any explicit on/off flag, emit a warning so the user knows to opt in/out[DbValue]
with Name
, but: doesn't impact parametersSee also https://stackoverflow.com/questions/77073286/can-dapper-mappings-be-automated-with-attributes
Right now when using a base class for your entities like this
public abstract class EntityBase
{
public long Id { get; set; }
}
public class Entity1 : EntityBase
{
public string Name { get; set; }
}
Then the Id
property will not be read by the code generated.
It only works when putting the Id directly into Entity1
(otherwise it never gets mapped, always default 0 for any query). I think it´s fairly common to want to enforce consistent naming of ids and timestamps. Although, this can be enforced with interfaces too I suppose (doing that now instead).
The above should either work, or a the analyzer should give you a warning about this (in case this is not gonna be implemented anytime soon). I don´t yet know enough about compile time generators to know how hard it is to figure out that there is a base class and walk back up the class tree.
Fixed a couple of highlighted errors, rebuilt the code and the red underlining of the sql query string is still visible.
Note: Errors are gone from Output & Error List windows.
First of all, Thank you very very much @mgravell for your hugely-appreciated efforts.
.NET would not be the same without you.
This library is very great and very important, and I can not stop introducing my friends and colleagues to it.
I would just suggest adding benchmarks to the documentation to show how important this library is.
It will be the easiest way for people to understand its importance by seeing how faster and less memory this approach provides.
Thank you a lot again
Using something similar to the following causes a couple of DAP214 variable not declared errors, when infact the code works as expected (results returned):
var parameters = new DynamicParameters();
parameters.Add("@StartDate", "2023-06-01");
parameters.Add("@EndDate", "2023-06-30");
When I switch to an anonymous type for the parameters everything is fine - no errors.
var parameters = new
{
parameters.Add("@StartDate", "2023-06-01").
parameters.Add("@EndDate", "2023-06-30")
}
I tried to setup one of the ASP.NET Core benchmarks app's (this one) to use Dapper.AOT but it appears to be causing a stack overflow with lots of the following printed to output during build:
C:\Program Files\dotnet\sdk\8.0.100\Roslyn\Microsoft.CSharp.Core.targets(84,5): error : at Dapper.Internal.Inspection.InvolvesTup
leType(Microsoft.CodeAnalysis.ITypeSymbol, Boolean ByRef) [D:\src\GitHub\aspnet\Benchmarks\src\BenchmarksApps\TodosApi\TodosApi.cspr
oj]
Visual Studio freezes and then disappears.
To Reproduce
Project that I was updating is: https://github.com/aspnet/Benchmarks/tree/damianedwards/dapper.aot/src/BenchmarksApps/TodosApi
Expected behavior
Expect it to work.
Screenshots
See error above.
Additional context
none
Very close to #28, but with different nuances. Example:
var obj = Foo.Create(value0, value2, value1);
obj.Blah = value4;
things like Query<int>
end up emitting a row-reader with a broken outdent level
new tests suggested:
Query<int>
Query<int?>
Query<string>
Query<string?>
Query<DbGeography>
Query<DbGeography?>
I have thought of generating the parser/materializers AOT as well for quite some time even more with the rise of Source Generators. However I was never able to figure out how one would implement it. Therefor I am curious on how you would solve this, especially in terms of the configuration.
SQL string looks something like "... WHERE [DividendId] IN @ids ORDER BY [SecId];"
This caused error DAP206 Incorrect syntax near @ids, but the code executes as expected without having the brackets around the IN parameter,
Should this be an Error or a Warning - if the code still works, it feels like a Warning, and the correction an advisory.
The incremental generator is stashing parts of the semantic model; it must not - we should create custom types (struct records? value tuples? must have equality support) and fully expand everything during parse, so we never store anything except our own data
for example, see #48
Add
usage other than just name+value: don't warnit comes to my mind the Project refit https://github.com/reactiveui/refit.
hoping the 'command' can auto-generate too.
Currently only fully public/internal/"protected internal" types can be processed; if a type is private/protected/"private protected", then emit will fail
It would be nice if a non-zero number of Dapper methods detected, but zero marked [DapperAot], resulted in a single message of the form (assuming pre-reqs met): "Dapper.AOT found candidate methods, but is not enabled at any level; consider adding [DapperAot] at the method, type, module or assembly level"
See DapperLib/Dapper#1914 and DapperLib/Dapper#1971
In analyzer mode (not in generator mode), we should be able to detect the following queries as problematic, i.e. the true parameters are not the same as Dapper is going to assume:
select 'this ? looks like OLE DB'
and
select 'this ?looks? like pseudo-positional
The problem here is that the runtime SQL parser in Dapper detects both of these as meaning the wrong thing; they aren't parameters. Our existing parameter handler in TSQL should be able to detect this.
The following create statement with an Enum in the class will fail with Invalid cast from 'System.Int64'.
I´m using sqlite, the query that fails looks like this
public async Task<long> CreateAsync(AppUser newUser)
{
await using var connection = new SqliteConnection(settings.ConnectionString);
const string sql =
$"""
INSERT INTO AppUser
(Username, Password, UserRole) VALUES
(
@{nameof(AppUser.Username)},
@{nameof(AppUser.Password)},
@{nameof(AppUser.UserRole)}
) RETURNING *;
""";
var insertedRows = await connection.QueryAsync<AppUser>(sql, newUser);
return insertedRows.FirstOrDefault()?.Id ?? -1;
}
The Enum is mapped to an INTEGER column in Sqlite. Without AOT the mapping works just fine.
Adding a TypeHandler like this does not seem to work either
internal class UserRoleConverter : SqlMapper.TypeHandler<UserRole>
{
public override UserRole Parse(object value)
{
// theoretically this should fix it as casting to enums only works if it´s int32 afaik, but it never gets called
var int32Value = Convert.ToInt32(value);
return (UserRole) int32Value;
}
public override void SetValue(IDbDataParameter parameter, UserRole value)
{
parameter.Value = (int)value;
}
}
// Registered on startup
SqlMapper.AddTypeHandler(new UserRoleConverter());
I am aware that this query in particular parsing the enum would be avoidable entierly, because I only need the Id back, but this would just be a fix for this one query and it would still be an issue for virtual everything else. It just happens to be the first thing that runs in virtually every single test I have.
There is this issue for regular dapper DapperLib/Dapper#259
So, does that mean if dapper AOT does not handle enums out of the box it just won´t be possible to make it work? I could probably work around this with a class that imitates enums and some explicit converters, but I would rather not.
Tried to create a simple wrapper class like this
public class UserRoleColumn(UserRole userRole)
{
public UserRole Value { get; set; } = userRole;
}
with this type converter
public class UserRoleColumnConverter : SqlMapper.TypeHandler<UserRoleColumn>
{
public override void SetValue(IDbDataParameter parameter, UserRoleColumn? value)
{
ArgumentNullException.ThrowIfNull(value);
parameter.Value = (int) value.Value;
}
public override UserRoleColumn? Parse(object value)
{
var userRole = (UserRole) Convert.ToInt32(value);
return new UserRoleColumn(userRole);
}
}
Also does not work throws No mapping exists from object type UserRoleColumn to a known managed provider native type
in the same place.
On further inspection SqlMapper.AddTypeHandler
is not yet AOT friendly. It breaks on publish so wether it´s used or not by the code generated does not even matter because you can´t register them.
In a few Dapper SQL queries, I have query level parameters which are used as variables inside the query scope and don't require any values to be passed in as arguments to the Dapper in methods like QueryAsync.
DECLARE @ActiveWidgets TABLE (Id int)
INSERT @ActiveWidgets SELECT Id FROM Widgets WHERE Active = 1
This is raising the error DAP019 SQL parameters were detected, but no parameters are being supplied
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.