MySQL-Fullltext:使用 MySQL 的简单搜索引擎的开始






4.90/5 (7投票s)
MySQL-Fullltext:使用MySQL C-API、WCF和jQuery构建一个简单的搜索引擎
引言
本文介绍了使用C语言实现一个简单搜索引擎的入门知识。
2013年8月22日发布的第一个版本仅执行查询。新版本实现了CRUD功能,这意味着您可以创建新记录、读取记录、更新记录和删除记录。新版本利用了jQuery插件 DataTables 和 editable。代码版本1已进行了清理,并且更好地处理了null
值。
我通常使用SQL Server和C#。但我教C/C++编程的朋友希望远离微软。过去,MySQL对我来说不是一个好的数据库,因为它在标准安装中缺乏事务支持,但它正在变得成熟。我使用MySQL 5.6 64位版,以InnoDB作为数据库,并使用Unicode(utf8)编码,这是新数据库的默认设置。
Freetext是InnoDB的一个新功能,它首次在MySQL 5.6版本中引入。
我通常更喜欢C++而不是C,即使是小型项目:不需要记住所有函数名,而是拥有一些带有所需操作的类,并且具有良好的Intellisense支持,这很好。在C++中,您也有用于集合和字符串的STL或Boost。
MySQL的C++接口相当小,而C接口却非常成熟,所以我决定使用C接口。
C DLL通过WCF发布以支持Ajax请求。在Visual Studio Ultimate 2012中,我使用了C#的"WCF服务应用程序"模板。我搜索了在C++中托管WebService
的方法,但只找到了一些调用托管C++中WebService的示例。
仅用于查询的UI是一个HTML页面,使用了JQuery和Jquery-UI的autocomplete功能。该页面被添加到"WCF服务应用程序"的网站中,项目名为VisionWeb
。页面名为"DefaultQueryOnly.html"。
网页看起来是这样的
对于版本2,我使用了文件"default.html"。页面看起来是这样的
我将项目配置为框架4.0、64位和调试。如果您使用32位MySQL服务器,则需要更改此设置。您必须将UNICODE选项保留在其默认值true。
配置MySQL
您可能会打开VisionDAL
项目,它位于VisionSmall
解决方案中。可能需要修改MySQL C接口的链接。这里,我将描述如何在新建项目中设置MySQL接口:检查这些设置是否正确,特别是mysql.lib
文件和VisionDAL.dll
的路径。
在Visual Studio中,添加一个名为VisionDAL
的项目,使用模板"其他语言/Visual C++/空项目"。在向导中,您只需要将"**应用程序类型**"更改为DLL。将VisionDAL.cpp重命名为VisionDAL.c,这足以让Visual Studio从编译C++切换到C。向项目中添加一个名为"VisionDAL.h"的头文件。
在解决方案资源管理器中,右键单击VisionDAL
项目并选择**属性**。在"配置属性"/链接器/输入下,选择"**其他依赖项**",然后添加libmysql.lib到路径,不要忘记分隔符";
"。
在"配置属性"/链接器/常规下,选择"**其他库目录**",对我来说,添加C:\Program Files\MySQL\MySQL Server 5.6\lib>。现在我们已经链接了C接口,但实现libmysql.lib中调用的DLL必须位于可执行文件的系统搜索路径中:从控制面板选择系统,点击"**高级系统设置**","**点击环境变量**",在"**系统变量**"下选择Path,然后添加libmysql.lib的路径(DLL与lib文件在同一个文件夹中):;C:\Program Files\MySQL\MySQL Server 5.6\lib。
我们还需要将VisionDal.dll也添加到我们的Path中,IIS在您将DLL放在其网站的bin文件夹中时找不到它。将<解决方案路径>/x64/debug添加到Path。我需要重启才能使此设置生效,最终通过切换"**托管管道模式**"设置来重启应用程序池可能会有帮助 - 您可以在IIS管理器中执行此操作。当网站接收到第一个请求时,它将加载VisionDAL.dll,如果您现在重新构建项目,您将收到一个关于VisionDAL.dll的写入错误:要修复它,请重置应用程序池或使用unlocker之类的工具。
然后我们指定VisionDAL
的包含属性。在"配置属性"/"C/C++"下,添加您的MySQL头文件路径,例如:C:\Program Files\MySQL\MySQL Server 5.6\include。
接下来,我们在"C/C++"/预处理器/"预处理器定义"下,将"**预编译头**"切换为"**不使用预编译头**",以避免在使用strcpy
和fopen
时出现错误消息,设置预处理器定义:USE_STANDARD_FILE_FUNCTIONS
和_CRT_SECURE_NO_WARNINGS
。
现在链接时,mysqllib
引用无法解析,因为它们是64位过程。通过打开VisionDAL
的项目属性,选择"**配置管理器**"按钮,并将平台设置为x64来修复此问题。
现在我们创建名为Vision的样本数据库
在SQL Development下打开MySql Workbench,打开您的实例。会出现一个新窗口"SQL File 1"。双击VisionDAL
项目中的Sql.txt文件。将所有内容复制到剪贴板,粘贴到工作台中"SQL File 1"窗口中。点击闪电图标(左侧的第三个图标)创建样本数据库。
接下来我们需要数据库登录的常用信息
我们为此有一个配置文件:<installation director>VisionSmall\x64\Debug\VisionConfiguration.txt
,它看起来是这样的
Host: localhost
User: root
Password: frob4frob
Database: vision
Port: 3306
修改值以匹配您的MySQL配置。
Vision数据库
数据库中只有一个表
CREATE TABLE `document` (
`DocumentID` int(11) NOT NULL AUTO_INCREMENT,
`Title` varchar(255) DEFAULT NULL,
`Text` text,
PRIMARY KEY (`DocumentID`),
FULLTEXT KEY `ft` (`Title`,`Text`),
FULLTEXT KEY `ftTitle` (`Title`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
搜索时,我们使用名为`ft
`的fulltext索引;在查找自动完成单词时,我们使用名为'ftTitle
'的fulltext索引。
如果您有一个包含多个字段的fulltext
索引,您可以在Microsoft SQL Server查询时选择应包含在搜索中的字段。在MySQL中,始终搜索fulltext
索引中的所有字段,因此我们需要指定附加的fulltext索引'ftTitle
'。
使用C接口查询MySQL
首先,我们需要连接到数据库并获取一个MYSQL指针以供进一步访问
MYSQL *Connect(){
MYSQL *conn; // Connection
// Connect to MySQL
conn = mysql_init(NULL);
if(mysql_real_connect(
conn, Configuration.Host, Configuration.User, Configuration.Password,
Configuration.Database, Configuration.Port, NULL, 0) == NULL) {
fprintf(stderr, "sorry, no database connection ...\n");
return NULL;
}
return conn;
}
启动时,我们使用配置文件VisionConfiguration.txt中的值填充全局Configuration struct
,该文件应与VisionDAL.dll在同一目录中。读取设置的例程是ConfigurationRead
。为了获取当前正在执行模块的路径,它使用Win32 API的GetModuleFileName
。
TCHAR *GetExecutablePath(){
HMODULE hMods[1024];
HANDLE hProcess;
DWORD cbNeeded;
unsigned int i, j;
DWORD processID = 0L;
TCHAR fileName[MAX_PATH];
TCHAR *szModName=(TCHAR *) malloc(MAX_PATH*2);
int size=MAX_PATH*2;
processID = GetCurrentProcessId();
// Get a handle to the process.
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, processID );
if (NULL == hProcess)
return NULL;
if( EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)){
for ( i = 0; i < (cbNeeded / sizeof(HMODULE)); i++ ){
szModName[0] = 0;
fileName[0]=0;
// Get the full path to the module's file.
if ( GetModuleFileNameEx( hProcess, hMods[i], szModName,
size )){
int lastBackSlash=0;
int k=0;
for(k=0; k <(int) wcslen(szModName) -1; k++){
if(szModName[k] == '\\') lastBackSlash = k;
}
if(lastBackSlash == 0) continue;
szModName[lastBackSlash]=0;
for(k=lastBackSlash + 1, j=0; szModName[k] != 0; k++, j++){
fileName[j]=szModName[k];
}
fileName[j]=0;
wStrLower(fileName);
if(!wcscmp(L"visiondal.dll", fileName)){
return szModName;
}
}
}
}
return NULL;
}
我们只想公开一个例程:GetDocuments
。在头文件中的定义
#define FORMAT_TEXT 0
#define FORMAT_JSON 1>
#define FORMAT_DATATABLE 2
__declspec(dllexport) wchar_t* __cdecl GetDocuments(TCHAR *search, int format, int forAutocomplete);
在源文件中的定义
__declspec(dllexport) wchar_t* GetDocuments(TCHAR *search, int format, int forAutocomplete)
声明和定义上的__declspec(dllexport)
效果是将调用添加到VisionDAL.lib文件并导出到VisionDAL.dll文件中。__cdecl
定义了如何调用过程,这里我们使用C风格的调用约定。TCHAR
是一个定义,当定义了UNICODE时,它与WCHAR
相同,否则它是一个简单的char
,在我们的例子中,UNICODE已打开。
- 请注意,存在不同的Unicode格式
- 在C代码中,我们使用2字节值来表示
char
值 - 在MySQL和.NET Framework中,格式是UTF-8,这意味着每个字符使用一个字节,并且只有在需要时才使用多个字节。
- 在控制台应用程序中,您通常为每个
char
使用一个字节,并使用代码页850处理大于127的值。
参数格式为FORMAT_TEXT
或FORMAT_JSON
,用于在文本和JSON之间切换输出。版本2添加了FORMAT_DATATABLE
格式,该格式将输出格式化为DataTable
的标准格式。
如果forAutocomplete
为true
,则只搜索并返回Title
。
VisionDALClientConsole
VisionDALClientConsole
是一个微小的Windows控制台应用程序,用于测试我们的GetDocuments
过程。
它设置了对VisionDAL
项目的引用。它的输出文件与VisionDAL
的输出文件一起转到VisionSmall\x64\Debug。
VisionDALClientConsole
要求输入搜索字符串
,通配符是"*
",它搜索title和text列,并输出GetDocuments
调用返回的文本。
一个示例运行
主例程
int _tmain(int argc,TCHAR* argv[])
{
char c;
TCHAR *result;
TCHAR *search = (TCHAR *)malloc(1000*2);
char *searchA = (char *)malloc(1000);
int retval = 1;
char buffer[32000];
buffer[0]=0;
printf("Search for: ");
/* wscanf doesn't get umlauts */
if(scanf("%[^\n]", searchA) <= 0){
printf("Could not read input - retrieving all Documents \n");
*search=0;
}else{
MultiByteToWideChar(850,0,searchA, -1,search, 999);
}
result=GetDocuments(search, FORMAT_TEXT, 0);
if(result == NULL){
retval = 0;
}else{
WideCharToMultiByte(850,0,result, -1,buffer, 32000,NULL,NULL);
printf("%s", buffer);
}
fflush(stdin);
printf("Press RETURN Key to Exit\n");
getchar();
return retval;
}
在Microsoft C V.12中,有一些例程可以处理Unicode-16字符串。它们有一个起始的附加w
,或者将str
替换为wcs
,例如:wscanf
、wprintf
而不是scanf
和printf
,以及wcslen
而不是strlen
。使用wscanf
未能正确处理带音标的字母。我使用了MultiByteToWideChar
(使用代码页850)来获取宽字符,并使用WideCharToMultiByte
转换回char
。
查询MySQL数据库
上面,我展示了如何连接数据库并获取一个名为conn
的MYSQL指针。
接下来,我们构建SQL查询,这是为自动完成生成列表的代码
mysql_query(conn, "SET NAMES 'utf8'");
if(forAutocomplete){
if(search == NULL || wcslen(search) ==0){
WideCharToMultiByte(CP_UTF8,0,L"SELECT Title from Document LIMIT 20",-1,
sql,1000,NULL,NULL);
}else{
wsprintf(lbuffer, L"SELECT Title, match(Title) against('%ls' IN BOOLEAN MODE)
as Score from Document where match(Title) against('%ls' IN BOOLEAN MODE) > 0.001
order by Score Desc LIMIT 20",
search, search);
WideCharToMultiByte(CP_UTF8,0,lbuffer,-1,sql,1000,NULL,NULL);
}
}
match(Title, Text) against('%ls' IN BOOLEAN MODE)
在Title
和Text
列中搜索搜索字符串
,并返回匹配程度的值。只显示Score
大于0.001
的文档,结果按分数排序。
IN BOOLEAN MODE
表示对多个单词的搜索被解释为或
。
在搜索字符串
中,您可以使用"*
"作为通配符,它将匹配0到n个字符。例如,"as*
"将找到ASP。搜索不区分大小写。有些情况很烦人:"as**
"找不到任何东西;"*sp
"或"*
"不匹配任何内容 - 在Microsoft SQL Server中,您可以在字符串的开头匹配通配符。
获取数据
...
if(mysql_query(conn, sql)) {
fprintf(stderr, "%s\n", mysql_error(conn));
fprintf(stderr, "%s\n", sql);
return NULL;
}
...
// Process results
result = mysql_store_result(conn);
...
while((row = mysql_fetch_row(result)) != NULL) {
...
}else if(format == FORMAT_JSON){
if(!forAutocomplete){
MultiByteToWideChar(CP_UTF8,0,row[0], -1,buffer, 255);
wsprintf(resultBufferp,L"{\"DocumentID\": %s, ", buffer);
resultBufferp+=wcslen(buffer)+wcslen(L"{\"DocumentID\": , ");
if(row[1]){
MultiByteToWideChar(CP_UTF8,0,row[1], -1,buffer, 255);
wsprintf(resultBufferp,L"\"Title\": \"%s\", ", buffer);
resultBufferp+=wcslen(buffer)+wcslen(L"\"Title\": \"\", ");
}else{
wsprintf(resultBufferp,L"\"Title\": null, ");
resultBufferp+=wcslen(L"\"Title\": null, ");
}
if(row[2]){
MultiByteToWideChar(CP_UTF8,0,row[2], -1,buffer, 32000);
wsprintf(resultBufferp,L"\"Text\": \"%s\"},", buffer);
resultBufferp+=wcslen(buffer)+wcslen(L"\"Text\": \"\"},");
}else{
wsprintf(resultBufferp,L"\"Text\": null},");
resultBufferp+=wcslen(L"\"Text\": null},");
}
}
...
}
...
}
mysql_query
将查询发送到服务器。mysql_store_result
将结果准备为一个集合,您可以使用mysql_fetch_row(result)
进行迭代。行是字符串
的数组,忽略列的数据类型。我更喜欢ADO.NET中的类型化列。在.NET中,我们可能会使用StringBuilder
来聚合结果字符串
,在这里我们用malloc分配一个char[]
并增加指针resultBufferp
,然后我们写入。我使用MultiByteToWideChar
转换为WCHAR
。
JSON格式
我决定使用轻量级的JSON格式,用于从网页通过Ajax与Webservice
通信,而不是XML。
使用FORMAT_DATATABLE
时的JSON输出如下所示
[{"DocumentID": 1, "Title": "ASP MVC 4", "Text": "Was für Profis"},
{"DocumentID": 2, "Title": "JQuery", "Text":
"Hat Ajax Support"},{"DocumentID": 3, "Title": "
WebServices", "Text": "Visual C++ kanns nicht"},
{"DocumentID": 4, "Title": "Boost", "Text": "Muss Extra installiert werden"}]
当参数autocomplete为true时的JSON输出如下所示
["ASP MVC 4","JQuery","WebServices","Boost"]
DataTable
期望一个二维数组,看起来像这样
[[1, "ASP MVC 4", "Was für Profis"],[2, "JQuery", "Hat Ajax Support"]]
但也可以使用对象数组,如上所示,用于FORMAT_DATATABLE
格式。
""[]
""表示数组的开始和结束,""{}
""表示对象的开始和结束。在对象中,":
"之前的部分是属性名,之后的部分是值。这与您在JavaScript中编写值时几乎相同。使用JavaScript命令JSON.parse
,您可以获得一个完整的对象,其属性可以通过常用的".
"表示法访问。
托管GetDocuments方法的WebService
我使用"Visual C#/WCF/WCF服务应用程序"模板创建了VisionWeb
项目,它添加了所需的引用,如System.ServiceModel
。
接下来,我们使用NuGet添加所需的JavaScript库。选择"工具/程序包管理器/程序包管理器控制台",然后输入命令
Install-Package jQuery
Install-Package jQuery.UI.Combined
接下来我们在"App-Code/IVisionService.cs"文件中定义服务契约
namespace VisionServices
{
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IVisionService
{
[OperationContract]
[WebInvoke(
Method = "POST",
BodyStyle = WebMessageBodyStyle.WrappedRequest,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
string GetDocuments(string search, int format, int forautocomplete);
}
}
WebInvoke
属性确保服务可以通过Ajax调用。作为方法,我选择了POST
,它将参数发送到HTTP请求的正文中。备选的GET
会将参数编码并显示在URL中。
我们指定请求和响应都以JSON格式发送。当使用多个参数时,必须使用BodyStyle = WebMessageBodyStyle.WrappedRequest
。如果您有零个或一个参数,可以使用WebMessageBodyStyle.Bare
。
WebService的实现
我们在"App-Code/VisionService.cs"中定义GetDocuments
的实现
namespace VisionServices
{
public class PInvoke
{
[DllImport("VisionDAL.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string GetDocuments(string search, int format, int forAutocomplete);
}
public class VisionService : IVisionService
{
public string GetDocuments(string search, int format, int forautocomplete)
{
string result = PInvoke.GetDocuments(search, format, forautocomplete).ToString();
return result;
}
}
}
VisionService.svc的实现
<%@ ServiceHost Language="C#" Debug="true" Service="VisionServices.VisionService"
CodeBehind="App_Code\VisionService.cs" %>
这定义了我们的服务可以调用的端点为"http://<您的Web服务器>:<您的端口>VisionService.svc",调用我们GetDocuments
函数的URL为"http://<您的Web服务器>:<您的端口>/VisionService.svc/GetDocuments"。
Web.config
<?xml version="1.0"?>
<configuration>
<appSettings/>
<system.web>
<httpRuntime/>
<compilation debug="true"/>
</system.web>
<system.serviceModel>
<services>
<service name="VisionServices.VisionService">
<endpoint address="" binding="webHttpBinding"
contract="VisionServices.IVisionService" behaviorConfiguration="webHttpEndpoint"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="webHttpEndpoint">
<webHttp helpEnabled="true"/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="false"
multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
这些设置允许Ajax调用。WCF有很多配置选项。您可以查阅更进一步的文档,例如[2],它可以在Safari上找到。
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
定义了元数据交换的端点。从元数据中,您可以自动生成代码来获取WebService
的代理,例如使用svcutil
。选择"程序/Microsoft Visual Studio 2012/Visual Studio Tools/Developer Command Prompt for VS2012"。输入svcutil https://:8001/VisionService.svc
。会生成一个VisionService.cs文件,在其他情况下,也会生成一个包含WebService
配置的文件。
请注意,Visual Studio有一个用于WebService配置的图形编辑器。它位于"工具/WCF服务配置编辑器"下。
托管网站
启动"设置/控制面板/管理工具/Internet Information Services (IIS)管理器"。如果缺少,请安装IIS。导航到"**应用程序池**",选择一个运行.NET Framework 4.0*的池的名称,或添加一个新的应用程序池。导航到"**网站**"节点,右键单击它并选择"**添加网站...**"。使用Vision作为网站名称,对于应用程序池,选择一个运行.NET Framework 4的池,物理路径使用<vision安装目录>/VisionWeb,端口设置为8001
。在VisionWeb
项目上选择属性,选择Web,选中"**使用自定义Web服务器**",在服务器URL中输入https://:8001
。您可以选择其他选项托管网站,例如在IIS Express中,但如果您不想修改Default.html和DefaultQueryOnly.html文件,则必须将端口设置为8001
。
HTML/JQuery页面
在VisionWeb
版本1项目中,有一个名为"DefaultQueryOnly.html"的单个HTML页面,其中包含HTML和JavaScript内容。在版本2中,解决方案以"Default.html"作为起始页。
再次看看网页的样子
<html>
<head>
<title>Search</title>
<script src="Scripts/jquery-2.0.2.js"></script>
<script src="Scripts/jquery-ui-1.10.3.js"></script>
<link href="Content/themes/base/jquery.ui.autocomplete.css" rel="stylesheet" />
<style type=text/css>
.ui-menu-item {
background: White;
}
.ui-helper-hidden-accessible { display:none; }
</style>
</head>
<code>html</code>
声明它具有HTML 5的文档类型。然后,我们包含必要的JavaScript文件。从JQuery-UI,我们只使用autocomplete小部件,为此我们包含了它的CSS文件。
对于包含.ui-menu-item
类的autocomplete对象,我们将背景设置为白色,否则它是透明的,并且表格的内容会显示出来。.ui-helper-hidden-accessible { display:none; }
删除了autocomplete小部件中一个烦人的帮助消息。
<form>
<label for="search" >Search:</label>
<input type="text" id="search", name="search" />
<input type="button" id="update" name="update" value="Update" />
<div id="result"></div>
</form>
表单中的元素被赋予了ID,以便您可以使用JQuery检索它们,例如$('#result')
。而不是快捷方式$
,您可以使用jQuery,例如[__em all="[object HTMLCollection]"__] jQuery('#result')
。JavaScript调用document.getElementById('result')
具有相同的效果,但JQuery支持各种CSS选择器以及更多内容。
我使用非侵入性JavaScript,这意味着HTML代码不会与JavaScript混合。eventhandler
在$(document).ready(function ()
函数中设置,该函数在页面加载并准备就绪后被调用。
function GetDocuments(e) {
var searchstring = AddWildcards($('#search').val());
$.ajax({
type: 'POST',
url: 'https://:8001/VisionService.svc/GetDocuments',
dataType: 'json',
crossDomain: true,
data: JSON.stringify({ search: searchstring, format: 1, forautocomplete: 0 }),
processData: true,
contentType: "application/json ; charset=utf-8",
success: function (json, textStatus) {
var result = JSON.parse(json);
var display;
display = "";
display += "<table id='mytable' border=2>
<thead><tr><th style='text-align:left' >ID</th>
<th style='text-align:left' >Title</th><th style='text-align:left' >Text</th>
</tr></thead>";
display += "<tbody>";
$.each(result, function (index, value) {
display += "<tr>";
display += "<td>" + value.DocumentID + "</td>";
display += "<td>" + ((value.Title != null)?value.Title:"") + "</td>";
display += "<td>" + ((value.Text != null)?value.Text:"") + "</td>";
display += "</tr>";
});
display += "</tbody></table>";
$('#result').empty()
$('#result').html(display);
},
error: function (xhr, textStatus, errorThrown) {
alert('An error occurred! ' + (errorThrown ? errorThrown : xhr.status) +
" xhr: " + xhr + " textStatus: " + textStatus);
}
});
}
当您单击"更新"按钮或在搜索字段中按Enter键时,将执行GetDocuments
调用。它执行全文查询并在HTML表中显示结果。
我们在searchstring
变量中获取搜索表单字段的值,并在AddWildcards
中为它的每个单词添加通配符*,如果它不存在的话。JQuery具有AJAX支持,例如$.ajax
调用。
您可以在这里找到此调用的参考:jQuery.ajax()。
url:
指向我们在WCF应用程序中配置的地址。正如我们在WCF应用程序中设置的那样,我们使用JSON作为数据格式。在success:
函数中(它在ajax调用成功时异步调用),我们在json
变量中获得我们GetDocuments
调用的输出。一个简单的调用JSON.parse(json)
,会得到一个完整的JavaScript对象,我们从中生成一个HTML表。变量result
是一个JavaScript对象数组。jQuery的$.each
调用迭代数组,并调用给定的函数,参数为当前数组元素的索引和当前位置的对象。使用$('#result').html(display)
,我们将HTML字符串
display
显示为我们结果div
的HTML。在data:
下,我们将调用参数指定为JavaScript对象,并使用JSON.stringify
调用为传输做准备。当发生错误时,error:
后面的代码将被执行。
自动完成的工作原理
在我们的JavaScript代码的开头,我们声明一个全局变量来存储autocomplete
的单词在一个数组中: var Documents = [];
。GetAutocomplete
函数填充Documents
数组。Autocomplete
函数
function GetAutocomplete(e) {
var searchstring = $('#search').val();
if (searchstring.length > 0) {
if (searchstring[searchstring.length - 1] != "*") {
searchstring += "*";
}
}
$.ajax({
type: 'POST',
url: 'https://:8001/VisionService.svc/GetDocuments',
dataType: 'json',
data: JSON.stringify({ search: searchstring, format: 1, forautocomplete: 1}),
processData: true,
async: false,
contentType: "application/json ; charset=utf-8",
success: function (json, textStatus) {
Documents = JSON.parse(json);
},
error: function (xhr, textStatus, errorThrown) {
alert('An error occurred! ' + (errorThrown ? errorThrown : xhr.status) +
" xhr: " + xhr + " textStatus: " + textStatus);
}
});
}
这看起来与GetDocuments
函数非常相似。success函数仅使用JSON.parse
解析我们Webservice
的输出来更新Documents
变量。注意async: false
,它会导致调用同步进行。autocomplete小部件将调用我们的GetAutocomplete
函数并立即显示Documents
。
在$(document).ready(function ()
中从autocomplete小部件初始化
$("#search").autocomplete({
source: function (request, callback) {
GetAutocomplete();
callback(Documents);
},
open: function (event) {
var $ul = $(this).autocomplete("widget");
}
});
您可以在这里找到关于autocomplete
的信息。
处理搜索框中的**[RETURN]**键
$('#search').bind("keydown", GetInput);
function GetInput(e) {
if (e.keyCode == 13) {
e.preventDefault();
GetDocuments(e);
$('#search').autocomplete("close");
}
}
e.preventDefault();
停止当前事件的处理。
调试
您可以在Web浏览器中输入URLhttps://:8001/VisionService.svc。如果发生服务激活错误,将显示一条消息,例如无法加载VisionDAL.dll,或未安装WCF,这将导致一条关于缺少处理程序的错误消息。您可以使用Fiddler等工具检查HTTP通信。
调试VisionDAL
时,运行VisionWeb
不起作用。无调试地启动Vision并附加到w3wp.exe。
VisionSmall 版本2 - 实现CRUD操作
到目前为止,我向您展示了DefaultQuery
,现在我们切换到Default.html。
我们点击"工具/程序包管理器/管理解决方案的NuGet程序包...",搜索jquery.datatables
,然后添加结果包。
我们添加一个名为oTable
的全局变量来保存对创建的表的引用。我们将GetDocuments
修改为使用DataTable
。
function GetDocuments(e) {
var searchstring = AddWildcards($('#search').val());
$.ajax({
type: 'POST',
url: 'https://:8001/VisionService.svc/GetDocuments',
dataType: 'json',
data: JSON.stringify({ search: searchstring, format: 2, forautocomplete: 0 }),
contentType: "application/json ; charset=utf-8",
success: function (json, textStatus) {
var result = JSON.parse(json);
var display;
display = "";
display += "<table id='mytable' border=2>
<thead><tr><th style='text-align:left' >ID</th>
<th style='text-align:left' >Title</th>
<th style='text-align:left' >Text</th></tr></thead>";
display += "<tbody>";
display += "</tbody>";
display += "</table>";
if (oTable) {
$('#mytable').remove();
oTable = null;
}
$('#result').empty()
$('#result').html(display);
oTable = $('#mytable').dataTable({
"aaData": result,
"bPaginate": false,
"bLengthChange": false,
"bFilter": false,
"bSort": true,
"bInfo": false,
"bAutoWidth": false
});
...
}
...
}
oTable = $('#mytable').dataTable(
将HTML表转换为DataTable
。表的内容通过 "aaData": result,
属性传递。表头与版本1一样,通过发出HTML代码生成。相反,您可以为DataTable
指定列。
function makeSelectable(e) {
if ($(this).hasClass('row_selected')) {
$(this).removeClass('row_selected');
}
else {
$(this).addClass('row_selected');
}
};
实现删除
首先,通过在我们的GetDocuments
函数中调用oTable.$("tbody tr").click(makeSelectable)
来使tablerows
可选择。之后,只有第一列是可选择的,因为我们将editable
应用于所有列,除了第一列"ID
"。
/* Get the rows which are currently selected */
function fnGetSelected(oTableLocal) {
var aReturn = new Array();
var aTrs = oTableLocal.fnGetNodes();
for (var i = 0 ; i < aTrs.length ; i++) {
if ($(aTrs[i]).hasClass('row_selected')) {
aReturn.push(aTrs[i]);
}
}
return aReturn;
}
...
$('#delete').click(function () {
if(!oTable)return;
var ids = new Array();
var anSelected = fnGetSelected(oTable);
if (anSelected.length == 0) return;
for (i = anSelected.length - 1; i >= 0; i--) {
// anSelected[i].cells[0].innerText works with IE and Chrome
// but not in Firefox(version 23.01)
if (anSelected[i].cells[0].innerHTML != "" && anSelected[i].cells[0].innerHTML != null) {
ids.push(parseInt(anSelected[i].cells[0].innerHTML));
oTable.fnDeleteRow(anSelected[i], null, false);
}
}
oTable.fnDraw();
$.ajax({
type: 'POST',
url: 'https://:8001/VisionService.svc/DeleteDocuments',
dataType: 'json',
data: JSON.stringify({"ids": ids}),
contentType: "application/json ; charset=utf-8",
asyn: false,
success: function (json, textStatus) {
var i = JSON.parse(json);
if(i != ids.length) alert("Error: should delete " + ids.length +
" but deleted " + i + " records");
},
error: function (xhr, textStatus, errorThrown) {
alert('An error occurred! ' + (errorThrown ? errorThrown : xhr.status) +
" xhr: " + xhr + " textStatus: " + textStatus);
}
});
});
...
我们迭代oTable
中选定的表行,并调用DataTable
函数fnDeleteRow
,该函数从表中删除给定的行。
Ajax调用由WCF项目中的DeleteDcouments
处理
public int DeleteDocuments(int[] ids)
{
int retval = -1;
IntPtr intPtr = Marshal.AllocHGlobal(ids.Length * sizeof(int));
Marshal.Copy(ids, 0, intPtr, ids.Length);
retval = PInvoke.DeleteDocuments(intPtr, ids.Length);
Marshal.FreeHGlobal(intPtr);
return retval;
}
请注意,WCF足够智能,可以自动将JSON数组转换为C# int数组。但为了调用我们的C例程,我们必须使用Marshal函数准备一个原始C数组。
DAL中的C例程
...
if(mysql_query(conn, buffer)) {
...
printf("%i records found\n", retval=mysql_affected_rows(conn));
该例程首先生成一个SQL字符串,例如
Delete from Document where DocumentID in (2,4)
. mysql_query
将SQL字符串发送到服务器。请注意,无论调用是读取、更新、删除还是新语句,都使用mysql_query
。mysql_affected_rows
返回最后mysql_query
语句影响的行数,在本例中为已删除的行数。
实现新增
...
$("#new").unbind('click');
$('#new').click(function ()
{
if(!oTable)return;
var id=-1; // generated on the server
var Title=null;
var Text=null;
$.ajax({
type: 'POST',
url: 'https://:8001/VisionService.svc/NewDocument',
dataType: 'json',
data: JSON.stringify({"title": Title, "text": Text }),
contentType: "application/json ; charset=utf-8",
aysnc: false,
success: function (json, textStatus) {
id = JSON.parse(json);
if (id == -1) {
alert("Error: inserting a new record failed");
return -1;
}
var values = new Array(id, Title, Text);
oTable.fnAddData(values);
// var tds = oTable.$("tr:last-child td:gt(0)"); // doesn't work
$("#mytable tr:last-child td:gt(0)").addClass("editable").editable(makeEditable,
{ "placeholder": "" });
oTable.$("tbody tr:last-child").click(makeSelectable);
$("#mytable tr:last-child td:eq(1)").click();
},
error: function (xhr, textStatus, errorThrown) {
alert('An error occurred! ' + (errorThrown ? errorThrown : xhr.status) +
" xhr: " + xhr + " textStatus: " + textStatus);
}
});
});
...
目前Title
和Text
未被使用。我们在数据库中生成一个空记录,这会导致生成一个新的DocumentID
,将其设置在新记录中并返回给调用的ajax函数。提交的SQL如下所示
Insert Document(Title) values(NULL)
我们使用DataTable
调用oTable.fnAddData(values)
将新记录添加到表中。然后我们需要使行可选择和可编辑。
实现更新
我们从editable下载jQuery editable。
在GetDocuments
函数中,我们绑定了来自New按钮的click事件。
...
oTable.$('td:gt(0)').addClass("editable").editable
(makeEditable,{ "placeholder": "" }); // when placeholder is not set empty cells display
// "Click to edit"
...
function makeEditable(value, settings) {
var columnName = this.parentNode.parentNode.parentNode.rows[0].cells[this.cellIndex].innerHTML;
var id = parseInt(this.parentNode.cells[0].innerHTML);
$.ajax({
type: 'POST',
url: 'https://:8001/VisionService.svc/UpdateDocument',
dataType: 'json',
data: JSON.stringify({ "id": id, "columnName": columnName, "newValue": value }),
async: false,
contentType: "application/json ; charset=utf-8",
success: function (json, textStatus) {
var i = JSON.parse(json);
if (i != 1) alert("Error: should update " + ids.length + " but updated " + i + " records");
},
error: function (xhr, textStatus, errorThrown) {
alert('An error occurred! ' + (errorThrown ? errorThrown : xhr.status) +
" xhr: " + xhr + " textStatus: " + textStatus);
}
});
return (value);
};
当一个HTML元素被设置为可编辑时,它会编辑对象,当您单击它一次。新数据将被提交。因此,每次更新都会修改单个列。
生成的SQL如下所示
Update Document set Title='new value' where DocumentID=4
参考文献
- [1]Michael Kofler;MySQL 5: Einführung, Programmierung, Referenz;Addison-Wesley Verlag 2009
- [2]Steven Cheng; Microsoft Windows Communication Foundation 4.0 Cookbook for Developing SOA Applications;Packt Publishing 2010
- [3]DataTables
- [4]editable