noDB






4.40/5 (9投票s)
使用对象列表、LINQ和类似SQL的字符串查询,代替数据库,并使用fastJSON序列化器
引言
我已经在生产环境中使用RaptorDB
很长时间了,最近我想,如果我可以在一个项目中不用数据库引擎,但仍然拥有相同的功能会怎么样。这个想法来自RealNews
,它根本不使用数据库,即使有大量的 feed 条目也能很好地工作。
通过我将在下面解释的内容,我能够将此功能添加到我的应用程序中,并通过配置设置,在RaptorDB
和动态LINQ to objects之间无缝切换,排序和分页功能对用户保持不变。
显然,这并非适用于所有情况的解决方案,并且仅当您的数据大小有限且可以放入内存时才有意义。
一些用途包括
- 在应用程序中缓存数据,以实现快速的无往返访问
- 小型/中型数据量的零安装应用程序
- 在Web API后面定义用户查询功能
需要什么
假设你有一个对象列表
var list = new List<SalesInvoice>();
在代码中,您已经可以使用 LINQ 做很多事情,例如
var results = list.FindAll(x => x.Serial<100);
这在编译时效果很好,但是如果过滤器是一个动态字符串,您从浏览器中获取,并且您的代码位于 API 后面的服务器上怎么办?
输入 System.LINQ.Dynamic
,它是 Microsoft 的一段代码,允许您在 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
是一个复杂的东西,不容易理解,但是通过调试,我能够找到进行相等性检查的位置,并插入所需的代码。
该代码检查等号左侧的 string
类型,然后构建一个 Expression
来处理右侧的 Contains()
检查。
此外,为了允许搜索 Guid
类型,它首先使用 ToString()
,然后使用 Contains()
,因此您可以搜索类似字符串的 Guid
。
Contains() 的问题
首先,我使用了 Contains()
,但很快发现它是区分大小写的,这不是用户所期望的,所以我不得不编写一个不区分大小写的版本,使用 IndexOf()
,本质上是 Expression
语法中的以下 C# 代码
var bool = str.IndexOf("val", StringComparison.OrdinalIgnoreCase) >= 0 ; // yes/no "val" in str
代码
下面是在 System.LINQ.Dynamic
的 Dynamic.cs 中添加和更改的代码。我注释掉的 throw
异常在某种原因下发生了,但是跳过它就可以正常工作。
// 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"); // ascending
list.Where("serial<100").Orderby("name desc"); // descending
如您所见,属性名称不区分大小写,这使得使用它很愉快。
此外,您可以使用
- 类似 SQL 的
and
or
- c# 风格
&&
||
- 不等于
!=
<>
- 括号
- 带有点的属性中的属性
(name = "peter" && address = "hill") or (serial<100 and date.year=2000)
name != "peter" and address <> "hill"
示例应用程序
为了展示这些功能,我创建了一个示例应用程序,它使用 Faker.dll 生成 100,000 个 SalesInvoice
对象,并允许您查询并在网格中显示结果。
您可以从菜单中选择示例查询,并通过文本框进行调整和更改。要运行查询,只需在键入时按 Enter 键。
其他可能的功能
- 在
RaptorDB
中,可全文搜索的列被分解为单词,因此您可以这样做name = "alice bob" means (name contains "alice" and name contains "bob") name = "alice +bob" means (name contains "alice" or name contains "bob") name = "alice -bob" means (name contains "alice" and name does not contain "bob")
- 您还可以使用任何其他序列化程序,例如更快的加载和保存速度的
fastBinaryJSON
,或者如果您的对象结构非常简单,则可以使用protobuf
甚至fastCSV
。- 在一个包含 13,586 个项目的列表上,我进行了测试,
fastCSV
花费了 68 毫秒来加载,而fastJSON
则花费了 230 毫秒。
- 在一个包含 13,586 个项目的列表上,我进行了测试,
- 处理在多用户环境中添加和删除项目,即在访问时锁定列表。
附录 - 更新 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)));
}
以前的版本
历史
- 初始版本 : 2020 年 1 月 3 日
- 更新 1 : 2020 年 1 月 11 日
- 空值检查