无需数据库引擎的项目功能实现

在许多项目中,数据库引擎是不可或缺的一部分,它为提供了数据存储、查询和管理的强大功能。然而,随着技术的发展,开始思考是否可以在不使用数据库引擎的情况下实现相同的功能。这种思考源于一个名为RealNews的项目,该项目完全不使用数据库,却能够处理大量的数据流,并且运行良好。

本文将介绍如何在应用程序中实现类似的功能,即使用内存数据存储和动态查询技术,以实现与数据库相似的数据操作体验。需要注意的是,这种方法并不适用于所有情况,它适用于数据量有限且可以完全加载到内存中的场景。

以下是一些可能的应用场景:

  • 在应用程序内部缓存数据,以实现快速的无往返访问。
  • 适用于小/中型数据规模的零安装应用程序。
  • 在Web API后端提供用户定义的查询功能。

实现所需

假设有一个对象列表:

var list = new List<SalesInvoice>();

在代码中,可以使用LINQ进行许多操作,例如:

var results = list.FindAll(x => x.Serial < 100);

这在编译时效果很好,但如果过滤器是一个动态字符串,用户在浏览器中输入,而代码在服务器端的API后面,该怎么办呢?

这时,System.LINQ.Dynamic就派上用场了,这是微软提供的一个代码库,允许在Where()函数中使用字符串。

var result = list.Where("name.Contains(\"Peter\") and serial<100");

这很棒,但个人更喜欢写SQL风格的查询,而不是C#特定的查询:

var result = list.Where("name = \"Peter\" and serial<100");

因此,需要对System.LINQ.Dynamic进行一些调整,这并不容易理解,但通过调试,找到了进行等值检查的地方,并插入了所需的代码。

工作原理

System.LINQ.Dynamic是一个复杂的东西,不容易理解,但通过调试,找到了进行等值检查的地方,并插入了所需的代码。代码检查等号左侧的类型是否为字符串,然后构建一个Expression来处理右侧的Contains()检查。

为了允许搜索Guid类型,它首先使用ToString(),然后使用Contains(),这样就可以像搜索字符串一样搜索Guid

Contains()的问题

首先,使用了Contains(),但很快发现它是区分大小写的,这不是用户所期望的,所以不得不编写一个不区分大小写的版本,使用IndexOf(),基本上是以下C#代码:

var bool = str.IndexOf("val", StringComparison.OrdinalIgnoreCase) >= 0;

是的/否 "val" 在 str 中

代码

以下是在System.LINQ.DynamicDynamic.cs中添加和更改的代码。注释了抛出异常的情况,但跳过它运行得很好。

// changed void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos) { Expression[] args = new Expression[] { left, right }; MethodBase method; if (FindMethod(signatures, "F", false, args, out method) != 1) ; // throw IncompatibleOperandsError(opName, left, right, errorPos); // MG left = args[0]; right = args[1]; } // added Expression Contains(Expression left, Expression right) { // if left string -> generate contains method Expression[] args = new Expression[] { right, Expression.Constant(StringComparison.OrdinalIgnoreCase) }; if (_indexof == null) { // using indexof() instead of contains() which is case sensitive FindMethod(typeof(string), "IndexOf", false, args, out MethodBase mb); _indexof = mb; } Expression ex = Expression.Call(left, (MethodInfo)_indexof, args); return Expression.GreaterThanOrEqual(ex, Expression.Constant(0)); } // added Expression NotContains(Expression left, Expression right) { // if left string -> generate method Expression[] args = new Expression[] { right, Expression.Constant(StringComparison.OrdinalIgnoreCase) }; if (_indexof == null) { // using indexof() instead of contains() which is case sensitive FindMethod(typeof(string), "IndexOf", false, args, out MethodBase mb); _indexof = mb; } Expression ex = Expression.Call(left, (MethodInfo)_indexof, args); return Expression.LessThan(ex, Expression.Constant(0)); } // changed Expression GenerateEqual(Expression left, Expression right) { // MG if (left.Type == typeof(Guid)) { FindMethod(typeof(Guid), "ToString", false, new Expression[] { }, out MethodBase ts); Expression tse = Expression.Call(left, (MethodInfo)ts, null); return Contains(tse, right); } if (left.Type == typeof(string)) return Contains(left, right); else return Expression.Equal(left, right); } // changed Expression GenerateNotEqual(Expression left, Expression right) { // MG if (left.Type == typeof(Guid)) { FindMethod(typeof(Guid), "ToString", false, new Expression[] { }, out MethodBase ts); Expression tse = Expression.Call(left, (MethodInfo)ts, null); return NotContains(tse, right); } if (left.Type == typeof(string)) return NotContains(left, right); else return Expression.NotEqual(left, right); }

