-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Feature Request: Adding Support for Tuples in DbContext.Database.SqlQuery<T>() #35163
Comments
One limitation of value tuples, is that the field names (Id, Name) aren't preserved, so it wouldn't be possible to match the tuple fields to the columns coming back from the database. We could match positionally though. |
I personally think records are better fit for this type of querying. |
@cincuranet I agree records are a good match here, though they do require an explicit type definition, which is somewhat annoying for an ad-hoc SQL query... On the other hand, positional result binding - which is the only thing we can do with tuples - carries a certain degree of confusion that may make it a pit of failure. For example: var results = context.Database.SqlQuery<(string Bar, string Foo)>("SELECT Foo, Bar FROM TableName"); In the code above, the natural expectation is for Foo on the returned tuple to contain the Foo value from the database, but it will not: since we don't have field names on the tuple, we can only bind positionally, meaning that Bar gets Foo, and Foo gets Bar. Let's discuss this briefly in design. I'm not sure this would be a good idea. |
FWIW there's |
I don't think that works for the generic invocation usage above - just like we can't know about reference nullability of generic parameters... There's nowhere for the compiler to emit |
Right. We're not in a place where it is declared. Pity. More points for records! :D |
Design decision: we won't be supporting this, because of the risk above with positional results binding. Note that it's possible to just project out an anonymous type (or even possibly a tuple), which seems like almost what you want. |
@roji I think developers should and are willing to take this risk, since they decide write raw sql, why we need |
I was interesting to know if (non-Value)
This isn't specific to Tuple, as got the same in a quick Test class: public class Test
{
public string Item1 { get; }
public string Item2 { get; }
public Test(string item1, string item2)
{
Item1 = item1;
Item2 = item2;
}
} I don't know much about constructor binding as usually just work with properties with public setters, but EF does seem to try to allow this, and I'd be interested to know why it doesn't work. I suspect something to do with it being ad-hoc rather than entity types. If it works for entity types but not ad-hoc types, maybe that's something that could be looked at...? |
First, using SQL doesn't mean that the things shouldn't be safe or be a pit of failure. But the important point is that in absolutely all other cases, EF binds result columns by name - this would be the only case where it does so by position, which is why it's risky: it would be very reasonable for users to expect named binding like everywhere else, and then get badly bit by incorrect data. What's your issue with simply projecting out an anonymous type, rather than a tuple? |
@roji how would you do that with |
You're right, I confused APIs there and forgot the shape of SqlQuery. If it's OK to start from a mapped entity type, then FromSql can do this quite well: _ = await context.Blogs.FromSql($"SELECT * FROM Blogs").Select(b => new { ... }).ToListAsync(); Otherwise I'm still quite apprehensive about seeing users do this kind of thing: var results = context.Database.SqlQuery<(string Bar, string Foo)>("SELECT Foo, Bar FROM TableName"); Any thoughts on all this @stevendarby? (regarding constructor binding for SqlQuery specifically, we might not do that currently for that API - open a separate issue?) |
@roji The question is I understood your point, I agree there is inconsistency here. but IMO, developers not babies, they don’t need to be taken care of so carefully, they are obliged to understand some basic knowledge, |
I'll reopen to bring this back to the team for discussion, but I'm still quite skeptical here. As you say, developers are not babies, but our job here is to design APIs which are not pits of failure which lead to unexpected (and silent!) data issues, which is what I'd expect a reasonable developer to fall into (I'm pretty sure I would). Documentation can help to a certain extent, but still doesn't remove the need to design good APIs. |
BTW am noting that Dapper does support mapping tuples/value tuples, although the exact same issue exists there. That could be an indication for us to do it too. |
Don't do this, it will cause data corruption |
@ErikEJ the way I see it, on of the main value propositions of SqlQuery is to allow one-off ad-hoc queries with arbitrary result shapes, i.e. without having a return type definition. Requiring users to define a type (e.g. record) every time they want to do an ad-hoc SQL query seems like quite the annoyance, and exactly the kind of thing this API should solve; and besides, if you're OK with defining a type, you may as well just include it in the model and use FromSql, and don't really need SqlQuery. That, coupled with the fact that Dapper has supported just this thing for quite a long time - and without tons of complaints as far as I can see - makes me think we should consider it... |
Ignore that, same issue with true entity types too when the property types don't have setters. Regarding positional mapping, could you perhaps instead insist on Item1, Item2, etc. naming in the SQL? These are 'stable' properties on |
@stevendarby interesting idea, it might be a good compromise... I'll point out that people regularly have trouble with naming their projection |
I did meet this |
Just to zoom out a little on why exactly Firstly it throws this:
So firstly there's a problem using any value types, not just As an experiment, I took a copy of
I could workaround this by changing the So it'd be interesting to understand why it doesn't work with fields - maybe it's not a big ask to get that working in general? To summarise, if it's at all possible to 'fix'...
... then edit: (1) On second thought, types like |
Thanks for diving into this - let us discuss this in the team and see what's what. |
Design decisions:
Additional context: we considered @stevendarby's proposal of allowing binding via Item1 and Item2. While this is indeed a bit safer than positional binding (less chance of binding incorrectly), the requirement is very undiscoverable (no new user would guess this and would fail, needing to go to the documentation etc.), the required SQL is quite ugly, plus the Dapper example seems to show that the risk of mix-up isn't that problematic (and/or that users discover the error relatively quickly via testing). var results = context.SqlQuery<(int, string)>("SELECT Id AS Item1, Name AS Item2 FROM Foo"); |
Currently, Entity Framework does not support using tuples as the generic type in the
DbContext.Database.SqlQuery<T>()
method. This limitation makes it challenging to execute SQL queries that return columns that do not directly map to entities or predefined models in the code.I would like Entity Framework to support tuples as the generic type in DbContext.Database.SqlQuery(). For example:
This would allow developers to work directly with strongly-typed tuple results, simplifying development and eliminating the need for additional classes to handle temporary or specific query results.
The text was updated successfully, but these errors were encountered: