如何在 Linq 中将 UNION ALL 转换为 OR 到 SQL 表达式树(Oracle ORA-00932:不一致的数据类型:预期得到 CLOB)

How to convert UNION ALL to OR in Linq to SQL expression trees (Oracle ORA-00932: inconsistent datatypes: expected got CLOB)

我正在使用 EF6 并使用 db first 为 MSSQLOracle 生成的模型。在少数地方,我通过多个搜索条件进行搜索,结果在每个查询都在其自己的子 select 中生成 UNION ALL sql。

Oracle table 中的一列是 CLOBlinq to sql,它用 UNION ALL 包裹了所有 select 的顶部在所有 UNIONS 中,它调用 SELECT DISTINCT "UnionAll1"."UNIQUE_ID" AS "C1", ... which requires to compare CLOB 并在 Oracle 端失败。

ORA-00932: inconsistent datatypes: expected - got CLOB

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: Oracle.ManagedDataAccess.Client.OracleException: ORA-00932: inconsistent datatypes: expected - got CLOB

有没有办法删除 DISTINCT 语句?我怎样才能使这个工作?

更新 生成 LINQ 的机制如下所示:

public static IQueryable<T> ApplySearch<T>(this IQueryable<T> queryable, SearchModel search) where T : class 
{
    var subQueries = new List<IQueryable<T>>();
    if (search != null)
    {
        if (search.PolicyNumber.HasValue && typeof (IPolicyNumber).IsAssignableFrom(queryable.ElementType))
        {
            subQueries.Add(queryable.SearchByPolicyNumber(search));
        }

        if (search.UniqueId.HasValue && typeof (IUniqueId).IsAssignableFrom(queryable.ElementType))
        {
            subQueries.Add(queryable.SearchByUniqueId(search));
        }

        if (!string.IsNullOrWhiteSpace(search.PostCode) && typeof(IPostCode).IsAssignableFrom(queryable.ElementType))
        {
            subQueries.Add(queryable.SearchByPostCode(search));
        }
    }

    return subQueries.DefaultIfEmpty(queryable)
        .Aggregate((a, b) => a.Union(b));
}

具体搜索方法示例:

 public static IQueryable<IRequestId> SearchByRequestId<IRequestId>(this IQueryable<IRequestId> queryable, SearchModel search)
    {
        var interfacesToColumnNames = new Dictionary<Type, string>
        {
            {typeof (IRequestId<>), "requestid"},
            {typeof (IRequest_Id<>), "request_id"},
        };

        var paramLambda = Expression.Parameter(typeof (IRequestId));
        var columnLambda = Expression.Property(paramLambda, interfacesToColumnNames.Single(o => queryable.ElementType.GetInterfaces().Any(oo => oo.Name == o.Key.Name)).Value);
        var lambda = Expression.Lambda<Func<IRequestId, bool>>(
            Expression.Equal(columnLambda, Expression.Convert(Expression.Constant(search.RequestId), columnLambda.Type)), paramLambda);
        queryable = queryable.Where(lambda);

        return queryable;
    }

在控制器中调用它的示例:

 public ActionResult QUOTE_HOUSE()
    {
        var onlineDocs =
            this.DatabaseManager.GetEntities<QUOTE_HOUSE>().ApplySearch(Search)
                .Take(10);
        return View("QUOTE_HOUSE", onlineDocs.ToList());
    }

根据评论中的附加信息,有问题的查询是由以下过程产生的:

public static IQueryable<T> ApplySearch<T>(this IQueryable<T> queryable, SearchModel search) where T : class 
{
    var subQueries = new List<IQueryable<T>>();
    if (search != null)
    {
        if (search.PolicyNumber.HasValue && typeof (IPolicyNumber).IsAssignableFrom(queryable.ElementType))
        {
            subQueries.Add(queryable.SearchByPolicyNumber(search));
        }

        if (search.UniqueId.HasValue && typeof (IUniqueId).IsAssignableFrom(queryable.ElementType))
        {
            subQueries.Add(queryable.SearchByUniqueId(search));
        }

        if (!string.IsNullOrWhiteSpace(search.PostCode) && typeof(IPostCode).IsAssignableFrom(queryable.ElementType))
        {
            subQueries.Add(queryable.SearchByPostCode(search));
        }
    }

    return subQueries.DefaultIfEmpty(queryable)
        .Aggregate((a, b) => a.Union(b));
}

