我如何告诉 linq2db 如何翻译给定的表达式,即 Split(char) 到 SQL 当它不知道如何做时?

How do I tell linq2db how to translate a given expression, ie Split(char) into SQL when it does not know how to do so?

我正在使用 linq2db,虽然它对于大多数 CRUD 操作来说工作得很好,但我遇到过很多无法转换为 SQL.

的表达式

已经到了这样的地步,除非我事先确切知道将涉及哪些类型的表达式并且之前已经成功调用过它们,否则我担心从 linq2db 中获得的任何好处都会被尝试查找然后删除(或从服务器端移开)有问题的表达式的成本。

如果我知道如何告诉 linq2db 如何根据需要随时将 Expression<Func<T,out T>> 或诸如此类的东西解析为 SQL,那么我会更有信心,我可以使用这个工具做很多事情。

String.Split(char separator)为例,该方法采用一个string和一个char到return一个string[]之间的每个子字符串分隔符。

假设我的 table Equipment 有一个可为空的 varchar 字段 Usages,其中包含以逗号分隔的不同设备用途列表。

我需要实施 IList<string> GetUsages(string tenantCode, string needle = null),它将提供给定租户代码和可选搜索字符串的用法列表。

我的查询将类似于:

var listOfListOfStringUsages =
    from et in MyConnection.GetTable<EquipmentTenant>()
    join e in MyConnection.GetTable<Equipment>() on et.EquipmentId = e.EquipmentId
    where (et.TenantCode == tenantCode)
    where (e.Usages != null)
    select e.Usages.Split(','); // cannot convert to sql here

var flattenedListOfStringUsages = 
    listOfListOfStringUsages.SelectMany(strsToAdd => strsToAdd)
                            .Select(str => str.Trim())
                            .Distinct();

var list = flattenedListOfStringUsages.ToList();

但是,它实际上会在运行时在注释指示的行上爆炸。

我完全明白,linq2db 的创建者不可能期望随 string 方法和主要数据库包的每个组合一起发布。

与此同时,如果我能看到一个这样做的例子(有人实现自定义表达式),我觉得好像可以完全告诉它如何处理这个问题。

所以我的问题是:我如何指导linq2db如何解析它无法开箱即用的Expression

试试 listOfListOfStringUsages.AsEnumerable()。它将强制在客户端执行 SelectMany。

更新:

我使用以下代码重现了这个问题:

var q =
    from t in db.Table
    where t.StringField != null
    select t.StringField.Split(' ');

var q1 = q
    //.AsEnumerable()
    .SelectMany(s => s)
    .Select(s => s.Trim())
    .Distinct()
    .ToList();

它不起作用。但是,如果我取消注释 .AsEnumerable(),它就可以正常工作。

几年前我写过这样的东西:

public class StoredFunctionAccessorAttribute : LinqToDB.Sql.FunctionAttribute
{
    public StoredFunctionAccessorAttribute()
    {
        base.ServerSideOnly = true;
    }

    // don't call these properties, they are made private because user of the attribute must not change them
    // call base.* if you ever need to access them
    private new bool ServerSideOnly { get; set; }
    private new int[] ArgIndices { get; set; }
    private new string Name { get; set; }
    private new bool PreferServerSide { get; set; }

    public override ISqlExpression GetExpression(System.Reflection.MemberInfo member, params ISqlExpression[] args)
    {
        if (args == null)
            throw new ArgumentNullException("args");

        if (args.Length == 0)
        {
            throw new ArgumentException(
                "The args array must have at least one member (that is a stored function name).");
        }

        if (!(args[0] is SqlValue))
            throw new ArgumentException("First element of the 'args' argument must be of SqlValue type.");

        return new SqlFunction(
            member.GetMemberType(),
            ((SqlValue)args[0]).Value.ToString(),
            args.Skip(1).ToArray());
    }
}

public static class Sql
{
    private const string _serverSideOnlyErrorMsg = "The 'StoredFunction' is server side only function.";

    [StoredFunctionAccessor]
    public static TResult StoredFunction<TResult>(string functionName)
    {
        throw new InvalidOperationException(_serverSideOnlyErrorMsg);
    }

    [StoredFunctionAccessor]
    public static TResult StoredFunction<TParameter, TResult>(string functionName, TParameter parameter)
    {
        throw new InvalidOperationException(_serverSideOnlyErrorMsg);
    }
}

...

[Test]
public void Test()
{
    using (var db = new TestDb())
    {
        var q = db.Customers.Select(c => Sql.StoredFunction<string, int>("Len", c.Name));
        var l = q.ToList();
    }
}

(当然,您可以围绕 Sql.StoredFunction() 方法编写包装器,以避免每次都将函数名称指定为字符串)

生成sql(用于上面代码中的测试):

SELECT
    Len([t1].[Name]) as [c1]
FROM
    [dbo].[Customer] [t1]

PS。我们在我们的项目中广泛使用了 linq2db,并且对它非常满意。但是,是的,有一个学习曲线(就像我们学习的几乎所有严肃的东西一样),人们需要花一些时间学习和使用图书馆,以便对它感到舒服并看到它可以提供的所有好处。