酷功能

LINQ允许分页数据:

list.Where("serial<100").Skip(100).Take(10);

它还允许排序数据:

list.Where("serial<100").OrderBy("name");

// 升序

list.Where("serial<100").OrderBy("name desc");

// 降序

如所见,属性名是不区分大小写的,这使得使用它成为一种乐趣。

也可以使用:

  • SQL风格的and、or
  • C#风格的&&、||
  • 不等于!=、<>
  • 括号
  • 属性中的属性,使用点分隔
(name = "peter" && address = "hill") or (serial<100 and date.year=2000) name != "peter" and address <> "hill"

示例应用程序

为了展示这些功能,创建了一个示例应用程序,它生成了100,000个SalesInvoice对象,并允许查询并在网格中显示结果。

可以选择菜单中的示例查询,并在文本框中进行调整和更改。要运行查询,只需在输入时按回车键。

其他可能的功能

在RaptorDB中,可以进行全文搜索的列被分解成单词,所以可以做:

name = "alice bob"

意味着(name包含"alice"和name包含"bob")

name = "alice +bob"

意味着(name包含"alice"或name包含"bob")

name = "alice -bob"

意味着(name包含"alice"且不包含"bob")

也可以使用任何其他序列化器,如fastBinaryJSON,它在加载和保存时更快,或者如果对象结构非常简单,可以使用像protobuf或fastCSV这样的工具。

在对一个包含13,586项的列表进行测试时,fastCSV加载需要68毫秒,而fastJSON需要230毫秒。

处理在多用户环境中添加和删除项目,即在访问时锁定列表。

附录 - 更新1

遇到的一个问题是,如果查询的属性中有null值,那么LINQ就会崩溃,为了克服这个问题,更改了Contains()和NotContains()函数,以检查这一点:

Expression NotContains(Expression left, Expression right) { // if left string -> generate method // FIX : if right has spaces -> generate multiple "and indexof()" Expression[] args = new Expression[] { right, Expression.Constant(StringComparison.OrdinalIgnoreCase) }; if (_indexof == null) { // using indexof() instead of contains() which is case sensitive FindMethod(typeof(string), "IndexOf", false, args, out MethodBase mb); _indexof = mb; } // null checking left value Expression nn = Expression.Equal(Expression.Constant(null), left); Expression ex = Expression.Call(left, (MethodInfo)_indexof, args); return Expression.OrElse(nn, Expression.LessThan(ex, Expression.Constant(0))); } // MG Expression Contains(Expression left, Expression right) { // if left string -> generate method // FIX : if right has spaces -> generate multiple "and indexof()" Expression[] args = new Expression[] { right, Expression.Constant(StringComparison.OrdinalIgnoreCase) }; if (_indexof == null) { // using indexof() instead of contains() which is case sensitive FindMethod(typeof(string), "IndexOf", false, args, out MethodBase mb); _indexof = mb; } // null checking left value Expression nn = Expression.NotEqual(Expression.Constant(null), left); Expression ex = Expression.Call(left, (MethodInfo)_indexof, args); return Expression.AndAlso(nn, Expression.GreaterThanOrEqual(ex, Expression.Constant(0))); }

下载源代码 - 59.3 KB

下载EXE - 59 KB

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485