EntityFramework对sqlite的中文支持问题

圣殿骑士18 2019-01-05 03:30:38
非常喜欢用EF,所以尽可能的对所有常用数据库都支持EF,包括sqlserver,mysql,sqlite。
对sqlite的中文支持,一直未发现问题,因为对sqlite的ef使用比较简单,直到有了对中文内容的模糊查询后,发现有问题了。

对EF常规的用法一般是这样的:
//常规的使用方式:存在的问题是,使用Contains()查询的时候,发现查询字符串越短越不精确,会查出不应该有的记录
var query = context.Was_Ba_Talk.AsQueryable();
string search = tbSearch.Text;
if (!search.IsNullOrEmpty())
{
query = query.Where(c => c.Titile.Contains(search) || c.Content.Contains(search));
}
this.gridTalk.DataSource = query.OrderBy(c => c.SortNo).ToList(); //Sqlite不支持query直接OrderBy

在使用Contains查询的时候,查询结果不精确,比如如果记录中的一个字段内容包含了“面对失败的风险”,我查询条件输入“面对”,
会发现把其他没有包含“面对”两个字的记录也查出来,我再加长查询条件,用“面对失败”,符合条件的记录数变少,再用“面对失败的风险”,就能精确查询出正确结果。

我原来猜测,可能是sqlite编码的问题,因为sqlite是utf8编码,而c#给与的是unicode编码字符串,可能是转码问题?但我用sql语句直接查,却否定了这个猜测。以下是我用SqlQuery直接执行sql语句,结果都正确,不受查询条件长度的影响:
DbSqlQuery<Was_Ba_Talk> query;
string sql = @"select * from Was_Ba_Talk";
string search = tbSearch.Text;
if (!search.IsNullOrEmpty())
{
sql += " where Titile like @p0 or Content like @p0";
query = context.Was_Ba_Talk.SqlQuery(sql, "%" + search + "%");
}
else
{
query = context.Was_Ba_Talk.SqlQuery(sql);
}
this.gridTalk.DataSource = query.OrderBy(c => c.SortNo).ToList(); //Sqlite不支持query直接OrderBy


既然直接写like语法可以,说明应该不是转码问题。
那么是因为EF解析Contains时,不是解析为“like”的?那是解析为什么?我网上查询的结果是sqlite并不支持charindex这种数据库函数,
只支持like,那么EF能把sql语句解析成什么?而导致这个结果呢?而又如何解决这个问题呢?

有没有熟悉sqlite和ef的朋友,答疑解惑?

开发环境:
win7
.Net4.0
EF6.1..3
SQLite.EF6 1.0.101
...全文
373 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
圣殿骑士18 2019-01-06
  • 打赏
  • 举报
回复
引用 10 楼 ilikeff8 的回复:
EF已经都帮你想到了

嗯,这个方法也不错
ilikeff8 2019-01-06
  • 打赏
  • 举报
回复
EF已经都帮你想到了
ilikeff8 2019-01-06
  • 打赏
  • 举报
回复
方法1:

public class SqliteInterceptor : IDbCommandInterceptor
{
private static Regex replaceRegex = new Regex(@"\(CHARINDEX\((.*?),\s?(.*?)\)\)\s*?>\s*?0");

public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}

public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}

public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
}

public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
ReplaceCharIndexFunc(command);
}

public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
}

public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
ReplaceCharIndexFunc(command);
}

private void ReplaceCharIndexFunc(DbCommand command)
{
bool isMatch = false;
var text = replaceRegex.Replace(command.CommandText, (match) =>
{
if (match.Success)
{
string paramsKey = match.Groups[1].Value;
string paramsColumnName = match.Groups[2].Value;
//replaceParams
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName == paramsKey.Substring(1))
{
param.Value = string.Format("%{0}%", param.Value);
break;
}
}
isMatch = true;
return string.Format("{0} LIKE {1}", paramsColumnName, paramsKey);
}
else
return match.Value;
});
if (isMatch)
command.CommandText = text;
}
}





