-
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: LINQ to table parameter #13239
Comments
Anything I can do to help this along? I'd be happy to put together a PR if you point me in the general direction of where you'd want to see this (assuming it fits with your direction for the project). |
Hi @awr your issue was assigned to me as investigation in 2.2 with the idea that I would do some thinking to try to decide if this was a duplicate of an existing issue or if there was something in your proposal that we wanted to pursue. I did a search and I actually couldn't find an active issue in our backlog that covers using TVPs in this way. We have certainly done some thinking about using TVPs as an implementation detail for things like public static async Task<List<Person>> GetWithTableParameterAsync(
IEnumerable<long> ids,
CancellationToken cancellationToken)
{
using (var db = new MyDbContext()) {
var query =
from person in db.People
where ids.Contains(person.PersonId)
select person;
return await query.ToListAsync(cancellationToken);
}
} However, there some challenges with doing this automatically. One of them is the fact that SQL Server requires table types to be declared in the database before you can use them in a TVP. Hence we have come up with other alternative implementations that presumably would have lower impact, like #12777. But I believe what you are proposing is really interesting. On one hand it could be used to solve the same scenario above, but instead of I think I have been able to come up with two orthogonal new capabilities that we could add which I think would generalize the idea even more:
Assuming we had these capabilities, your example could look something like this: public class MyDbContext : DbContext
{
// ... or however it makes sense to define a udt
public DbQuery<LongRow> LongRows { get; set; }
override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<LongRow>().ForSqlSserverDeclareTableType();
}
}
public class LongRow
{
public long Id { get; set; }
}
public static class Execution
{
public static async Task<List<Person>> GetWithTableParameterAsync(
IEnumerable<long> ids,
CancellationToken cancellationToken)
{
using (var db = new MyDbContext()) {
var query = from person in db.People
join id in db.LongRows.FromData(ids => ids.Select(id => new LongRow {Id = id})))
on person.PersonId equals id.Id
select person;
return await query.ToListAsync(cancellationToken);
}
}
} Of course we could also have sugar to make this more terse, like an attribute on the type or DbSet or DbQuery property to automatically create the table type, and a version of |
Note for triage: I think this belongs in the backlog for now, but since @awr is interested in contributing we can discuss the design in general terms. |
Updated my previous comment to reflect that client evaluation isn't an option anymore after 3.0. |
I think the syntax should be more close to that of Set() something along the lines of
By conventon EF could use a table value paramater type named |
If EF core eventually support insert / delete / update directly on IQueryables you could even do
If we extend the table value parameter mechanics to support custom entity types not just simple value types we could even bulk insert with a crazy high level of performance. Something like.
Possibilities are endless :D |
This issue need more love from the ef core team. |
Rather than using a temp table or a TVP, couldn't this translate to joining either a CTE or a VALUES list? for example:
or:
My understanding is also that MS SQL's TVP implementation is a bit more specific to them, while WITH and VALUES are more generally supported (so hopefully more work could be re-used across providers) |
@hauntingEcho both of the above suggestions involve embedding constant values in SQL, meaning that you get different SQLs for different values. That causes query plan fragmentation, which is bad for performance, and the thing that the OP tried to avoid (see above). Note that the queries above are mainly about selecting rows with IDs in a given list. For this case specifically (which is usually express via LINQ Contains and translated to SQL IN), see #13617 which discusses some optimization techniques which are cross-database. |
@roji that makes sense. I may have misread this ticket as being primarily about server-side joins on input data (with a bonus of good query plans from TVP) rather than primarily about the query plan on single-column raw data filters. My queries had only used a single column to simplify the examples. |
Just stumbled upon this issue. I would love to have a robust, built-in solution for this problem. However, until then, I also wanted to share an EF Core extension I found that accomplishes a similar task: EntityFramework.MemoryJoin (for anyone else who needs an immediate solution). You basically just add a dedicated By default, they have a I haven't successfully run a query that supplies multiple lists in a single request so not sure if that's supported or if I'm just doing things incorrectly. At least gives a general idea of how something like this could be implemented though. |
sadly it does not seems to make it easy to add new type mapping like Nodatime or Postgres Enums. Would also love to see a robust solution for this problem |
@erwan-joly how do you see this as relevant for NodaTime or PG enum support? Both these are already natively supported by the PostgreSQL provider etc. |
I think I was unclear, what I meant was the other way around: it would be nice if the proposed solution was supporting those (and any type supported by the provider) not that the support of those types would change in anyway with such a solution. The suggested solution EntityFramework.MemoryJoin does not work with those types and also create a new set of parameters at every run so it will always get a new query plan. It only support a small subset of types: https://github.com/neisbut/EntityFramework.MemoryJoin/blob/master/src/EntityFramework.MemoryJoin/Internal/MappingHelper.cs#L321 it would be nice to be able to join with an in memory list for any supported type in the provider. And I believe having this in EF would result in each provider being able to add the support on their side with things like ‘select * from table t |
Everyone, EF 8.0 is introducing full support for primitive collections, which very much addresses what has been discussed above (see this blog post). Rather than using a SQL Server TVP (Table-Valued Parameter), we've gone with JSON arrays as the way to represent arrays. For example, consider the following simple LINQ query using Contains: var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync(); EF Core 7.0 translated this as follows: SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2') The new 8.0 translation: Executed DbCommand (49ms) [Parameters=[@__names_0='["Blog1","Blog2"]' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (
SELECT [n].[value]
FROM OPENJSON(@__names_0) WITH ([value] nvarchar(max) '$') AS [n]
) This allow the same query plan to be reused regardless of parameters. And in addition, any LINQ operators can now be composed over the array parameter, not just Contains.
So unless I'm mistaken, there's no longer a reason for this issue to be open; unless someone can come up with a specific reason why TVPs should be used (as opposed to a JSON array), EF 8.0 seems to already resolve the issues described here. |
Seems perfect 🎉 |
Use Case: Ability to use query against a variable number of inputs, for a batching scenario. Ideally I'd like to have the same query plan used whether there are 2 input values or 100 input values, in conjunction with a linq query.
Hypothetical linq:
Ideally this generates t-sql that looks something like:
Note that I realize I can already do something similar with a
Contains
-- something like:The difference is in the sql that gets generated, since it embeds the id values into the query (or if using an expression tree walker, I believe it's possible to change this to have n equality checks with sql parameters). I'd prefer a single query plan to either of these solutions.
I also realize that it's possible to use table parameters with raw sql, but the challenge there is that I can't easily inject it into the middle of a complex query if I want to use LINQ.
The text was updated successfully, but these errors were encountered: