EFCore 连接值

EFCore join values

我正在尝试使用 EFCore 2.1.1 将一个大的 table 加入一个小的数据对列表。我希望这种加入发生在服务器端,而不是尝试下载整个 table,例如翻译成这样的东西:

SELECT a.*
FROM Groups AS a  
INNER JOIN (VALUES (1, 'admins'), (2, 'support'), (1, 'admins')) AS b(organization_id, name)   
ON a.organization_id = b.organization_id AND a.name = b.name;  

或等效的东西(例如使用常见的 table 表达式)。这可能吗?如果是这样,如何?将对象列表传递给 LINQ .join 似乎总是在客户端处理。

由于大量测试债务和the EFCore 3 breaking change on client-side evaluation,目前我们无法升级(但与较新版本相关的答案可能有助于我们推动管理)

如果您期望 EF Core 3.x 可以支持这一点,那您就错了。如果您打算升级您的应用程序,最好考虑 EF Core 6 和 .net 6。

反正我知道几个选项:

  1. 使用扩展方法 或类似方法
var items = ...

var query = context.Groups
    .FilterByItems(items, (q, b) => q.organization_id == b.organization_id && q.name == i.name, true);
  1. 使用第三方扩展 inq2db.EntityFrameworkCore 版本 2.x,请注意我是创作者之一。它将生成与问题完全相同的 SQL。
var items = ...

var query = 
    from g in context.Groups
    join b in items on new { g.organization_id, g.name } equals new { b.organization_id, b.name }
    select g;

var result = query.ToLinqToDB().ToList();

您可以使用 Table 值参数解决此问题。

首先定义一个数据库类型;

IF TYPE_ID(N'[IdName]') IS NULL
    CREATE TYPE [IdName] AS TABLE (
        [Id] int NOT NULL
        [Name] nvarchar(max) NOT NULL
    )

然后您可以从 IEnumerable;

构建 SqlParameter
public class IdName
{
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }

    public static SqlParameter ToParameter(IEnumerable<IdName> values)
    {
        var meta = new SqlMetaData[]{
            new SqlMetaData(nameof(Id), SqlDbType.Int),
            new SqlMetaData(nameof(Name), SqlDbType.NVarChar, int.MaxValue)
        };

        return new SqlParameter()
        {
            TypeName = nameof(IdName),
            SqlDbType = SqlDbType.Structured,
            Value = values.Select(v => {
                var record = new SqlDataRecord(meta);
                record.SetInt32(0, v.Id);
                record.SetString(1, v.Name);
                return record;
            })
        };
    }
}

然后定义一个 EF Core 查询类型,你可以把那个 SqlParameter 变成 IQueryable;

public void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<IdName>(e =>
    {
        e.HasNoKey();
        e.ToView(nameof(IdName));
    });
}

public static IQueryable<IdName> ToQueryable(DbContext db, IEnumerable<IdName> values)
    => db.Set<IdName>().FromSqlInterpolated($"select * from {ToParameter(values)}");

现在您可以在 Linq 连接中使用 IQueryable