-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Fix InvalidCastException on multi mapping #1167
Conversation
ad50ea3
to
9a3d25c
Compare
While this is one way to fix the problem, it adds extreme overhead converting every value coming back to try a coerce things. The fundamental issue underneath is we're trying to deserialize the wrong type. We either need to return integers for integers, or use longs if we expect longs. The alternative (in the PR here) it to add a lot of overhead to paper over some type mismatches, which are ultimately that: mismatches. In such cases, it's better for us to fix the mismatch, rather than globally incurring penalties on performance for all users. I know that answer may such (as the work has been done here), but I wanted to lay out alternative thinking plainly on tradeoffs here - thoughts? |
I drew on the existing implementation of normal I think the overhead is reasonable because this method was taken to by the original implementation. |
When I specified a primitive type as a type argument of MultiMap and type conversion from the return value of IDataReader.GetValue is required, InvalidOperationException was thrown. This commit fixes this problem by inserting a call to SqlMapper.GetValue.
9a3d25c
to
2fda058
Compare
I have updated my branch and made it use
I think Dapper should convert values to the right type. I will introduce our use cases for multi mapping. Use Case 1: Get a column of joined tablesThe first is to get all columns of a table and a column of joined tables or a calculated value at one time. using Dapper;
using Microsoft.Data.Sqlite;
class Program
{
static void Main(string[] args)
{
using var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
connection.Execute(@"
CREATE TABLE T1 (Id, Value);
CREATE TABLE T2 (Id, Count);
INSERT INTO T1 (Id, Value) VALUES (@id, @value);
INSERT INTO T2 (Id, Count) VALUES (@id, @count);",
new { id = 1, value = "string", count = 1 });
// Get all columns of T1 and some columns or calculated values from joined tables
var tuples = connection.Query(
"SELECT T1.*, T2.Count FROM T1 INNER JOIN T2 ON T1.Id = T2.Id",
(T1 x, int y) => (x, y),
splitOn: "Count");
}
}
public class T1
{
public int Id { get; set; }
public string Value { get; set; }
}
public class T2
{
public int Id { get; set; }
public int Count { get; set; }
} Use Case 2: Check whether there is a record joined by LEFT JOINThe second is to get all columns of a table and check whether joined record is available at one time. using var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
connection.Execute(@"
CREATE TABLE T1 (Id, Value);
CREATE TABLE T2 (Id, Count);
INSERT INTO T1 (Id, Value) VALUES (@id, @value);",
new { id = 1, value = "string" });
// Get all columns of T1 and check whether T2 record is available
var tuples = connection.Query(
"SELECT T1.*, T2.Id IS NOT NULL AS T2Available FROM T1 LEFT JOIN T2 ON T1.Id = T2.Id",
(T1 x, bool y) => (x, y),
splitOn: "T2Available"); In both cases, Overheadhttps://github.com/azyobuzin/DapperMultimapBenchmark The benchmark shows that the more columns we split, the more overhead it takes. In common cases, I think the number of divisions is less than 4 and this overhead is acceptable in common cases. |
We will continue to support conversion for most reasonable things as it does today, e.g. If you need to bring back types, doing so at the SQL level and requiring no conversion on the client will remain the most optimal approach. Going to close this out since the situation hasn't changed here - this just isn't a performance regression we'd want to take. There are more lenient (and less performant) ORM options out there if this behavior is more desired than performance - that's just not in line with the goals of Dapper. |
When I used
Query
with multi-mapping, I found thatInvalidCastException
was thrown when the mapping type is a primitive type for exampleint
,long
,string
, and so on.Here is the reproducing code. MySQL Connector/.NET (ver. 8.0.13) returns the integer values in the
SELECT
statement aslong
. In the test cases ofint
andstring
, the expected behavior is that the method returns values converted to the specified type, but the actual behavior is that the method throwsInvalidCastException
.Output:
This pull request fixes this problem by inserting a call to
Convert.ChangeType
inGenerateMapper
method. And I wrote a test case and it passed on SQL Server 2016 LocalDB.