3

我正在使用linq2db它,虽然它对于大多数 CRUD 操作都足够好,但我遇到了许多无法转换为 SQL 的表达式。

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

如果我知道如何在临时、按需的基础上告诉linq2db如何将一个或诸如此类的东西解析为 SQL,那么我会更有信心,并且可以使用这个工具做很多事情。Expression<Func<T,out T>>

以 为例,String.Split(char separator)该方法采用 astring和 achar来返回string[]分隔符之间的每个子字符串的 a。

假设我的表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它无法解析的开箱即用?

4

2 回答 2

4

几年前,我写了这样一段话:

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,并且对它完全满意。但是,是的,有一个学习曲线(就像我们学习的几乎所有严肃的东西一样),需要花一些时间学习和玩图书馆,以便对它感到舒服并看到它可以带来的所有好处。

于 2015-05-05T15:45:11.053 回答
1

试试 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(),它就可以正常工作。

于 2015-05-03T23:05:14.037 回答