我正在寻找在SQL Server中实现但使用MySQL的EF.Functions.FreeText
语法的类似MATCH...AGAINST
的东西。
这是我当前的工作流程:
AspNetCore 2.1.1
EntityFrameworkCore 2.1.4
Pomelo.EntityFrameworkCore.MySql 2.1.4
问题是MySQL使用两个函数,我不知道如何用DbFunction
来解释它,并为每个参数分开参数。有人知道如何实现吗?
这应该是Linq语法:
query.Where(x => DbContext.FullText(new[] { x.Col1, x.Col2, x.Col3 }, "keywords"));
这应该是在SQL中生成的结果:
SELECT * FROM t WHERE MATCH(`Col1`, `Col2`, `Col3`) AGAINST('keywords');
我正在尝试使用HasTranslation
函数遵循以下示例:
https://github.com/aspnet/EntityFrameworkCore/issues/11295#issuecomment-511440395
https://github.com/aspnet/EntityFrameworkCore/issues/10241#issuecomment-342989770
注意:我知道可以用FromSql
来解决,但这不是我想要的。
答案 0 :(得分:1)
当我需要EF Core ROW_NUMBER
支持时,您的用例与我的用例非常相似。
示例:
// gets translated to
// ROW_NUMBER() OVER(PARTITION BY ProductId ORDER BY OrderId, Count)
DbContext.OrderItems.Select(o => new {
RowNumber = EF.Functions.RowNumber(o.ProductId, new {
o.OrderId,
o.Count
})
})
使用匿名类代替数组
您要做的第一件事是从使用数组切换到匿名类,即您将调用从更改为
DbContext.FullText(new[] { x.Col1, x.Col2, x.Col3 }, "keywords")
到
DbContext.FullText(new { x.Col1, x.Col2, x.Col3 }, "keywords")
参数的排序顺序将保持查询中定义的顺序,
即new { x.Col1, x.Col2 }
将被翻译为Col1, Col2
和new { x.Col2, x.Col1 }
至Col2, Col1
。
您甚至可以执行以下操作:new { x.Col1, _ = x.Col1, Foo = "bar" }
将被转换为Col1, Col1, 'bar'
。
实施自定义IMethodCallTranslator
如果您需要一些提示,则可以在Azure DevOps: RowNumber Support上浏览我的代码,或者如果您可以等待几天,那么我将提供有关自定义功能实现的博客文章。
已更新(2019年7月27日)
感谢下面的评论,我认为需要澄清。
1)如下面的评论所指出的,还有另一种方法。使用HasDbFunction
可以为我节省一些输入,例如用于在EF中注册翻译器的代码,但是我仍然需要RowNumberExpression
,因为该函数具有2组参数(用于PARTITION BY
和{ {1}}和现有的ORDER BY
不支持。 (或者我错过了什么吗?)之所以选择SqlFunctionExpression
使用该方法,是因为我希望在设置IMethodCallTranslator
期间而不是在{{1 }}。也就是说,这是我的个人喜好。
最后,线程创建者也可以使用DbContextOptionsBuilder
来实现所需的功能。就我而言,代码如下所示:
OnModelCreating
2)匿名类型不能强制其成员的类型,因此,如果使用例如HasDbFunction
来调用该函数,则可以获取运行时异常。 // OnModelCreating
var methodInfo = typeof(DemoDbContext).GetMethod(nameof(DemoRowNumber));
modelBuilder.HasDbFunction(methodInfo)
.HasTranslation(expressions => {
var partitionBy = (Expression[])((ConstantExpression)expressions.First()).Value;
var orderBy = (Expression[])((ConstantExpression)expressions.Skip(1).First()).Value;
return new RowNumberExpression(partitionBy, orderBy);
});
// the usage with this approach is identical to my current approach
.Select(c => new {
RowNumber = DemoDbContext.DemoRowNumber(
new { c.Id },
new { c.RowVersion })
})
中的。尽管如此,它仍然是有效的解决方案。根据客户的不同,您为解决方案工作的可行性或多或少是可行的,最终由客户决定。不提供任何替代方案也是可能的解决方案,但不是令人满意的方案。
特别是,如果不希望使用SQL(因为从编译器获得的支持更少),那么运行时异常可能是一个很好的折衷方案。
但是,如果折衷方案仍然不能接受,那么我们可以研究如何增加对阵列的支持。
第一种方法可能是实现自定义integer
以将数组的处理“重定向”给我们。
请注意,这只是一个原型,需要更多的调查/测试:-)
string
为了进行测试,我引入了另一个包含IExpressionFragmentTranslator
作为参数的重载。
尽管如此,在我的用例中,这种方法完全没有意义:)
// to get into EF pipeline
public class DemoArrayTranslator : IExpressionFragmentTranslator
{
public Expression Translate(Expression expression)
{
if (expression?.NodeType == ExpressionType.NewArrayInit)
{
var arrayInit = (NewArrayExpression)expression;
return new DemoArrayInitExpression(arrayInit.Type, arrayInit.Expressions);
}
return null;
}
}
// lets visitors visit the array-elements
public class DemoArrayInitExpression : Expression
{
private readonly ReadOnlyCollection<Expression> _expressions;
public override Type Type { get; }
public override ExpressionType NodeType => ExpressionType.Extension;
public DemoArrayInitExpression(Type type,
ReadOnlyCollection<Expression> expressions)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
_expressions = expressions ?? throw new ArgumentNullException(nameof(expressions));
}
protected override Expression Accept(ExpressionVisitor visitor)
{
var visitedExpression = visitor.Visit(_expressions);
return NewArrayInit(Type.GetElementType(), visitedExpression);
}
}
// adds our DemoArrayTranslator to the others
public class DemoRelationalCompositeExpressionFragmentTranslator
: RelationalCompositeExpressionFragmentTranslator
{
public DemoRelationalCompositeExpressionFragmentTranslator(
RelationalCompositeExpressionFragmentTranslatorDependencies dependencies)
: base(dependencies)
{
AddTranslators(new[] { new DemoArrayTranslator() });
}
}
// Register the translator
services
.AddDbContext<DemoDbContext>(builder => builder
.ReplaceService<IExpressionFragmentTranslator,
DemoRelationalCompositeExpressionFragmentTranslator>());
并调整了方法的用法
Guid[]