LINQ 脚本:一种通用的面向对象数据库查询语言
使用 LINQ 脚本查询面向对象生物数据库。
- 下载 LINQFramework-noexe.zip - 172 KB
- 下载 LINQFramework.zip - 820 KB
- 下载 TestData_xcc8004_genes.zip - 1.4 MB
背景
对象关系数据库是当今世界上最流行的数据库类型。众所周知,我们可以使用 SQL 脚本来查询这些 o-r 数据库。但随着互联网行业的发展,o-r 数据库将无法满足未来的数据存储需求,而面向对象数据库产品正在开发中以满足数据存储需求。o-o 数据库尚未得到成功应用。
但是,面向对象数据库已成功应用于生物信息学研究领域:几乎所有著名的生物数据库都是 o-o 数据库,这些数据库免费为全球数十亿生物研究人员提供服务,以支持他们的科学研究项目!尽管几乎所有这些数据库都以 TEXT 文件格式存储生物数据,并且体积庞大。
是否有通用的查询语言来查询这些生物数据库?没有,这意味着这些数据库中的每一种都有自己的查询数据库的方式!根据我在大学学习期间的编程经验,我发现 LINQ 是查询这些 o-o 数据库的最佳查询语言,但不幸的是,我们必须在代码中使用 LINQ,然后在 Visual Studio 中编译我们的代码,这意味着 LINQ 集成在我们的 VisualBasic/C# 代码中,LINQ 无法独立于程序运行,LINQ 不是像 SQL 那样的脚本语言,它只是 VisualBasic/c# 语言的一种语言语法。当然,并非所有生物研究人员都使用 Visual Studio 或在其计算机上安装 Visual Studio。
因此,在我这篇博文的工作中,我将 LINQ 作为一种通用的脚本语言来查询这些生物数据库和其他面向对象数据库。
引言
LINQ 的基本知识
语言集成查询 (LINQ) 是 Visual Studio 2008 和 .NET Framework 3.5 版本中引入的一项创新,它弥合了对象世界与数据世界之间的鸿沟。Microsoft 将 LINQ 分为 4 类:(SQL Server 数据库) LINQ to SQL,(XML 文档) LINQ to XML,(ADO.NET 数据集) LINQ to Dataset,(.NET 集合、文件、字符串等) LINQ to Objects。
但通常,我们将 LINQ 分为 3 类:LINQ to SQL、LINQ to XML 和 LINQ to Object。LINQ to Object 是 .NET 语言编程中最常用的 LINQ 类型。
以下是一个典型的 LINQ to Object 查询语句语法示例
Dim Collection As Generic.IEnumerable(Of <TYPE>) =
From object As <TYPE> In <TYPE Object Collection>[.AsParallel]
[Let readonlyObject1 = expression1 Let readonlyObject2 = expression2 …]
Where <Boolean Expression>
[Let readonlyObject3 = expression3 …]
Select <Expression>
[Distinct Order By <Expression> Ascending|Descending]
通常,VisualBasic/C# 中的任何 For 循环都可以转换为 LINQ 语句,这意味着我们的程序是基于查询语句的程序,因为 For 循环包含了大部分数据查询操作。实际上,我们可以轻松地通过 .AsParallel 扩展方法使我们的程序成为并行版本。
介绍知识:CodeDom 编译器
CodeDom 提供了表示许多常见源代码元素的类型的类。您可以设计一个程序,使用 CodeDOM 元素构建源代码模型来组装对象图。然后可以使用 CodeDOM 代码生成器将此对象图呈现为支持的编程语言的源代码。CodeDom 还可用于将源代码编译为二进制程序集。
从我的观点来看,System.CodeDOM 命名空间主要包含两类对象
- System.CodeDOM 命名空间中用于表示源代码逻辑结构的类,独立于语言语法。您可以使用此命名空间中的类来构建您自己编程语言的源代码对象模型!
- 用于将源代码对象模型编译为二进制程序集文件的类。实际上,此命名空间中的类只是 .NET 程序编译器 vbc.exe 或 csc.exe 的命令行互操作工具。
因此,使用 CodeDom 类对象,您可以构建自己的语言编译器,将您的语言编译为 .Net 程序。困难的工作只是编写一个解析器,将您语言中的源代码正确地解析成与语言无关的 CodeDOM 元素。然后使用 CodeDOM 编译二进制程序集非常容易。
.NET 程序使用用户定义编译器时的编译过程
也许许多 .NET 程序员都使用过 SharpDevelop 中的离线代码约定功能。此功能的实现方式与 CodeDom 相同
源代码(VisualBasic)-> 解析为 CodeDom 对象模型 -> 另一种格式的代码(Boo、c#、Python、Ruby)
使用 CodeDom
通常,.net 程序主要由几个类对象定义组成,因为任何 .NET 语言都是完全面向对象的编程语言。当您了解了 .net 程序由什么构成后,就可以开始使用 CodeDom 对您的源代码进行建模。使用 CodeDom 对 .NET 程序进行建模的过程与遵循源代码编码方式的步骤一样简单
1. 定义一个 Assembly 模块
Dim Assembly As CodeDom.CodeCompileUnit = New CodeDom.CodeCompileUnit
代码编译单元代表您的应用程序程序集文件。您可以使用此对象来编译 EXE 程序或 DLL 库模块。
2. 声明命名空间
Dim CodeNameSpace As CodeDom.CodeNamespace = New CodeDom.CodeNamespace("NamespaceName")
Call Assembly.Namespaces.Add(CodeNameSpace)
您总是需要一个命名空间来包含类对象。
3. 将类对象添加到您的程序中
Dim DeclareClassType As CodeDom.CodeTypeDeclaration = New CodeDom.CodeTypeDeclaration(ClassName)
然后,您可以使用此语句定义一个具有特定名称 ClassName 的类对象,然后将这个新类添加到您先前声明的命名空间中
CodeNameSpace.Types.Add(DeclareClassType)
4. 向类添加一些方法函数和属性
然后,您就可以将一些属性、字段和方法函数添加到您在步骤 3 中定义的类类型中,以实现特定功能
Dim CodeMemberMethod As CodeDom.CodeMemberMethod = New CodeDom.CodeMemberMethod()
CodeMemberMethod.Name = Name : CodeMemberMethod.ReturnType =
New CodeDom.CodeTypeReference(Type)
CodeMemberMethod.Statements.Add(New CodeDom.CodeVariableDeclarationStatement(Type, "rval")) CodeMemberMethod.Statements.AddRange(StatementsCollectionInTheFunction)
CodeMemberMethod.Statements.Add(New CodeDom.CodeMethodReturnStatement(New CodeDom.CodeVariableReferenceExpression("rval"))) '引用返回值的局部变量
最后,在您将声明的函数添加到已声明的类对象后,您就拥有了一个几乎完整的 .NET 程序。
Call DeclareClassType.Members.Add(CodeMemberMethod)
您可以轻松地使用此函数从 CodeDom 对象模型中获取自动生成的源代码,很简单 ,玩得开心
''' <summary>
''' Generate the source code from the CodeDOM object model.
''' (根据对象模型生成源代码以方便调试程序)
''' </summary>
''' <param name="NameSpace"></param>
''' <param name="CodeStyle">VisualBasic, C#</param>
''' <returns></returns>
''' <remarks>
''' You can easily convert the source code between VisualBasic and C# using this function just by makes change in statement:
''' CodeDomProvider.GetCompilerInfo("VisualBasic").CreateProvider().GenerateCodeFromNamespace([NameSpace], sWriter, Options)
''' Modify the VisualBasic in to C#
''' </remarks>
Public Shared Function GenerateCode([NameSpace] As CodeDom.CodeNamespace, Optional CodeStyle As String = "VisualBasic") As String
Dim sBuilder As StringBuilder = New StringBuilder()
Using sWriter As IO.StringWriter = New System.IO.StringWriter(sBuilder)
Dim Options As New CodeGeneratorOptions() With {
.IndentString = " ", .ElseOnClosing = True, .BlankLinesBetweenMembers = True}
CodeDomProvider.GetCompilerInfo(CodeStyle).CreateProvider().GenerateCodeFromNamespace([NameSpace], sWriter, Options)
Return sBuilder.ToString()
End Using
End Function
根据我的测试,CodeDom 编译器目前仅支持 VisualBasic 和 C# 语言,F# 语言尚不支持,因为我在尝试使用 F# 或 FSharp 作为关键字时,CodeDom 编译器会抛出 codedomprovider not found 异常。
并使用此函数将 CodeDOM 对象模型编译为二进制程序集文件 (EXE/DLL)
''' <summary>
''' Compile the codedom object model into a binary assembly module file.
''' (将CodeDOM对象模型编译为二进制应用程序文件)
''' </summary>
''' <param name="ObjectModel">CodeDom dynamic code object model.(目标动态代码的对象模型)</param>
''' <param name="Reference">Reference assemby file path collection.(用户代码的引用DLL文件列表)</param>
''' <param name="DotNETReferenceAssembliesDir">.NET Framework SDK</param>
''' <param name="CodeStyle">VisualBasic, C#</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function Compile(ObjectModel As CodeDom.CodeCompileUnit, Reference As String(), DotNETReferenceAssembliesDir As String, Optional CodeStyle As String = "VisualBasic") As System.Reflection.Assembly
Dim CodeDomProvider As CodeDom.Compiler.CodeDomProvider =
CodeDom.Compiler.CodeDomProvider.CreateProvider(CodeStyle)
Dim Options As CodeDom.Compiler.CompilerParameters =
New CodeDom.Compiler.CompilerParameters
Options.GenerateInMemory = True
Options.IncludeDebugInformation = False
Options.GenerateExecutable = False
If Not Reference.IsNullOrEmpty Then
Call Options.ReferencedAssemblies.AddRange(Reference)
End If
Call Options.ReferencedAssemblies.AddRange(New String() {
DotNETReferenceAssembliesDir & "\System.dll",
DotNETReferenceAssembliesDir & "\System.Core.dll",
DotNETReferenceAssembliesDir & "\System.Data.dll",
DotNETReferenceAssembliesDir & "\System.Data.DataSetExtensions.dll",
DotNETReferenceAssembliesDir & "\System.Xml.dll",
DotNETReferenceAssembliesDir & "\System.Xml.Linq.dll"})
Dim Compiled = CodeDomProvider.CompileAssemblyFromDom(Options, ObjectModel)
Return Compiled.CompiledAssembly
End Function
名为 DotNETReferenceAssembliesDir
的参数是 .NET Framework 程序集文件引用的目录,因为从测试中我们发现 .NET Framework 程序集文件的引用在 Win7 和 Win8 之间存在差异,所以我使用此参数使 CodeDOM 编译器正常工作。如果您遇到任何编译错误,例如目录错误,您应该首先检查此参数。
LINQ 脚本:通用对象查询框架
LINQ 框架工作流程概述
这个 LINQ 脚本模块是如何工作的?它的工作方式与 .NET 程序的编译过程相同。LINQFramework 项目中有 3 个命名空间来实现此脚本功能
Framework: 此命名空间包含一个动态代码编译器,它使用语句命名空间中定义的模型来编译 LINQ 语句。它还包含一个 LINQ 框架接口 (ILINQ 接口和 LQueryFramework) 作为查询实体的互操作服务,以及一个 TypeRegistry 用于对象类型识别。
Parser: 语句解析器,此命名空间中的 parser 类将 LINQ 语句的令牌解析为一组 CodeDom 代码语句对象模型。非常感谢 Dustin Metzgar 在本文中出色的编码工作
https://codeproject.org.cn/Articles/14383/An-Expression-Parser-for-CodeDom
Statement: LINQ 语句的对象模型定义在此处。
如何动态编译 LINQ 语句为程序集?
LINQ 脚本基于 CodeDOM 的动态代码编译。因此,这是 LINQ 框架工作流程的概述
首先,用户输入的 LINQ 查询脚本由 LINQ 语句对象解析,然后使用 Parser 类将每个令牌元素表达式解析为 CodeDom 代码对象模型。
然后,框架将此 LINQ 语句对象模型编译成程序集模块,并动态加载已编译模块中的元素,完成 LINQ 框架的初始化操作。
最后,LINQ 框架使用特定的 LINQ 实体执行查询操作。
代码实现细节
构造 LINQ 语句对象模型
首先,我们来看我上传项目中 Statements 命名空间中的类类型,其中有一些令牌元素和一个 statement 类。
Statements.Tokens 命名空间中的每个类对象都对应 LINQ 脚本中的每个语句令牌
类对象 | LINQ 语句令牌 | 信息 |
ObjectDeclaration | From object As Type | 您应该在 LINQ 脚本中为此令牌指定对象类型,以便加载正确的 LINQ 实体模块。 |
ObjectCollection | In <Collection Expression> | Collection Expression 是文件路径或数据库连接字符串 |
ReadOnlyObject | Let object = <expression> | “只读”意味着我们无法修改其值。 |
WhereCondition | Where <Boolean Expression> | 测试条件是否为 True,以决定在此迭代中是否执行 select 令牌 |
SelectConstruct | Select <Expression> | 获取一个对象作为返回结果集 |
选项 | 此功能尚未实现 | |
Token | 此命名空间中所有类对象的基类 |
然后,我们可以使用这些令牌元素来构建 LINQ 语句对象模型,这是一个 LINQ 语句对象模型的类定义片段
命名空间 Statements
''' <summary>
''' A linq statement object model.
''' </summary>
''' <remarks>
''' From [Object [As TypeId]]
''' In [Collection]
''' Let [Declaration1, Declaration2, ...]
''' Where [Condition Test]
''' Select [Object/Object Constrctor]
''' [Distinct]
''' [Order Statement]</remarks>
Public Class Statement : Inherits LINQ.Statements.Tokens.Token
''' <summary>
''' An object element in the target query collection.(目标待查询集合之中的一个元素)
''' </summary>
''' <remarks></remarks>
Public Property [Object] As LINQ.Statements.Tokens.ObjectDeclaration
''' <summary>
''' Where test condition for the query.(查询所使用的Where条件测试语句)
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ConditionTest As LINQ.Statements.Tokens.WhereCondition
''' <summary>
''' Target query collection expression, this can be a file path or a database connection string.
''' (目标待查询集合,值可以为一个文件路径或者数据库连接字符串)
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property Collection As LINQ.Statements.Tokens.ObjectCollection
''' <summary>
''' A read only object collection which were construct by the LET statement token in the LINQ statement.
''' (使用Let语句所构造出来的只读对象类型的对象申明集合)
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ReadOnlyObjects As LINQ.Statements.Tokens.ReadOnlyObject()
''' <summary>
''' A expression for return the query result.(用于生成查询数据返回的语句)
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property SelectConstruct As LINQ.Statements.Tokens.SelectConstruct
Friend _Tokens As String()
Friend TypeRegistry As LINQ.Framework.TypeRegistry
''' <summary>
''' 本LINQ脚本对象所编译出来的临时模块
''' </summary>
''' <remarks></remarks>
Friend ILINQProgram As System.Type
TypeRegistry 组件
TypeRegistry 类对象是外部模块加载所必需的。在 ObjectDeclaration 类对象被解析后,构造函数将查询类型注册表并获取外部模块中 LINQ 实体的类型信息。获取 LINQ 实体类型信息后,将获取对象类型信息,最后,对象类型信息将被完整解析并编译到动态程序集中。
RegistryItem 定义
Registry item 记录外部模块中每个 LINQ 实体类型的相关信息
''' <summary>
''' item in the type registry table
''' </summary>
''' <remarks></remarks>
Public Class RegistryItem
''' <summary>
''' 类型的简称或者别称,即本属性为LINQEntity自定义属性中的构造函数的参数
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
<Xml.Serialization.XmlAttribute> Public Property Name As String
''' <summary>
''' 建议使用相对路径,以防止移动程序的时候任然需要重新注册方可以使用
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
<Xml.Serialization.XmlAttribute> Public Property AssemblyPath As String
''' <summary>
''' Full type name for the target LINQ entity type.(目标LINQEntity集合中的类型全称)
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
<Xml.Serialization.XmlAttribute>
Public Property TypeId As String
外部模块注册
在运行 LINQ 脚本查询数据库之前,您需要将 LINQ 实体类型信息注册到 LINQ 框架的类型注册表中,以便 LINQ 框架能够正确地从外部程序集模块加载 LINQ 实体。这是外部模块注册操作的前置函数
''' <summary>
''' Registry the external LINQ entity assembly module in the LINQFramework
''' </summary>
''' <param name="AssemblyPath">DLL file path</param>
''' <returns></returns>
''' <remarks>查询出目标元素的类型定义并获取信息</remarks>
Public Function Register(AssemblyPath As String) As Boolean
Dim Assembly As System.Reflection.Assembly = System.Reflection.Assembly.LoadFile(IO.Path.GetFullPath(AssemblyPath)) 'Load external module
Dim ILINQEntityTypes As System.Reflection.TypeInfo() =
LINQ.Framework.LQueryFramework.LoadAssembly(Assembly, Reflection.LINQEntity.ILINQEntity) 'Get type define informations of LINQ entity
If ILINQEntityTypes.Count > 0 Then
Dim LQuery As Generic.IEnumerable(Of TypeRegistry.RegistryItem) =
From Type As System.Type In ILINQEntityTypes
Select New TypeRegistry.RegistryItem With {
.Name = Framework.Reflection.LINQEntity.GetEntityType(Type),
.AssemblyPath = AssemblyPath,
.TypeId = Type.FullName} 'Generate the resitry item for each external type
For Each Item In LQuery.ToArray 'Update exists registry item or insrt new item into the table
Dim Item2 As RegistryItem = Find(Item.Name) '在注册表中查询是否有已注册的类型
If Item2 Is Nothing Then
Call ExternalModules.Add(Item) 'Insert new record.(添加数据)
Else 'Update exists data.(更新数据)
Item2.AssemblyPath = Item.AssemblyPath
Item2.TypeId = Item.TypeId
End If
Next
Return True
'I did't found any LINQ entity type define information, skip this dll assembly file
Else
Return False
End If
End Function
类型查找
这里我使用一个名为 find 的函数来查询注册表中的外部 LINQ 实体类型
''' <summary>
''' Return a registry item in the table using its specific name property.
''' (返回注册表中的一个指定名称的项目)
''' </summary>
''' <param name="Name"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function Find(Name As String) As TypeRegistry.RegistryItem
For i As Integer = 0 To ExternalModules.Count - 1
If String.Equals(Name, ExternalModules(i).Name, StringComparison.OrdinalIgnoreCase) Then
Return ExternalModules(i)
End If
Next
Return Nothing
End Function
动态编译
语句令牌 | CodeDOM 元素 | 编译结果 |
ObjectDeclaration | CodeMemberField, CodeMemberMethod | ILINQProgram 类中的一个字段和一个名为 SetObject 的函数 |
ObjectCollection | - | 外部位置作为 ILINQCollection 接口,未包含在此动态编译的代码中。 |
ReadOnlyObject | CodeMemberField, CodeAssignStatement | ILINQProgram 类中的一个字段和 SetObject 函数中的一个赋值语句 |
WhereCondition | CodeMemberMethod | ILINQProgram 类对象中的测试函数 |
SelectConstruct | CodeMemberMethod | ILINQProgram 类对象中的 SelectMethod 函数 |
选项 | 尚未实现 |
因此,如果我们把 LINQ 语句转换成一个对象定义,它可能看起来像这样
原始 LINQ 查询脚本
Dim LQuery As String = "from fasta as fasta in ""/home/xieguigang/BLAST/db/xcc8004.fsa"" " &
"let seq = fasta.sequence " &
"where regex.match(seq,""A{5}T{1}"").success & seq.length < 500 " &
"select system.string.format(""{0}{1}{2}{3}"", fasta.tostring, microsoft.visualbasic.vbcrlf, seq, microsoft.visualbasic.vbcrlf)"
此 LINQ 语句的 VisualBasic 编译版本
Namespace LINQDynamicCodeCompiled
Public Class ILINQProgram
Public fasta As TestEntity.FASTA ‘[from fasta as fasta] variable declaration statement token in the LINQ
Public seq As Object ‘[let seq = fasta.sequence] readonly object variables declaration statement in the LINQ
Public Overridable Function Test() As Boolean ‘[Where <condition test>] condition test statement token in the LINQ
Dim rval As Boolean
rval = (regex.match(seq, "A{5}T{1}").success _
And (seq.length < 500))
Return rval
End Function
Public Overridable Function SetObject(ByVal p As TestEntity.FASTA) As Boolean ‘This method is use for each loop iterator, get an object in the target collection and the use it to initialize each read-only object which is declared from the “let” statement.
Dim rval As Boolean
Me.fasta = p
seq = fasta.sequence
Return rval
End Function
Public Overridable Function SelectMethod() As Object ‘[Select <expression>] the select statement token are using for return value collection which was generate from this LINQ statement.
Dim rval As Object
rval = system.string.format("{0}{1}{2}{3}", fasta.tostring, microsoft.visualbasic.vbcrlf, seq, microsoft.visualbasic.vbcrlf)
Return rval
End Function
End Class
End Namespace
处理没有 where 条件表达式的情况
有时 LINQ 语句没有“where 条件测试”,因为我们只想使用 select 方法进行批量对象转换,如何处理这种情况?我创建了一个空的 Test 函数,并使其始终返回 true。如果语句解析 where 条件测试表达式并获得一个 null CodeExpression,则下面编译函数中的 StatementCollection 对象将为空,然后另一个函数将 rval 变量的值赋给 TRUE,这样我们就不会在将来修改 LINQ 对象模型结构。
Public Function Compile() As CodeDom.CodeTypeMember
Dim StatementCollection As CodeDom.CodeStatementCollection = Nothing
If Not Statement.ConditionTest.Expression Is Nothing Then
StatementCollection = New CodeDom.CodeStatementCollection
StatementCollection.Add(New CodeDom.CodeAssignStatement(
New CodeDom.CodeVariableReferenceExpression("rval"),
Statement.ConditionTest.Expression))
End If
Dim [Function] As CodeDom.CodeMemberMethod =
DynamicCode.VBC.DynamicCompiler.DeclareFunction(FunctionName,
"System.Boolean", StatementCollection)
[Function].Attributes = CodeDom.MemberAttributes.Public
Return [Function]
End Function
''' <summary>
''' Declare a function with a specific function name and return type. please notice that in this newly
''' declare function there is always a local variable name rval using for return the value.
''' (申明一个方法,返回指定类型的数据并且具有一个特定的函数名,请注意,在这个新申明的函数之中,
''' 固定含有一个rval的局部变量用于返回数据)
''' </summary>
''' <param name="Name">Function name.(函数名)</param>
''' <param name="Type">Function return value type.(该函数的返回值类型)</param>
''' <returns>A codeDOM object model of the target function.(一个函数的CodeDom对象模型)</returns>
''' <remarks></remarks>
Public Shared Function DeclareFunction(Name As String, Type As String, Statements As CodeDom.CodeStatementCollection) As CodeDom.CodeMemberMethod
Dim CodeMemberMethod As CodeDom.CodeMemberMethod = New CodeDom.CodeMemberMethod()
'创建一个名为“WhereTest”,返回值类型为Boolean的无参数的函数
CodeMemberMethod.Name = Name : CodeMemberMethod.ReturnType =
New CodeDom.CodeTypeReference(Type)
If String.Equals(Type, "System.Boolean", StringComparison.OrdinalIgnoreCase) Then
CodeMemberMethod.Statements.Add(
New CodeDom.CodeVariableDeclarationStatement(Type, "rval",
New CodeDom.CodePrimitiveExpression(True))) '创建一个用于返回值的局部变量,对于逻辑值,默认为真
Else
CodeMemberMethod.Statements.Add(
New CodeDom.CodeVariableDeclarationStatement(Type, "rval")) '创建一个用于返回值的局部变量
End If
If Not (Statements Is Nothing OrElse Statements.Count = 0) Then
CodeMemberMethod.Statements.AddRange(Statements)
End If
CodeMemberMethod.Statements.Add(
New CodeDom.CodeMethodReturnStatement(
New CodeDom.CodeVariableReferenceExpression("rval"))) '引用返回值的局部变量
Return CodeMemberMethod
End Function
创建用户使用的 LINQ 实体
每个 LINQ 实体对象都应实现一个接口:LINQ.Framework.ILINQCollection,这是 ILINQCollection 接口的定义
命名空间 Framework
''' <summary>
''' LINQ Entity
''' </summary>
''' <remarks></remarks>
Public Interface ILINQCollection
''' <summary>
''' Get a Collection of the target LINQ entity.(获取目标LINQ实体对象的集合)
''' </summary>
''' <param name="Statement"></param>
''' <returns></returns>
''' <remarks></remarks>
Function GetCollection(Statement As LINQ.Statements.LINQStatement) As Object()
''' <summary>
''' Get the type information of the element object in the linq entity collection.
''' (获取LINQ实体集合中的元素对象的类型信息)
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Function GetEntityType() As System.Type
End Interface
End Namespace
为什么我们需要这样做?
获取数据源集合并声明 LINQ 实体集合中的数据类型。从接口对象中的 GetCollection 函数,我们可以从 LINQ 查询脚本中的目标集合表达式加载数据,从 GetEntityType 函数,我们可以声明动态编译的 LINQ 查询类对象中的一个对象。
如何做?
首先,创建一个空的类对象,然后实现该接口,Visual Studio 会自动将空方法添加到您的类中。让我们看一个非常简单的例子
<LINQ.Framework.Reflection.LINQEntity("member")>
<Xml.Serialization.XmlType("doc")> Public Class ExampleXMLCollection
Implements LINQ.Framework.ILINQCollection
Public Property members As List(Of member)
Public Function GetCollection(Statement As LINQ.Statements.LINQStatement) As Object() Implements LINQ.Framework.ILINQCollection.GetCollection
Dim xml = Statement.Collection.Value.ToString.LoadXml(Of ExampleXMLCollection)()
Me.members = xml.members
Return members.ToArray
End Function
Public Function GetEntityType() As Type Implements LINQ.Framework.ILINQCollection.GetEntityType
Return GetType(member)
End Function
End Class
在上面显示的简单示例中,GetCollection 函数定义了对象集合的加载方法,也就是面向对象数据库的加载过程。从 GetEntityType 函数,LINQFramework 能够知道集合中的对象是成员类型。这样,LINQFramework 就可以正确地创建查询实例。
用户使用 LINQ 框架查询 O-O 数据库
查询步骤
- 从外部编译的程序集模块加载 LINQ 实体对象类型,加载类型信息来自 statements.tokens 命名空间中的对象声明类对象。
- 然后使用 Activator.CreateInstance 函数创建已加载 LINQ 实体的实例。然后,我们可以使用此 LINQ 实体加载对象集合。
- 在此步骤中,我初始化了 LINQ 语句对象模型中的所有语句令牌:这意味着我们使用反射操作来获取加载的 LINQ 实体类型的成员信息,然后我们就可以为查询操作创建 Lambda 表达式。
- 然后,我们拥有构建代码中 LINQ 查询对象模型所需的所有元素,并使用 LINQ 语句代码执行面向对象数据库查询操作。
- 将查询结果返回给用户
最后,我们可以通过这个查询函数使用 LINQ 脚本查询面向对象数据库
''' <summary>
''' Execute a compiled LINQ statement object model to query a object-orientale database.
''' </summary>
''' <param name="Statement"></param>
''' <returns></returns>
''' <remarks>
''' Dim List As List(Of Object) = New List(Of Object)
'''
''' For Each [Object] In LINQ.GetCollection(Statement)
''' Call SetObject([Object])
''' If True = Test() Then
''' List.Add(SelectConstruct())
''' End If
''' Next
''' Return List.ToArray
''' </remarks>
Public Function EXEC(Statement As LINQ.Statements.LINQStatement) As Object()
'Create a instance for the LINQ entity and intialzie the components
Dim StatementInstance = Statement.CreateInstance
Dim LINQ As ILINQCollection = Statement.Collection.ILINQCollection '
'Construct the Lambda expression
Dim Test As System.Func(Of Boolean) =
Function() Statement.ConditionTest.TestMethod.Invoke(StatementInstance, Nothing)
Dim SetObject As System.Func(Of Object, Boolean) =
Function(p As Object) Statement.Object.SetObject.Invoke(StatementInstance, {p})
Dim SelectConstruct As System.Func(Of Object) =
Function() Statement.SelectConstruct.SelectMethod.Invoke(StatementInstance, Nothing)
Dim LQuery = From [Object] As Object In LINQ.GetCollection(Statement)
Let f = SetObject([Object])
Where True = Test()
Let t = SelectConstruct()
Select t 'Build a LINQ query object model using the constructed elements
Return LQuery.ToArray 'return the query result
End Function
代码使用总结:o-o 数据库查询功能实现步骤
首先,创建一个 LINQ 实体,在此步骤中,您只需指定项目中的一个自定义属性
<LINQ.Framework.Reflection.LINQEntity("EntityName")>
此属性用于对象类型,它为目标对象映射类型生成一个集合。当然,此目标对象类型必须实现 LINQ.Framework 命名空间中定义的 ILINQ 接口。
然后将您的 LINQ 实体项目程序集模块编译为 dll 文件。
第二,使用代码在 LINQFramework 中注册您的已编译 LINQ 实体 dll 文件
Using LINQ As LINQ.Framework.LQueryFramework = New LINQ.Framework.LQueryFramework
Call LINQ.TypeRegistry.Register("Dll Assembly Path")
Call LINQ.TypeRegistry.Save()
…
End Using
然后,您可以从文本框中获取 LINQ 查询脚本,这意味着用户在您的程序中的 GUI 界面输入 LINQ 查询脚本,并将 LINQ 脚本编译为 LINQ 对象模型
Dim LQuery As String = <User input script string>
Dim Statement = Global.LINQ.Statements.Statement.TryParse(LQuery, LINQ.TypeRegistry)’LINQ script compiled into a LINQ object model.
最后,使用 LINQFramework 执行 LINQ 脚本以获取查询结果对象集合
Dim result = LINQ.EXEC(Statement)
别忘了将结果集返回给您的用户。
测试与示例项目
在这里,我带来了一些示例来向您展示 LINQ 作为面向对象数据库通用查询脚本的能力;您可以在我的测试项目:TestLINQEntity 中找到测试示例项目。编译此测试项目,然后将编译后的程序集文件复制到 WinForm 测试程序的根目录,然后注册此 TestLINQEntity.dll 文件。(菜单:文件 -> 注册外部模块)
在测试窗体中,exe 方法用于执行 LINQ 脚本查询,RegistryExternalModuleToolStripMenuItem_Click 方法用于向 LINQ 框架注册一个新的外部模块。
''' <summary>
''' Execute the linq script
''' </summary>
''' <param name="Linq"></param>
''' <remarks></remarks>
Private Sub Exe(Linq As String)
Dim Statement =
Global.LINQ.Statements.Statement.TryParse(Linq, LINQFramework.TypeRegistry)
TextBox1.AppendText(String.Format("{0}{1}Auto-generated code for debug:{2}{3}{4}", vbCrLf, vbCrLf, vbCrLf, Statement.CompiledCode, vbCrLf))
TextBox1.AppendText(vbCrLf & "Query Result:" & vbCrLf)
Dim Collection = LINQFramework.EXEC(Statement)
For Each obj In Collection
Call TextBox1.AppendText(vbCrLf & obj.ToString & vbCrLf)
Next
End Sub
''' <summary>
''' registry the LINQ entity external module
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub RegistryExternalModuleToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles RegistryExternalModuleToolStripMenuItem.Click
Dim File As New Windows.Forms.OpenFileDialog
If File.ShowDialog = Windows.Forms.DialogResult.OK Then
Call LINQFramework.TypeRegistry.Register(File.FileName)
Call LINQFramework.TypeRegistry.Save()
End If
End Sub
XML 查询
在 TestLINQEnityt 项目中,源代码文件 ExampleXML.vb 定义了一个用于 xml 文件查询操作的 LINQ 实体示例。从菜单 Example -> Query XML 可以获得一个示例 LINQ 查询脚本,然后单击执行按钮,您将获得通过此 LINQ 框架进行的 xml 文件查询操作的结果。
XML 查询的 LINQ 脚本示例 [Example -> Query XML]
from member as examplexml in "TEST_XML_FILE.xml" let name = member.name where microsoft.visualbasic.instr(name,"MetaCyc") select microsoft.visualbasic.mid(member.summary,1,20)
序列模式搜索
细菌基因组中的某些基因处于有趣的序列模式中,它们有时是非常重要的表达调控基因,并且可以通过正则表达式找到这种模式。下面是如何使用 LINQ 脚本查询 FASTA 数据库并使用特定序列模式查找基因的示例。
序列模式搜索的 LINQ 脚本示例 [Example -> sequence pattern search]
from fasta as fasta in ".\xcc8004.fsa" let seq = fasta.sequence let match = regex.match(seq,"A+T{2,}GCA+TT") where match.success select fasta.tostring + microsoft.visualbasic.vbcrlf + "***" + match.value + "***"
关于 CodeDom 编译器的已知问题
有一个已知问题是,编译的 LINQ 实体程序集文件必须放在您程序根目录的位置,否则在执行 LINQ 脚本查询操作时,您会收到一个类型丢失异常:动态编译的应用程序找不到您编译的 LINQ 实体程序集文件。我不确定这是 CodeDom 的限制还是出于其他安全考虑。
或者类似的类型不匹配异常