我假设支持的方法是这样的

public static IQueryable<T> SearchByPolicyNumber<T>(this IQueryable<T> queryable, SearchModel search) where T : class 
{
    return queryable.Where(x => predicate_using_PolicyNumber(x, search));
}

public static IQueryable<T> SearchByUniqueId<T>(this IQueryable<T> queryable, SearchModel search) where T : class 
{
    return queryable.Where(x => predicate_using_UniqueId(x, search));
}

public static IQueryable<T> SearchByPostCode<T>(this IQueryable<T> queryable, SearchModel search) where T : class 
{
    return queryable.Where(x => predicate_using_PostCode(x, search));
}

问题是 EF 将 LINQ Union 运算符转换为 SQL UNION ALL 子查询并应用了 DISTINCT SELECT ...,正如您已经发现的那样。我不知道为什么它这样做而不是简单地将它翻译成 SQL UNION,但实际上不能保证它也适用于这种类型的列。

解决我看到的问题的唯一方法是消除 Union 运算符,将其替换为具有 Or 条件的单个 Where。为此,您必须稍微更改一下设计。

首先从支持的方法中提取谓词部分:

public static class SearchPredicates
{
    public static Expression<Func<T, bool>> ByPolicyNumber<T>(SearchModel search) where T : class 
    {
        return x => predicate_using_PolicyNumber(x, search);
    }

    public static Expression<Func<T, bool>> ByUniqueId<T>(SearchModel search) where T : class 
    {
        return x => predicate_using_UniqueId(x, search);
    }

    public static Expression<Func<T, bool>> ByPostCode<T>(SearchModel search) where T : class 
    {
        return x => predicate_using_PostCode(x, search);
    }
}

然后修改main方法如下:

public static IQueryable<T> ApplySearch<T>(this IQueryable<T> queryable, SearchModel search) where T : class 
{
    var predicates = new List<Expression<<Func<T, bool>>>();
    if (search != null)
    {
        if (search.PolicyNumber.HasValue && typeof (IPolicyNumber).IsAssignableFrom(queryable.ElementType))
            predicates.Add(SearchPredicates.ByPolicyNumber(search));
        if (search.UniqueId.HasValue && typeof (IUniqueId).IsAssignableFrom(queryable.ElementType))
            predicates.Add(SearchPredicates.ByUniqueId(search));
        if (!string.IsNullOrWhiteSpace(search.PostCode) && typeof(IPostCode).IsAssignableFrom(queryable.ElementType))
            predicates.Add(SearchPredicates.ByPostCode(search));
    }
    if (predicates.Count == 0)
        return queryable;

    var parameter = predicates[0].Parameters[0];
    var condition = predicates[0].Body;
    for (int i = 1; i < predicates.Count; i++)
        condition = Expression.Or(condition, predicates[i].Body.ReplaceParameter(predicates[i].Parameters[0], parameter));
    var predicate = Expression.Lambda<Func<T, bool>>(condition, parameter);
    return queryable.Where(predicate);
}

您可以使用任何兼容 EF 的谓词生成器,这里我手动构建谓词。使用的辅助方法是:

public static class ExpressionUtils
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == Source ? Target : base.VisitParameter(node);
        }
    }
}

应用所有这些,希望问题能够得到解决。

刚想到一个替代方案。

return subQueries.DefaultIfEmpty(queryable) .Aggregate((a, b) => a.Concat(b));

然后在 ToList() 之后你实际使用输出 .Distinct(new YourEqualityComparer()))。过滤掉可能的重复项。

这不是理想的解决方案,因为需要您在结构上手动实施 IEquitable,但如果您追求性能,它可能会更快。 OR 将需要数据库上的复合索引,同时通过两个单独的单列索引进行查询,然后合并数据将不需要在 table 上拥有所有可能的列索引组合。另一个缺点是您可能会在 ToList() 之前使用 Take(),然后在过滤之后您可能会得到很少的记录(并且必须重新查询)。