对 Oracle 的 EF 查询抛出“ORA-12704:字符集不匹配”

Posted

技术标签:

【中文标题】对 Oracle 的 EF 查询抛出“ORA-12704:字符集不匹配”【英文标题】:EF query to Oracle throwing "ORA-12704: character set mismatch" 【发布时间】:2017-10-02 04:37:45 【问题描述】:

我正在尝试合并来自 Oracle 的 EF 中的几列,然后在这些列上执行 .Contains(),如下所示:

public IEnumerable<User> SearchUsers(string search)

    search = search.ToLower();

    return _securityUow.Users
            .Where(u => (u.FirstName.ToLower() + " " + u.LastName.ToLower() + " (" + u.NetId.ToLower() + ")").Contains(search))
            .OrderBy(u => u.LastName)
            .ThenBy(u => u.FirstName)
            .AsEnumerable();

但是,我遇到了这个异常:


  "Message": "An error has occurred.",
  "ExceptionMessage": "An error occurred while executing the command definition. See the inner exception for details.",
  "ExceptionType": "System.Data.Entity.Core.EntityCommandExecutionException",
  "StackTrace": "   at SoftwareRegistration.WebUI.Controllers.Api.V1.UserContactController.Lookup(String search) in C:\LocalRepository\OnlineSupport\SoftwareRegistration\trunk\release\SoftwareRegistration\SoftwareRegistration.WebUI\Controllers\Api\V1\UserContactController.cs:line 40\r\n   at lambda_method(Closure , Object , Object[] )\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
  "InnerException": 
    "Message": "An error has occurred.",
    "ExceptionMessage": "ORA-12704: character set mismatch",
    "ExceptionType": "Oracle.ManagedDataAccess.Client.OracleException",
    "StackTrace": "   at OracleInternal.ServiceObjects.OracleCommandImpl.VerifyExecution(OracleConnectionImpl connectionImpl, Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, OracleException& exceptionForArrayBindDML, Boolean& hasMoreRowsInDB, Boolean bFirstIterationDone)\r\n   at OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteReader(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, OracleDataReaderImpl& rdrImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[] scnForExecution, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, Int64& internalInitialLOBFS, OracleException& exceptionForArrayBindDML, Boolean isDescribeOnly, Boolean isFromEF)\r\n   at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior)\r\n   at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)\r\n   at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)\r\n   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<Reader>b__c(DbCommand t, DbCommandInterceptionContext`1 c)\r\n   at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)\r\n   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(DbCommand command, DbCommandInterceptionContext interceptionContext)\r\n   at System.Data.Entity.Internal.InterceptableDbCommand.ExecuteDbDataReader(CommandBehavior behavior)\r\n   at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)\r\n   at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)"
  

我查询的列都是 Oracle 中的 VARCHAR2(128) 类型。

我也在另一个项目中使用了完全相同的代码,它可以工作。唯一的区别是,对于有效的项目,我使用Oracle.DataAccess,而对于无效的项目,我使用Oracle.ManagedDataAccess(我无法在这个项目中使用Oracle.DataAccess)。所以我相信托管驱动程序中存在错误/问题。

我愿意接受解决方案或变通办法。

【问题讨论】:

您的数据库使用什么字符集?运行SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET' 来确定这一点。 我跑了,上面写着WE8MSWIN1252 嗯,这与我的预期相反。 WE8MSWIN1252 是一个单字节字符集,但 IIRC .net 在内部使用 Unicode,所以我希望数据库或驱动程序应该能够从单字节数据库字符集到/从单字节数据库字符集转换为 Unicode。我查看了OracleGlobalization class docs,看起来在托管驱动程序下没有任何允许设置字符集的方法可用。祝你好运。 事实上,ODP.NET 托管驱动程序对 NLS_LANG 不敏感。它仅对 .NET 语言环境敏感,请参阅 Globalization Support。当您尝试使用 ToLower(CultureInfo) 时,它可能会有所帮助。 【参考方案1】:

我最终让这个 (ODP.Net Managed Driver - ORA-12704: character set mismatch in generated code) 的作者更新了这个问题,他发布了一个使用拦截器的解决方法,我会在这里更详细一点......

首先,我装饰了我的 DBContext 以加载配置。你可以跳过这个,如果你有一个,只需添加到你的配置中:

[DbConfigurationType(typeof(MyDbConfiguration))]
public partial class MyContext : DbContext

创建配置类:

public class MyDbConfiguration : DbConfiguration

    public MyDbConfiguration()
    
        this.AddInterceptor(new NVarcharInterceptor()); //add this line to existing config.
    

接下来,创建拦截器:

public class NVarcharInterceptor : IDbCommandInterceptor

    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    
        if (command != null && !string.IsNullOrWhiteSpace(command.CommandText))
            command.CommandText = command.CommandText.Replace("N''", "''");
    

    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    
        if (command != null && !string.IsNullOrWhiteSpace(command.CommandText))
            command.CommandText = command.CommandText.Replace("N''", "''");
    

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    
        if (command != null && !string.IsNullOrWhiteSpace(command.CommandText))
            command.CommandText = command.CommandText.Replace("N''", "''");
    

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    
        if (command != null && !string.IsNullOrWhiteSpace(command.CommandText))
            command.CommandText = command.CommandText.Replace("N''", "''");
    

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    
        if (command != null && !string.IsNullOrWhiteSpace(command.CommandText))
            command.CommandText = command.CommandText.Replace("N''", "''");
    

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    
        if (command != null && !string.IsNullOrWhiteSpace(command.CommandText))
            command.CommandText = command.CommandText.Replace("N''", "''");
    

【讨论】:

请注意:如果您的字符串物理上包含字符串 N'',那么您会大吃一惊。只是我的2c。最重要的是,该方法可能会更好,它转换为 N' -> ' (而不是 N'' -> '')【参考方案2】:

在这些情况下,最好手动连接这些查询,具体取决于列是否为 unicode,其中一些方式

return _securityUow.Users
    .Where(u => (u.FirstName.ToLower() + DbFunctions.AsNonUnicode(" ") + u.LastName.ToLower() + DbFunctions.AsNonUnicode(" (") + u.NetId.ToLower() + DbFunctions.AsNonUnicode(")")).Contains(search))
    ...

return _securityUow.Users
    .Where(u => (u.FirstName.ToLower() + DbFunctions.AsUnicode(" ") + u.LastName.ToLower() + DbFunctions.AsUnicode(" (") + u.NetId.ToLower() + DbFunctions.AsUnicode(")")).Contains(search))
    ...

取决于列的类型是 NVarchar 还是 Varchar(默认情况下 EF 使用 nvarchar,除非属性中设置了 .isUnicode)

【讨论】:

以上是关于对 Oracle 的 EF 查询抛出“ORA-12704:字符集不匹配”的主要内容,如果未能解决你的问题,请参考以下文章

带有 .NET EF Core 的内存缓存抛出错误

Web Config&EF6&DbFirst&Oracle - 无法将OracleConnection转换为SqlConnection

启动Oracle时提示:ORA-01078:failure in processing system parameters

如何将此混合行/列表转换为所需的输出。 (Oracle SQL/发行版:Ora12c)

Oracle数据库多个实例的查询与指定

EF Oracle Code First踩过的坑