在许多项目中,数据库引擎是不可或缺的一部分,它为提供了数据存储、查询和管理的强大功能。然而,随着技术的发展,开始思考是否可以在不使用数据库引擎的情况下实现相同的功能。这种思考源于一个名为RealNews的项目,该项目完全不使用数据库,却能够处理大量的数据流,并且运行良好。
本文将介绍如何在应用程序中实现类似的功能,即使用内存数据存储和动态查询技术,以实现与数据库相似的数据操作体验。需要注意的是,这种方法并不适用于所有情况,它适用于数据量有限且可以完全加载到内存中的场景。
以下是一些可能的应用场景:
假设有一个对象列表:
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()
,但很快发现它是区分大小写的,这不是用户所期望的,所以不得不编写一个不区分大小写的版本,使用IndexOf()
,基本上是以下C#代码:
var bool = str.IndexOf("val", StringComparison.OrdinalIgnoreCase) >= 0;
是的/否 "val" 在 str 中
以下是在System.LINQ.Dynamic
的Dynamic.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");
// 降序
如所见,属性名是不区分大小写的,这使得使用它成为一种乐趣。
也可以使用:
(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毫秒。
遇到的一个问题是,如果查询的属性中有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