public partial class Local2Entities : DbContext
{
public Local2Entities()
: base(Functions.GetConnectionString_Local2())
{
DbInterception.Add(new SqliteInterceptor());
}




public ICommand SearchLocalErrorLogCommand
{
get
{
return new DelegateCommand<Button>((button) =>
{
Functions.ButtonZoomStoryboard(button, (sender, e) =>
{
try
{
ClearErrorInfo();

using (Local2Entities localEntity2 = new Local2Entities())
{
var q = from d in localEntity2.LocalErrorLog.AsNoTracking()
select d;

if (!string.IsNullOrEmpty(SearchLocalErrorLogErrorInfo))
{
q = q.Where(p => p.ErrorInfo.Contains(SearchLocalErrorLogErrorInfo));
}

SearchLocalErrorLog = (from d in q
orderby d.CreateTime descending
select d).ToList().Take(5000).ToList();
SearchLocalErrorLogTotalRecordNumber = SearchLocalErrorLog.Count();
}
}
catch (Exception ex)
{
Functions.PlayErrorSound();
Functions.Speak("发生错误");
ShowAndLogErrorMessage(ex.Message, ex);

return;
}
});
});
}
}


方法2:

using (Local2Entities localEntity2 = new Local2Entities())
{
var q=localEntity2.Database.SqlQuery<LocalErrorLog>("select * from LocalErrorLog where ErrorInfo like ?", "%"+SearchLocalErrorLogErrorInfo+ "%");
SearchLocalErrorLog = q.ToList();



SearchLocalErrorLogTotalRecordNumber = SearchLocalErrorLog.Count();
}

圣殿骑士18 2019-01-05
  • 打赏
  • 举报
回复
解决步骤:
1、经xuzuning提醒,考虑用自定义函数的方式。检查有没有c#实现自定义函数的方案,结果找到了:
https://blog.csdn.net/lc156845259/article/details/68944742

2、定义自己的自定义函数:
/// <summary>
/// 返回字符或者字符串在另一个字符串中的起始位置
/// charindex(findString, origalString)
/// </summary>
[SQLiteFunction(Name = "CharIndex", FuncType = FunctionType.Scalar)]
public class FunctionCharIndexCn : SQLiteFunction
{
public override object Invoke(object[] args)
{
object arg1 = args[0];
object arg2 = args[1];
if (arg1 == null || arg2 == null) return -1; //没找到
string find = arg1.ToString();
string original = arg2.ToString();

return original.IndexOf(find);
}
}


3、执行,发现无效。猜测是仍然是在用系统原来的charindex。因此sqlite自定义函数的选用逻辑应该是:
同名函数优先选系统函数,如果没有系统函数,则选择自定义函数。那么只有将自定义函数改名了?
而sql语句是EF自动生成的,我怎么去改EF自动生成的sql语句呢??
答案是,使用EF的自定义函数功能,函数改名为:CharIndexCn。
public class EFDbFunctions
{
/// <summary>
/// 解决Sqlite中的CharIndex判断中文的bug,使用此自定义函数替换
/// </summary>
/// <param name="find"></param>
/// <param name="original"></param>
/// <returns></returns>
[DbFunctionAttribute("CodeFirstDatabaseSchema", "CharIndexCn")]
public static int CharIndexCn(string find, string original)
{
throw new NotSupportedException();
}
}


4、加载EF自定义函数:在DbContext的OnModelCreating中增加:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Was_Ba_OptionMap());
modelBuilder.Configurations.Add(new Was_Ba_TasksMap());
modelBuilder.Configurations.Add(new Was_Ba_WorkLogMap());
modelBuilder.Configurations.Add(new Was_Ba_TalkMap());

//加载自定义函数
modelBuilder.Conventions.Add(new FunctionsConvention("", typeof(EFDbFunctions)));
}


5、在业务代码中,使用CharIndexCn替换Contains:
using (var context = new AppDbContext())
{
var query = context.Was_Ba_Talk.AsQueryable();
string search = tbSearch.Text;
if (!search.IsNullOrEmpty())
{
//query = query.Where(c => c.Title.Contains(search) || c.Content.Contains(search)); //对中文判断有问题
query = query.Where(c => EFDbFunctions.CharIndexCn(search, c.Title) > 0 || EFDbFunctions.CharIndexCn(search, c.Content) > 0);
}
this.gridTalk.DataSource = query.OrderBy(c => c.SortNo).ToList();
}


Over。
圣殿骑士18 2019-01-05
  • 打赏
  • 举报
回复
引用 6 楼 小疯纸纸 的回复:
charindexFunc
我记得charindex在sqlservr2008的时候ntext类型数据比较靠后就 查不出来了,你想模糊查询的话可以搞拦截IDbCommandInterceptor,遇到charindex就给 替换成like语句

我解决了。
感谢xuzuning的提醒,我是用自定义函数的。稍后贴出方案
  • 打赏
  • 举报
回复
charindexFunc 我记得charindex在sqlservr2008的时候ntext类型数据比较靠后就 查不出来了,你想模糊查询的话可以搞拦截IDbCommandInterceptor,遇到charindex就给 替换成like语句
圣殿骑士18 2019-01-05
  • 打赏
  • 举报
回复
我的问题换一种问法,就是怎么既使用EF,又能避免sqlite中模糊条件查询中文时的问题?
圣殿骑士18 2019-01-05
  • 打赏
  • 举报
回复
引用 2 楼 xuzuning 的回复:
不太清楚你在纠结什么
但是你似乎忽略了一点:SQLite 是可以扩展用户语言函数的

虽然直接用sql语句没问题。但我想保持强类型的纯洁性,不想搞一些用sql语句的特殊化。
圣殿骑士18 2019-01-05
  • 打赏
  • 举报
回复
引用 2 楼 xuzuning 的回复:
不太清楚你在纠结什么
但是你似乎忽略了一点:SQLite 是可以扩展用户语言函数的

可能我没说清楚?不能查出应该查出的正确结果,这是程序有bug,应该重视。我现在的办法,如果只能全部数据查出来,然后在内存里过滤。

另,sqlite扩展函数,要用c吧?这我不会。。。
xuzuning 2019-01-05
  • 打赏
  • 举报
回复
不太清楚你在纠结什么
但是你似乎忽略了一点:SQLite 是可以扩展用户语言函数的
圣殿骑士18 2019-01-05
  • 打赏
  • 举报
回复
补充:
我调试跟踪,看query的内容,发现其sql语句,果然将contains解析成了charindex:
SELECT 
[Extent1].[ListId] AS [ListId],
[Extent1].[Titile] AS [Titile],
[Extent1].[Content] AS [Content],
[Extent1].[SortNo] AS [SortNo],
[Extent1].[Status] AS [Status]
FROM [Was_Ba_Talk] AS [Extent1]
WHERE (((CHARINDEX(@p__linq__0, [Extent1].[Titile])) - 1) > 0) OR ((CHARINDEX(@p__linq__1, [Extent1].[Content])) > 0)


奇葩的是,我将参数替换为我想查询的参数,放sqlite查询工具直接查sql的时候,却提示我:没有charindex函数,这说明sqlite确实没有这个函数。
但是!!!!!!
1、sql语句中含有不支持的函数,为什么程序运行不报异常?
2、为什么明明不支持的函数,在输入“面对”时,确实能查出符合条件的数据?(只是也查出了不符合条件的部分数据),而输入“面对失败的风险”时,查出了正确的数据?这是什么道理呢?

110,538

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