[英]LINQ expression could not be translated and will be evaluated locally
使用具有自定義類型屬性的實體時,無法將類型轉換為 SQL。
我創建了一個示例來解釋我的解決方法:
class 發生在某個學期。 學期作為DateTime
值存儲在數據庫中。
學期本身是一種自定義類型,具有附加屬性。
public class Semester
{
public enum HalfYear
{
First = 1,
Second = 7
}
DateTime _dateTime;
public Semester (HalfYear halfYear, int year)
{
_dateTime = new DateTime(year, (int) halfYear, 1)
}
public int Year => _dateTime.Year;
public HalfYear HalfYear => (HalfYear) _dateTime.Month;
public DateTime FirstDay => new DateTime(Year, _dateTime.Month, 1);
public DateTime LastDay => new DateTime(Year, _dateTime.Month + 5, DateTime.DaysInMonth(Year, _dateTime.Month + 5));
}
public class Class
{
int Id { get; set; }
string Title { get; set; }
Semester Semester { get; set; }
}
Semester
類型可以使用 值轉換器映射到DateTime
。
這在Where
子句中不起作用,例如
db.Classes.Where(c = c.Semester.FirstDay <= DateTime.Now &&
c.Semester.LastDay >= DateTime.Now)
當 Entity Framework Core 嘗試將表達式樹轉換為 SQL 時,它不知道如何轉換Semester.FirstDay
或Semester.LastDay
。
正如文檔所述,這是價值轉換的已知限制
使用值轉換可能會影響 EF Core 將表達式轉換為 SQL 的能力。 對於此類情況,將記錄警告。 正在考慮在未來的版本中刪除這些限制。
如何解決這個問題?
EntityFrameworkCore 有 3 個擴展點,可用於將自定義類型轉換為 SQL。
這些翻譯器和映射可以使用相應的插件注冊:
插件使用IDbContextOptionsExtension注冊
以下示例說明了我如何實現這些接口來注冊自定義類型 Semester:
IMemberTranslator
public class SqlServerSemesterMemberTranslator : IMemberTranslator
{
public Expression Translate(MemberExpression memberExpression)
{
if (memberExpression.Member.DeclaringType != typeof(Semester)) {
return null;
}
var memberName = memberExpression.Member.Name;
if (memberName == nameof(Semester.FirstDay)) {
return new SqlFunctionExpression(
"DATEFROMPARTS",
typeof(DateTime),
new Expression[] {
new SqlFunctionExpression( "YEAR", typeof(int),new[] { memberExpression.Expression }),
new SqlFunctionExpression( "MONTH", typeof(int),new[] { memberExpression.Expression }),
Expression.Constant(1, typeof(int))
});
}
if (memberName == nameof(Semester.LastDay)) {
return new SqlFunctionExpression(
"EOMONTH",
typeof(DateTime),
new Expression[] {
memberExpression.Expression
});
}
if (memberName == nameof(Semester.HalfYear)) {
return Expression.Convert(
new SqlFunctionExpression(
"MONTH",
typeof(int),
new Expression[] {
memberExpression.Expression
}),
typeof(HalfYear));
}
if (memberName == nameof(Semester.Year)) {
return new SqlFunctionExpression(
"YEAR",
typeof(int),
new Expression[] {
memberExpression.Expression
});
}
return null;
}
}
IMethodCallTranslator
public class SqlServerSemesterMethodCallTranslator : IMethodCallTranslator
{
public Expression Translate(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType != typeof(Period)) {
return null;
}
var methodName = methodCallExpression.Method.Name;
// Implement your Method translations here
return null;
}
}
關系類型映射
public class SqlServerSemesterTypeMapping : DateTimeTypeMapping
{
public SqlServerSemesterTypeMapping(string storeType, DbType? dbType = null) :
base(storeType, dbType)
{
}
protected SqlServerSemesterTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
{
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) => new SqlServerSemesterTypeMapping(parameters);
}
IMemberTranslatorPlugin
public class SqlServerCustomMemberTranslatorPlugin : IMemberTranslatorPlugin
{
public IEnumerable<IMemberTranslator> Translators => new IMemberTranslator[] { new SqlServerSemesterMemberTranslator() };
}
public class SqlServerCustomMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin
{
public IEnumerable<IMethodCallTranslator> Translators => new IMethodCallTranslator[] { new SqlServerSemesterMethodCallTranslator() };
}
IRelationalTypeMappingSourcePlugin
public class SqlServerCustomTypeMappingSourcePlugin : IRelationalTypeMappingSourcePlugin
{
public RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
=> mappingInfo.ClrType == typeof(Semester) || (mappingInfo.StoreTypeName == nameof(DateTime))
? new SqlServerSemesterTypeMapping(mappingInfo.StoreTypeName ?? "datetime")
: null;
}
定義並注冊翻譯器后,您必須在 DbContext 中配置它們。
IDbContextOptionsExtension
public class SqlServerCustomTypeOptionsExtension : IDbContextOptionsExtensionWithDebugInfo
{
public string LogFragment => "using CustomTypes";
public bool ApplyServices(IServiceCollection services)
{
services.AddEntityFrameworkSqlServerCustomTypes();
return false;
}
public long GetServiceProviderHashCode() => 0;
public void PopulateDebugInfo(IDictionary<string, string> debugInfo)
=> debugInfo["SqlServer:" + nameof(SqlServerCustomDbContextOptionsBuilderExtensions.UseCustomTypes)] = "1";
public void Validate(IDbContextOptions options)
{
}
}
擴展方法
public static class SqlServerCustomDbContextOptionsBuilderExtensions
{
public static object UseCustomTypes(this SqlServerDbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder == null) throw new ArgumentNullException(nameof(optionsBuilder));
// Registere die SqlServerDiamantOptionsExtension.
var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder;
var extension = coreOptionsBuilder.Options.FindExtension<SqlServerCustomTypeOptionsExtension>()
?? new SqlServerCustomTypeOptionsExtension();
((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
// Configure Warnings
coreOptionsBuilder
.ConfigureWarnings(warnings => warnings
.Log(RelationalEventId.QueryClientEvaluationWarning) // Should be thrown to prevent only warnings if a query is not fully evaluated on the db
.Ignore(RelationalEventId.ValueConversionSqlLiteralWarning)); // Ignore warnings for types that are using a ValueConverter
return optionsBuilder;
}
}
public static class SqlServerServiceCollectionExtensions
{
public static IServiceCollection AddEntityFrameworkSqlServerCustomTypes(
this IServiceCollection serviceCollection)
{
if (serviceCollection == null) throw new ArgumentNullException(nameof(serviceCollection));
new EntityFrameworkRelationalServicesBuilder(serviceCollection)
.TryAddProviderSpecificServices(
x => x.TryAddSingletonEnumerable<IRelationalTypeMappingSourcePlugin, SqlServerCustomTypeMappingSourcePlugin>()
.TryAddSingletonEnumerable<IMemberTranslatorPlugin, SqlServerCustomTypeMemberTranslatorPlugin>()
.TryAddSingletonEnumerable<IMethodCallTranslatorPlugin, SqlServerCustomTypeMethodCallTranslatorPlugin>());
return serviceCollection;
}
}
在 DbContext 中注冊選項
dbOptionsBuilder.UseSqlServer(connectionString, builder => builder.UseCustomTypes())
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.