WORD写的测试
Test
O 引言
近年来在机械制造加工行业,随着”甩图板”工程的迅猛发展, CAD 应用及企业信息化建设方面已取得了巨大成效,企业逐渐积累了大量的图纸、文档等技术资料。这些电子图档是企业产品设计的主要信息资源,为企业今后进一步实现信息化集成打下了良好的基础。但目前国内绝大部分企业还只停留在单纯使用CAD 进行产品设计的初期阶段,也没考虑到如何对大量的设计技术图档进行合理、有效、安全的管理,设计资料的重用率低;设计人员仍各自为政,难以避免图档的重复设计;设计数据的传递只能通过最传统的文件传递方式来完成,因此数据安全性无法保证,实对交流不便,对图文档的分类管理也无从谈起,图档版本混乱,更无法实时监控图档的整个设计流程;当然还有很多其它弊端。内地的中小型企业此类现象尤为突出。
1 系统设计目标
1.1 提高图文档的管理水平
对图文档及用户进行分级管理,有效控制未授权人员的非法使用;建立与图文档相对应的产品结构树,使产品零件与图文裆进行关联,有利于对各类产品及相关数据信息的集中管理。
1.2 提高工作效率
对各类图文档进行综合管理,便于设计图挡的适时更新和共享,加快技术文挡的查询速度;对同一产品零件图挡的不同版本进行管理,从前提高设计部门的运作效率;通过权限设置实现图文挡的安全共享。
1.3 确保技术文档安全性
建立两种数据库的备份机制,一种是利用DBMS 系统提供的维护计划自动定时备份数据库,另一种通过客户端操作实现备份或恢复。另外,如果在硬件条件允许的情况下可增加一台数据库备份服务器。在客户端,连接数据库服务器设置参数经过加密后以二进制方式存入配置文件,并将此配置文件属性设置为系统、隐藏,以防数据库账号外泄。要对用户进行身份验证,并对用户密码进行双重加密后保存到数据库内,避免用户通过读取数据库获取用户密码。另外,用户要经过授权后方可访问其图文档数据信息。所有用户操作内容都记入日志,以备后查。
1 .4提供数据集中、管理分散的操作模式
本软件采用C/S 构架;通过企业局域网,客户端可以分布在网络的任何节点上;最终的操作结果均存储在数据库服务器上,通过网络实现多个用户之间的协同工作。
2 系统解决方案
系统设计采用基于.Net 技术的Visual C# 为前台开发语言和ADO.NET 数据库访问接口技术,并以SQL Server2000 作为网络数据库系统平台,充分发挥C# 真正面向对象(OOP) 的编程优点,系统采用N 层结构模式,在系统的实现过程中,分别将相对独立的功能模块及代码封装在不同的类中,通过创建类的实例可方便调用各功能函数。
系统采用一级身份验证和二级权限分配机制。用户登录后首先从用户权限表获取该用户的权限。在产品结构树中只显示该用户有预览权的项目目录。
用户(或部门)对产品结构树中某一项(或组)的权限级别有:入库、预览、修改、删除、出库及批量出库六种。系统管理员具有所有权限,并能对各部门负责人进行委托授权,有权查看及管理所有日志,并对系统参数进行设置,如设置图档的保留版本数,图档归档流程及再修改授权流程等。系统操作流程及功能模块如图l 所示。

3 系统的实现
本系统主要完成企业设计技术文挡的分类管理,实现对各类技术文档的预览、版本管理、入库、出库、批量出库、图纸与产品结构树的动态关联等。具有用户权限的动态设置及自顶向下的委托授权机制,同时强调系统的安全性和稳定性;数据库结构的设计符合2NF 范式;在代码编写过程中,将可能产生错误的程序代码加入异常处理机制,捕捉错误并加以适当解决,尽量避免异常现象的发生。以下对主要的几个核心功能的实现方法加以阐述。
3.1 产品结构树设计

具有的基本功能:通过右键菜单可动态设计产品结构树项目。操作界面如图2 所示。
此功能的实现主要是基于数据库的树状结构的实时显示,经分析采用递归算法来实现,并充分利用C# 语言的OOP 程序开发优点,添加基于数据库的树状目录结构动态构建函数及公用类。代码如下:
public class GenTree
{ public void genTreeNode(TreeNodeCollection nodes,
DataRowColleclion data, string parentld)
{ foreach (DataRow dr in data)
{if(dr["parentld"[.ToString() == parentld)
{ TreeNode newNode = new TreeNode(dn"name"].
ToString()};
newNode.Name =dr["id"].ToString();
newNode.Tag =drrisChild1;
if(bool)dr["isChild"]
{ newNode.lmagelndex =3; nodes. Add(newNode);}
else
{ newNode.lmagelndex =0; nodes.Add(newNode);
genTreeNode(newNode.Nodes , data,
dr["id"].ToString()}
}
}
}
}
}
在需要显示产品结构树窗体的Load 事件内,置入下面的代码:
private void inputDocFrm_Load(object sender, EventArgs e)
{ SqlDataAdapter cmd =new SqlDataAdapter (”select* from
M_ProductorTree “sqlConnection1 );
cmd.Fill(ds,”treeInfo”); GenTree myTree =new GenTree();
myTree.genTreeNode (ProTree.Nodes [O].Nodes,ds.Tables
["treelnfo"].Rows,
ProTree.Nodes[O].Name};
}// ProTree 是TreeView 控件实例
以上公用类也可在人员部门结构树及其它结构树的实现时直接调用。
3.2 图文档文件的版本管理

假设系统参数设置图文档需保留五个版本,最新版本保留在M_Doc 数据表内,其它版本保存到M_DocBak.数据表内。图文档存储麦、产品结构树表和图档关联表结构关系如图3 所示。
M_DocBak 表和M_Doc 表内的Doc 字段为Image 类型,用于存放文件。设计更新版本的存储过程UpdateDoc ,最后在业务逻辑层直接调用此存储过程并指定参数完成版本的更新,此模块采用分层设计模式来实现。创建对数据表M_DocFile 的更新图文档存储过程,代码如下:
CREATE PROCEDURE UpdateDoc(@DclD int,@Doc image,
@Modifier nvarchar(10),@Demo nvarchar(30),@Vercount int,
@Newver int output)
AS declare @baksum int;
begin transaction
select @baksum=count(*) from M_DocBak;
// 存储现有版本数
if(@baksum<@Vercount)
begin
Insert into dbo.M_DocBak(DocID, Ver, Doc, DocType,
Modifier, Demo)
select@D00ID , @baksum+1 ,Doc , docType , inputName ,
Demo from dbo.M Doc
where docld=@DoclD;
update dbo.M_Doc set Doc=@Doc,inputName=@Modifier,
inputDate=getdate() ,authorized =false
where docId=@DocID;
select @Newver=@baksum+1;
return;
end //设置返回版本号
else
begin // 如果版本已满
update dbo.M_DocBak set Ver=Ver-1
where DocID=@DoclD; // 版本号均减1
declare @Tinyver nehar(6):
select @Tinyver=min(Ver) from dbo.M_DocBak
where DocID=@DoclD;
delete from dbo.M_DocBak where Ver-@Tinyver;
Insert into dbo.M_DocBak(DocID, Ver, Doc, DocType,
Modifier, Demo)
select @DocID , @baksum , Doc ,也cType.inputName ,@Demo
from dbo.M_Doc where docld=@DocID;
update dbo.M_Doc set Doc=@Doc.inputName=@Modifier,
inputDate=getdate(). authorized=false
where docld=@DoclD;
select @Newver=@baksum;
return:
end commit GO// 提交事务
在其它模块中可调用以上存储过程实现图档版本的更新。
3.3 图文挡的实时预览
传统的文档预览及修改功能一般是通过OLE 技术来实现的。OLE 即对象连接与嵌入,是应用程序共享对象的一种工业标准,为程序协同工作提供了一种方式。但利用OLE 技术首先需要客户方计算机已安装有此应用程序,且占用内存多,预览速度慢,不利于软件的后期部署。另外也可调用AutoCAD 自带的Vola View Express 来预览dwg 、dwf 及dxf 等常用格式的图档文件,但也需要客户端先安装有此类应用程序。
综合以上考虑,本系统采用由AutoDesk 公司提供的一个ActiveX 控件来实现对dwg 文件的预览功能。此ActiveX 控件以ocx 格式提供.对应AutoCAD 安装盘内MIGRATE\AMA\Ocx 目录下的DwgThumbnail. ocx 文件。操作步骤如下:
首先将DwgThumbnail. ocx 文件从安装光盘考入Winodws系统目录下的System32 目录内,然后在Windows 的开始→运行中输入regsvr32 DwgThumbnail.00x 并确定,注册此控件。
打开C# 开发环境,通过菜单P叫eet→Add Reference… 调出刚拷入系统目录下的DwgThumbnaiL.ocx 文件,此时在C# 的解决方案管理器的References 目录下自动添加了DWGTHUMBNAILL 、stdole 和StdType 三个ActiveX 引用。
4 系统的安装部署
本系统是基于N盯架构和SQL Server2,创始后台DBMS的一个应用软件,必须首先安装.NET FrameWork 框架,然后再安装SQL Server2则客户端连接(数据库连接驱动程序) ,系统方能正常运行。
5 结束语
本系统实现了企业对大量图纸、文件、技术资料的规范管理,可以将企业的所有设计文档全部按产品结构树进行分类管理,并且提供了完善的版本及权限管理机制,确保技术文档管理的安全性和有效性;另外,系统提供的图档关联功能可以将同一图档关联到其它多个产品结构树分支上,实现→图多用,保证了图档的一致性。
本系统能改善企业现有的图文档管理现状,提高管理规范性、安全保密性、操作可控性和版本一致性,从而提高产品数据信息的利用率,改善电子设计资料管理混乱的局面,为提高企业的技术创新能力和产品设计及研发效率起到非常重要的作用,同时也为后期的PDM 系统集成奠定良好的基础。
翻译至:http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
作者网址:
Most users at one time or another have dealt with hierarchical data in a SQL database and no doubt learned that the management of hierarchical data is not what a relational database is intended for. The tables of a relational database are not hierarchical (like XML), but are simply a flat list. Hierarchical data has a parent-child relationship that is not naturally represented in a relational database table.
我们认为,分层的数据结合是这样的,每个项有一个父节点同时可以有0个或多个子项(根项父节点)。分层数据在大量的数据库应用程序中使用,包括论坛、邮件列表主题、商业组织结构、内容管理分类和产品分类。我们采用产品商店的产品分类作为示例:

大多数分层数据的组织都和上述类似,本文我们将介绍两种模式处理MySQL中的分层数据。我们从毗邻目录模式谈起吧。
通常我们对于上述的示例分配采用下面的方式的存储与数据表之中:
CREATE TABLE category( category_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(20) NOT NULL, parent INT DEFAULT NULL); INSERT INTO category VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2), (4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1), (7,'MP3 PLAYERS',6),(8,'FLASH',7), (9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6); SELECT * FROM category ORDER BY category_id; +-------------+----------------------+--------+ | category_id | name | parent | +-------------+----------------------+--------+ | 1 | ELECTRONICS | NULL | | 2 | TELEVISIONS | 1 | | 3 | TUBE | 2 | | 4 | LCD | 2 | | 5 | PLASMA | 2 | | 6 | PORTABLE ELECTRONICS | 1 | | 7 | MP3 PLAYERS | 6 | | 8 | FLASH | 7 | | 9 | CD PLAYERS | 6 | | 10 | 2 WAY RADIOS | 6 | +-------------+----------------------+--------+ 10 rows in set (0.00 sec)
在毗邻目录模式中,表中的每一个记录都包含一个父节点的指向。顶端的对象(本例中的电子产品)的父指向为NULL。毗邻目录模式的优点是简单、可以简单的看到mp3播放器是便携式电子产品的子项,同时也是电子产品的子项。While the adjacency list model can be dealt with fairly easily in client-side code, working with the model can be more problematic in pure SQL.
第一常见的工作便是用这些分层数据来显示一个完成树,
The first common task when dealing with hierarchical data is the display of the entire tree, usually with some form of indentation. The most common way of doing this is in pure SQL is through the use of a self-join:
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4 FROM category AS t1 LEFT JOIN category AS t2 ON t2.parent = t1.category_id LEFT JOIN category AS t3 ON t3.parent = t2.category_id LEFT JOIN category AS t4 ON t4.parent = t3.category_id WHERE t1.name = 'ELECTRONICS'; +-------------+----------------------+--------------+-------+ | lev1 | lev2 | lev3 | lev4 | +-------------+----------------------+--------------+-------+ | ELECTRONICS | TELEVISIONS | TUBE | NULL | | ELECTRONICS | TELEVISIONS | LCD | NULL | | ELECTRONICS | TELEVISIONS | PLASMA | NULL | | ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH | | ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS | NULL | | ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL | +-------------+----------------------+--------------+-------+ 6 rows in set (0.00 sec)
We can find all the leaf nodes in our tree (没有子项) by using a LEFT JOIN query:
SELECT t1.name FROM category AS t1 LEFT JOIN category as t2 ON t1.category_id = t2.parent WHERE t2.category_id IS NULL; +--------------+ | name | +--------------+ | TUBE | | LCD | | PLASMA | | FLASH | | CD PLAYERS | | 2 WAY RADIOS | +--------------+
The self-join also allows us to see the full path through our hierarchies:
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4 FROM category AS t1 LEFT JOIN category AS t2 ON t2.parent = t1.category_id LEFT JOIN category AS t3 ON t3.parent = t2.category_id LEFT JOIN category AS t4 ON t4.parent = t3.category_id WHERE t1.name = 'ELECTRONICS' AND t4.name = 'FLASH'; +-------------+----------------------+-------------+-------+ | lev1 | lev2 | lev3 | lev4 | +-------------+----------------------+-------------+-------+ | ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH | +-------------+----------------------+-------------+-------+ 1 row in set (0.01 sec)
The main limitation of such an approach is that you need one self-join for every level in the hierarchy, and performance will naturally degrade with each level added as the joining grows in complexity.
Working with the adjacency list model in pure SQL can be difficult at best. Before being able to see the full path of a category we have to know the level at which it resides. In addition, special care must be taken when deleting nodes because of the potential for orphaning an entire sub-tree in the process (delete the portable electronics category and all of its children are orphaned). Some of these limitations can be addressed through the use of client-side code or stored procedures. With a procedural language we can start at the bottom of the tree and iterate upwards to return the full tree or a single path. We can also use procedural programming to delete nodes without orphaning entire sub-trees by promoting one child element and re-ordering the remaining children to point to the new parent.
What I would like to focus on in this article is a different approach, commonly referred to as the Nested Set Model. In the Nested Set Model, we can look at our hierarchy in a new way, not as nodes and lines, but as nested containers. Try picturing our electronics categories this way:

Notice how our hierarchy is still maintained, as parent categories envelop their children.We represent this form of hierarchy in a table through the use of left and right values to represent the nesting of our nodes:
CREATE TABLE nested_category ( category_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(20) NOT NULL, lft INT NOT NULL, rgt INT NOT NULL ); INSERT INTO nested_category VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4), (4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19), (7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13), (9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18); SELECT * FROM nested_category ORDER BY category_id; +-------------+----------------------+-----+-----+ | category_id | name | lft | rgt | +-------------+----------------------+-----+-----+ | 1 | ELECTRONICS | 1 | 20 | | 2 | TELEVISIONS | 2 | 9 | | 3 | TUBE | 3 | 4 | | 4 | LCD | 5 | 6 | | 5 | PLASMA | 7 | 8 | | 6 | PORTABLE ELECTRONICS | 10 | 19 | | 7 | MP3 PLAYERS | 11 | 14 | | 8 | FLASH | 12 | 13 | | 9 | CD PLAYERS | 15 | 16 | | 10 | 2 WAY RADIOS | 17 | 18 | +-------------+----------------------+-----+-----+
之所以我们使用 lft 和 rgt ,因为 left 和 right 是MySQL的保留关键字, 参见保留关键字列表 http://dev.mysql.com/doc/mysql/en/reserved-words.html 。
那么我们想知道怎么去设置左右的值呢?我们从最左边的边框开始编号,一直到最右边:

这个设计转化成树结构就是这个样子:

When working with a tree, we work from left to right, one layer at a time, descending to each node’s children before assigning a right-hand number and moving on to the right. This approach is called the modified preorder tree traversal algorithm.
We can retrieve the full tree through the use of a self-join that links parents with nodes on the basis that a node’s lft value will always appear between its parent’s lft and rgt values:
SELECT node.name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt AND parent.name = 'ELECTRONICS' ORDER BY node.lft; +----------------------+ | name | +----------------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | PORTABLE ELECTRONICS | | MP3 PLAYERS | | FLASH | | CD PLAYERS | | 2 WAY RADIOS | +----------------------+
Unlike our previous examples with the adjacency list model, this query will work regardless of the depth of the tree. We do not concern ourselves with the rgt value of the node in our BETWEEN clause because the rgt value will always fall within the same parent as the lft values.
Finding all leaf nodes in the nested set model even simpler than the LEFT JOIN method used in the adjacency list model. If you look at the nested_category table, you may notice that the lft and rgt values for leaf nodes are consecutive numbers. To find the leaf nodes, we look for nodes where rgt = lft + 1:
SELECT name FROM nested_category WHERE rgt = lft + 1; +--------------+ | name | +--------------+ | TUBE | | LCD | | PLASMA | | FLASH | | CD PLAYERS | | 2 WAY RADIOS | +--------------+
With the nested set model, we can retrieve a single path without having multiple self-joins:
SELECT parent.name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.name = 'FLASH' ORDER BY parent.lft; +----------------------+ | name | +----------------------+ | ELECTRONICS | | PORTABLE ELECTRONICS | | MP3 PLAYERS | | FLASH | +----------------------+
We have already looked at how to show the entire tree, but what if we want to also show the depth of each node in the tree, to better identify how each node fits in the hierarchy? This can be done by adding a COUNT function and a GROUP BY clause to our existing query for showing the entire tree:
SELECT node.name, (COUNT(parent.name) - 1) AS depth FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt GROUP BY node.name ORDER BY node.lft; +----------------------+-------+ | name | depth | +----------------------+-------+ | ELECTRONICS | 0 | | TELEVISIONS | 1 | | TUBE | 2 | | LCD | 2 | | PLASMA | 2 | | PORTABLE ELECTRONICS | 1 | | MP3 PLAYERS | 2 | | FLASH | 3 | | CD PLAYERS | 2 | | 2 WAY RADIOS | 2 | +----------------------+-------+
We can use the depth value to indent our category names with the CONCAT and REPEAT string functions:
SELECT CONCAT( REPEAT(' ', COUNT(parent.name) - 1), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
+-----------------------+
| name |
+-----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+-----------------------+
Of course, in a client-side application you will be more likely to use the depth value directly to display your hierarchy. Web developers could loop through the tree, adding <li></li> and <ul></ul> tags as the depth number increases and decreases.
When we need depth information for a sub-tree, we cannot limit either the node or parent tables in our self-join because it will corrupt our results. Instead, we add a third self-join, along with a sub-query to determine the depth that will be the new starting point for our sub-tree:
SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth FROM nested_category AS node, nested_category AS parent, nested_category AS sub_parent, ( SELECT node.name, (COUNT(parent.name) - 1) AS depth FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.name = 'PORTABLE ELECTRONICS' GROUP BY node.name ORDER BY node.lft )AS sub_tree WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt AND sub_parent.name = sub_tree.name GROUP BY node.name ORDER BY node.lft; +----------------------+-------+ | name | depth | +----------------------+-------+ | PORTABLE ELECTRONICS | 0 | | MP3 PLAYERS | 1 | | FLASH | 2 | | CD PLAYERS | 1 | | 2 WAY RADIOS | 1 | +----------------------+-------+
This function can be used with any node name, including the root node. The depth values are always relative to the named node.
Imagine you are showing a category of electronics products on a retailer web site. When a user clicks on a category, you would want to show the products of that category, as well as list its immediate sub-categories, but not the entire tree of categories beneath it. For this, we need to show the node and its immediate sub-nodes, but no further down the tree. For example, when showing the PORTABLE ELECTRONICS category, we will want to show MP3 PLAYERS, CD PLAYERS, and 2 WAY RADIOS, but not FLASH.
This can be easily accomplished by adding a HAVING clause to our previous query:
SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth FROM nested_category AS node, nested_category AS parent, nested_category AS sub_parent, ( SELECT node.name, (COUNT(parent.name) - 1) AS depth FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.name = 'PORTABLE ELECTRONICS' GROUP BY node.name ORDER BY node.lft )AS sub_tree WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt AND sub_parent.name = sub_tree.name GROUP BY node.name HAVING depth <= 1 ORDER BY node.lft; +----------------------+-------+ | name | depth | +----------------------+-------+ | PORTABLE ELECTRONICS | 0 | | MP3 PLAYERS | 1 | | CD PLAYERS | 1 | | 2 WAY RADIOS | 1 | +----------------------+-------+
If you do not wish to show the parent node, change the HAVING depth <= 1 line to HAVING depth = 1.
Let’s add a table of products that we can use to demonstrate aggregate functions with:
CREATE TABLE product(
product_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(40),
category_id INT NOT NULL
);
INSERT INTO product(name, category_id) VALUES('20" TV',3),('36" TV',3),
('Super-LCD 42"',4),('Ultra-Plasma 62"',5),('Value Plasma 38"',5),
('Power-MP3 5gb',7),('Super-Player 1gb',8),('Porta CD',9),('CD To go!',9),
('Family Talk 360',10);
SELECT * FROM product;
+------------+-------------------+-------------+
| product_id | name | category_id |
+------------+-------------------+-------------+
| 1 | 20" TV | 3 |
| 2 | 36" TV | 3 |
| 3 | Super-LCD 42" | 4 |
| 4 | Ultra-Plasma 62" | 5 |
| 5 | Value Plasma 38" | 5 |
| 6 | Power-MP3 128mb | 7 |
| 7 | Super-Shuffle 1gb | 8 |
| 8 | Porta CD | 9 |
| 9 | CD To go! | 9 |
| 10 | Family Talk 360 | 10 |
+------------+-------------------+-------------+
Now let’s produce a query that can retrieve our category tree, along with a product count for each category:
SELECT parent.name, COUNT(product.name) FROM nested_category AS node , nested_category AS parent, product WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.category_id = product.category_id GROUP BY parent.name ORDER BY node.lft; +----------------------+---------------------+ | name | COUNT(product.name) | +----------------------+---------------------+ | ELECTRONICS | 10 | | TELEVISIONS | 5 | | TUBE | 2 | | LCD | 1 | | PLASMA | 2 | | PORTABLE ELECTRONICS | 5 | | MP3 PLAYERS | 2 | | FLASH | 1 | | CD PLAYERS | 2 | | 2 WAY RADIOS | 1 | +----------------------+---------------------+
This is our typical whole tree query with a COUNT and GROUP BY added, along with a reference to the product table and a join between the node and product table in the WHERE clause. As you can see, there is a count for each category and the count of subcategories is reflected in the parent categories.
Now that we have learned how to query our tree, we should take a look at how to update our tree by adding a new node. Let’s look at our nested set diagram again:

If we wanted to add a new node between the TELEVISIONS and PORTABLE ELECTRONICS nodes, the new node would have lft and rgt values of 10 and 11, and all nodes to its right would have their lft and rgt values increased by two. We would then add the new node with the appropriate lft and rgt values. While this can be done with a stored procedure in MySQL 5, I will assume for the moment that most readers are using 4.1, as it is the latest stable version, and I will isolate my queries with a LOCK TABLES statement instead:
LOCK TABLE nested_category WRITE;
SELECT @myRight := rgt FROM nested_category
WHERE name = 'TELEVISIONS';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;
INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight + 1, @myRight + 2);
UNLOCK TABLES;
We can then check our nesting with our indented tree query:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
+-----------------------+
| name |
+-----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| GAME CONSOLES |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+-----------------------+
If we instead want to add a node as a child of a node that has no existing children, we need to modify our procedure slightly. Let’s add a new FRS node below the 2 WAY RADIOS node:
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft FROM nested_category
WHERE name = '2 WAY RADIOS';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myLeft;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myLeft;
INSERT INTO nested_category(name, lft, rgt) VALUES('FRS', @myLeft + 1, @myLeft + 2);
UNLOCK TABLES;
In this example we expand everything to the right of the left-hand number of our proud new parent node, then place the node to the right of the left-hand value. As you can see, our new node is now properly nested:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt GROUP BY node.name ORDER BY node.lft; +-----------------------+ | name | +-----------------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | GAME CONSOLES | | PORTABLE ELECTRONICS | | MP3 PLAYERS | | FLASH | | CD PLAYERS | | 2 WAY RADIOS | | FRS | +-----------------------+
The last basic task involved in working with nested sets is the removal of nodes. The course of action you take when deleting a node depends on the node’s position in the hierarchy; deleting leaf nodes is easier than deleting nodes with children because we have to handle the orphaned nodes.
When deleting a leaf node, the process if just the opposite of adding a new node, we delete the node and its width from every node to its right:
LOCK TABLE nested_category WRITE; SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM nested_category WHERE name = 'GAME CONSOLES'; DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight; UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight; UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight; UNLOCK TABLES;
And once again, we execute our indented tree query to confirm that our node has been deleted without corrupting the hierarchy:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt GROUP BY node.name ORDER BY node.lft; +-----------------------+ | name | +-----------------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | PORTABLE ELECTRONICS | | MP3 PLAYERS | | FLASH | | CD PLAYERS | | 2 WAY RADIOS | | FRS | +-----------------------+
This approach works equally well to delete a node and all its children:
LOCK TABLE nested_category WRITE; SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM nested_category WHERE name = 'MP3 PLAYERS'; DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight; UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight; UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight; UNLOCK TABLES;
And once again, we query to see that we have successfully deleted an entire sub-tree:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt GROUP BY node.name ORDER BY node.lft; +-----------------------+ | name | +-----------------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | PORTABLE ELECTRONICS | | CD PLAYERS | | 2 WAY RADIOS | | FRS | +-----------------------+
The other scenario we have to deal with is the deletion of a parent node but not the children. In some cases you may wish to just change the name to a placeholder until a replacement is presented, such as when a supervisor is fired. In other cases, the child nodes should all be moved up to the level of the deleted parent:
LOCK TABLE nested_category WRITE; SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM nested_category WHERE name = 'PORTABLE ELECTRONICS'; DELETE FROM nested_category WHERE lft = @myLeft; UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight; UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight; UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight; UNLOCK TABLES;
In this case we subtract two from all elements to the right of the node (since without children it would have a width of two), and one from the nodes that are its children (to close the gap created by the loss of the parent’s left value). Once again, we can confirm our elements have been promoted:
SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt GROUP BY node.name ORDER BY node.lft; +---------------+ | name | +---------------+ | ELECTRONICS | | TELEVISIONS | | TUBE | | LCD | | PLASMA | | CD PLAYERS | | 2 WAY RADIOS | | FRS | +---------------+
Other scenarios when deleting nodes would include promoting one of the children to the parent position and moving the child nodes under a sibling of the parent node, but for the sake of space these scenarios will not be covered in this article.
While I hope the information within this article will be of use to you, the concept of nested sets in SQL has been around for over a decade, and there is a lot of additional information available in books and on the Internet. In my opinion the most comprehensive source of information on managing hierarchical information is a book called Joe Celko’s Trees and Hierarchies in SQL for Smarties, written by a very respected author in the field of advanced SQL, Joe Celko. Joe Celko is often credited with the nested sets model and is by far the most prolific author on the subject. I have found Celko’s book to be an invaluable resource in my own studies and highly recommend it. The book covers advanced topics which I have not covered in this article, and provides additional methods for managing hierarchical data in addition to the Adjacency List and Nested Set models.
In the References / Resources section that follows I have listed some web resources that may be of use in your research of managing hierarchal data, including a pair of PHP related resources that include pre-built PHP libraries for handling nested sets in MySQL. Those of you who currently use the adjacency list model and would like to experiment with the nested set model will find sample code for converting between the two in the Storing Hierarchical Data in a Database resource listed below.
FltReadFile 从一个打开的文件、流或者设备中读取数据。
NTSTATUS
FltReadFile(
IN PFLT_INSTANCE InitiatingInstance,
IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN ULONG Length,
OUT PVOID Buffer,
IN FLT_IO_OPERATION_FLAGS Flags,
OUT PULONG BytesRead OPTIONAL,
IN PFLT_COMPLETED_ASYNC_IO_CALLBACK CallbackRoutine OPTIONAL,
IN PVOID CallbackContext OPTIONAL
);
If the file object that FileObject points to was opened for asynchronous I/O, this parameter is required and cannot be NULL.
| 标志 | 含义 |
|---|---|
| FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET | Minifilter drivers can set this flag to specify that FltReadFile should not update the file object’s CurrentByteOffset field. |
| FLTFL_IO_OPERATION_NON_CACHED | Minifilter drivers can set this flag to specify a noncached read, even if the file object was not opened with FILE_NO_INTERMEDIATE_BUFFERING. |
| FLTFL_IO_OPERATION_PAGING | Minifilter drivers can set this flag to specify a paging read. |
FltReadFile returns the NTSTATUS value that was returned by the file system.
微过滤器调用 FltReadFile 从一个打开的文件中读取数据。
FltReadFile 创建一个读请求并发送到微过滤器驱动挂载的进程后到文件系统。指定的进程之上的挂载进程则收不到读请求。
FltReadFile 在下列条件成立时,准备非缓存I/O:
Noncached I/O imposes the following restrictions on the parameter values passed to FltReadFile:
If an attempt is made to read beyond the end of the file, FltReadFile returns an error.
If the value of the CallbackRoutine parameter is not NULL, the read operation is performed asynchronously.
If the value of the CallbackRoutine parameter is NULL, the read operation is performed synchronously. That is, FltReadFile waits until the read operation is complete before returning. This is true even if the file object that FileObject points to was opened for asynchronous I/O.
If multiple threads call FltReadFile for the same file object, and the file object was opened for synchronous I/O, the Filter Manager does not attempt to serialize I/O on the file. In this respect, FltReadFile differs from ZwReadFile.
IRQL: PASSIVE_LEVEL
Headers: Declared in fltkernel.h. Include fltkernel.h.
上下文 是一个由微过滤器定义的结构,可以用于和一个过滤管理器对象关联。微过滤器可以微下列对象创建和设置上下文:
除了卷上下文必须在非分页的内存池上分配,其他的既可以在非分页也可以在分页内存池中分配。
过滤管理器在他们挂载的对象删除后、微过滤器驱动实例从卷上解挂载或微过滤器卸载,自动删除相关的上下文。
当微过滤器驱动在DriverEntry中调用 FsRegisterFilter时,必须注册好每个要使用的上下文类型。
要注册上下文类型,微过滤器驱动创建一个类型为 FLT_CONTEXT_REGISTRATION 的变长数组。保持在 FLT_REGISTRATION 结构的 ContextRegistration 域中,传递到 FltRegisterFilter 函数的 Registration 参数。数组中的成员的顺序没有关系,但最后一个成员必须是 {FLT_CONTEXT_END}。
对于微过滤器驱动使用到的每一个上下文类型,FLT_CONTEXT_REGISTRATION 结构必须至少提供一个上下文定义。每一个FLT_CONTEXT_REGISTRATION结构定义了上下文的类型、大小和其他信息。
微过滤器驱动调用FltAllocateContext创建一个新的上下文,过滤管理器使用size参数
对于固定大小的上下文,FLT_CONTEXT_REGISTRATION结构的Size成员指定了字节大小,上下文的大小最大为MAXUSHORT(64KB)。0也是一个有效的值。过滤管理器用旁视列表分配固定大小的上下文。
对于不定大小的上下文,Size成员必须设置为FLT_VARIABLE_SIZED_CONTEXTS。过滤管理器直接从分页或非分页的内存池中分配不定长的上下文。
FLT_CONTEXT_REGISTRATION结构的Flags成员,可以指定FLTFL_CONTEXT_REGISTRATION_NO_EXACT_SIZE_MATCH。如果微过滤器驱动使用定长的上下文,并且指定了这个标志,上下文的大小大于等于请求的长度的话,过滤管理器从旁视列表里分配内存。否则上下文大小必须等于请求大小。
对于一个给定的上下文类型,微过滤器驱动可以支持3种固定大小(每一个大小不同)的上下文定义和1种变长定义。【更多信息,参见FLT_CONTEXT_REGISTRATION】
微过滤器驱动支持可选上下文释放前的清理回调例程。【参见 PFLT_CONTEXT_CLEANUP_CALLBACK】
微过滤器驱动可以定义一个他自己的分配、释放回调例程。【参见PFLT_CONTEXT_ALLOCATE_CALLBACK 和 PFLT_CONTEXT_FREE_CALLBACK】。
下面是从CTX示例中的部分代码,展示了一个用于注册实例、文件、流和文件对象(流句柄)的FLT_CONTEXT_REGISTRATION结构数组。
1 引言
PDM(Product Data Management)是以产品数据为中心,集成并管理所有与产品相关的信息、过程、人与组织的大型管理软件。目前,国外典型的PDM软件主要有UGS公司的TeamCenter、PTC公司的Windchill、MatrixOne公司的e-Matrix、IBM公司的SmarTeam、SAP公司的 mySAP等,这些软件基本代表了现今PDM技术的最高水平。经过多年的发展,国产PDM软件得到了长足的进步,但与国外PDM软件仍存在一定的差距,主要表现在体系架构、信息模型、功能模块、解决方案和实施方案学等几个方面。
2 技术分析
2.1 体系架构
软件的体系架构决定了软件的可应用性、可扩展性等重要特性,不同的企业选择体系结构适合自身特点的PDM系统才能最大限度地确保实施的成功。
随着Web技术、面向对象技术的不断发展和应用,国外PDM的体系结构日趋先进,已经从传统的客户机/服务器结构转向基于Web应用、J2EE技术、 C/B/S三段式结构的多层体系框架。通常,PDM系统的体系结构整体上分五层:底层平台层、PDM核心服务层、PDM应用组件层、应用工具层和实施理念层,这里主要讨论PDM核心服务层。
PDM软件产品一般指的是核心服务层和应用组件层。在C/S结构下,核心服务层一般就是服务器端,客户端软件就属于PDM的应用组件,在C/B/S结构下,二者都运行于服务器端,但安装有所不同,核心服务是必须的,而应用组件是选用的,如TeamCenter的对象管理框架、Windchill的 Windchill Foundation、e-Matrix的AEF(Application Exchange Framework)都是典型的核心服务。
核心服务层向下连接并操纵数据库,向上为应用组件服务,如Web处理机制、API应用集成接口等。核心服务层的核心是信息模型,它也是PDM的技术核心。
相比而言,国产PDM在Web应用、J2EE技术等方面的发展比较慢,主流PDM软件普遍还停留在客户机/服务器模式,只有少数几家发布了支持Web应用的PLM产品,如清软英泰、上海同捷;在对象模型及多层结构方面,国产PDM正逐步朝建立核心服务层、应用组件层的方向努力,如开目PDM的对象模型。
总之,受各种条件的限制,国产PDM软件可能在PDM体系结构五个层次的某个层次上有所进步和突破,但总体上与国外PDM软件还有一定距离。
2.2 信息模型
信息模型是PDM核心服务层的基础,在PDM体系结构中占有重要地位,包括对象模型和过程模型两方面。
2.2.1 对象模型
国外PDM系统已普遍采用了面向对象的数据建模方法。随着面向对象的方法、分布式技术的发展,国外PDM系统的对象模型已逐渐发展为面向对象、支持分布式管理的单一产品数据源的对象模型,如TeamCenter、Windchill、e-Matrix、SmartTeam等。
在国内,各PDM厂商越来越意识到数据建模技术所提供的可扩展性对支持用户复杂业务和个性需求的重要性,已经从基于扩展数据表或字段的配置技术提升为使用建模工具进行数据建模的技术。面向对象的建模技术的研究也取得了一系列研究成果,并在一些国产的商品化PDM系统中得到了应用。国内PDM关于数据建模技术的一些现状和特点如下:
* 部分国内厂商已经提供了面向对象的对象模型支持,如武汉开目和CAXA采用面向对象的对象模型;
* 各厂商提供的对象模型各有特点,如清软英泰提供了基于语义网络的对象模型;
* 对建模工具的图形化支持还不够,面向对象的能力支持不够,需要进一步提高性能,并扩展模型对PDM的支持;
* 部分厂商提供了分布式对象模型支持,但分布式对象模型应用有多个层次,绝大多数国内PDM厂商并不支持分布式对象模型或只提供较简单的应用模式;
* 分布式对象模型的用户一般是大型企业,往往选择国外知名的PDM系统,而绝大多数国内PDM厂商定位在中小型企业,所以关于分布式对象模型研究进展缓慢。
2.2.2 过程模型
国外的主流PDM系统均提供了较为强大的过程模型支持。
在对象的全生命周期管理方面,国外的研究已基本成熟,并已在实际的软件系统中得到应用,如Smarteam、Windchill、TeamCenter和 e-Matrix等系统均支持对象的全生命周期管理。在工作流管理方面,国外开展的研究比较早,也比较深入。1995年,工作流管理联盟根据研究成果和应用实践制定了工作流管理系统的相关术语、体系结构及应用接口等方面的一系列标准,并提出了相应的过程流参考模型,目前,国外的主流PLM相关产品的工作流模型均支持此标准。
在对象的全生命周期管理与工作流程的集成方面,国外PDM系统主要关注在面向对象的工作流应用方面,如Windchill和TeamCenter的变更管理。
在国内,结合数据建模技术,各PDM厂商对面向对象的过程建模技术进行了一些研究,并对已有的PDM系统进行了改进,取得到了很好的应用效果。国内PDM关于过程建模方面的一些现状和特点如下:
*过程模型的框架差异较大,采用的技术差别也较大;
*过程模型提供的建模能力差异很大,现有的过程建模工具不足以支持用户复杂的业务过程;
* 过程模型和对象模型集成不够理想;
* 支持跨企业、跨地域的分布式应用的过程建模技术与国外相比有较大的差距;
* 对支持符合业务需要的业务模型的研究不足;
* 部分厂商提供了对WFMC的支持,但并不普遍,对相关国际标准的支持不够。
2.3 功能模块
系统功能模块实际上就是由调用核心基础服务的一组程序(界面)组成并能够完成一定应用功能的应用组件。国外PDM软件都在不断丰富自己的功能模块,根据 AMR Research的研究报告,国外PDM软件支持新产品的定义和投放市场的工程,市场,操作和原料等63种功能需求。应该说,国外各个PDM系统的功能模块已基本覆盖从产品概念设计到应用集成的各个业务层面。
国外各个PDM系统之间的功能模块基本相同,但是存在一定差异。如TeamCenter提供了产品配置管理、生命周期管理等具体的模块,部分基础功能满足了制造业的基本需求,实施时利用现有模块和基础功能比较方便,在应用于制造业时有一定优势,而e-Matrix提供了较基础的系统构建框架,更加开放,但应用时配置工作量更大。
相比而言,国产PDM的功能模块虽然已经基本覆盖PDM的功能边界,但在业务解决方案和细节方面还有待完善,功能模块的层次划分也不够清晰。
常见的PDM系统功能模块有:文档管理、生命周期管理、更改控制管理、产品结构管理、产品配置管理、零部件族管理、产品可视化管理、工作流管理、项目管理、应用集成接口以及Web服务等。下面仅就项目管理和应用集成方面比较国产PDM与国外PDM之间的差异。
2.3.1 项目管理
从国外PDM产品的发展趋势来看,项目管理技术已从原来面向组织和功能的传统管理模式转变到面向项目的现代管理方式,通过项目将人员、流程以及各应用功能联结起来,从而构建面向业务的应用系统。如TeamCenter推出的TeamCenter Project项目协同模块,主要包括项目进度管理、项目资源管理、项目协同发布、项目跟踪与汇报、项目工作区安全等功能,支持将复杂的项目细化为明确的任务,并将这些任务分配给指定的资源,同时管理每个资源所承受的工作负荷;支持项目团队协作,促使产品价值链上项目团队的任务和调度同步,优化配置资源,降低开发成本。mySAP PLM提供的计划和项目管理模块,能够协助企业对产品开发流程进行规划、管理和控制,如控制项目的结构、日程计划、成本和资源等。Winchill解决方案中推出的项目协同管理Project Link,旨在将不同的产品开发以项目的形式进行管理,并通过Internet连接企业内外的供应链。
国产PDM的项目管理普遍与文档签审流程紧密集成,而项目的资源管理、协同发布等项目管理的基本功能相对较弱,部分供应商通过与MS Project的简单集成实现项目的资源管理,但在工作协同方面则没有研究和应用。
2.3.2 应用集成
作为比较成熟的PDM产品,国外PDM系统都提供开放的软件接口,也具有较强大的集成组件。相比而言,受各种条件的限制,国内PDM普遍未提供开放的软件接口,各个PDM软件虽然与某个应用软件的集成性比较优秀,但从整体而言,应用集成性还处于比较低的层次。
应用集成包括与单元应用软件、系统管理软件等各种软件之间的集成,下面主要讨论PDM与CAD、CAPP和管理软件之间的集成。
2.3.2.1 与CAD的集成
PDM与CAD的集成主要是与主流3D软件的集成。由于国内制造企业的3DCAD的普及比较晚,因而国产PDM与3DCAD的应用集成研究相对较晚,与国外PDM软件相比有很大的距离,主要表现在数据集成、浏览/圈阅工具、协同可视化工具等方面。
国外主流的PDM软件基本能够支持15种独立CAD软件包,基于较完善的对象模型,基本与3DCAD实现了无缝集成;而国内PDM软件则主要支持几种主流的3D软件,如UG/ProE/SolidEdge/SolidWorks等,在数据集成的深度方面也很有限,仅处于数据提取的水平。
在浏览和圈阅方面,国外PDM基本采用两种策略。
以UGS、Windchill为代表的供应商采取自身开发浏览器的策略,并发布统一的3D浏览/圈阅工具(或控件),如UGS的JT2GO;
以MatrixOne、Smarteam、Agile为代表的供应商则采取集成专业浏览器实现与3DCAD的集成,浏览器主要是业界领先的Cimmetry公司的AutoVue浏览器。
受各种条件限制,国产PDM在3DCAD的浏览/圈阅方面还处于摸索阶段,部分厂商提供了与AutoVue浏览器的集成。
2.3.2.2 与CAPP的集成
基于CAPP软件的工艺解决方案是中国企业的独特需求,一直以来,国外PDM软件并不重视PDM与CAPP的集成,相比而言,国产PDM始终致力于国内企业的信息化工作,重视PDM与CAPP的集成工作,对CAPP软件的支持比国外PDM做的好。
近年来,随着国内企业对PDM与CAPP集成的需求越来越强烈,国外PDM供应商也逐渐开始对国产CAPP软件提供支持,如Windchill提供了与开目CAPP的集成。
2.3.2.3 与ERP/CRM的集成
经过多年积累,国外PDM软件与ERP/CRM的集成已经非常成熟,在提供集成接口的基础上,对国外主流ERP/CRM,如SAP R3、Oracle Manufacture还提供专用的集成组件,如Smarteam、TeamCenter、Windchill等。而国产PDM与ERP/CRM的集成水平相对较低,基本停留在中间交换文件的水平上,没有与ERP/CRM的专用集成接口。
3 实施分析
3.1 解决方案
国外软件供应商普遍将PDM软件作为PLM完整解决方案的一个组成部分,处理整个产品生命周期中的主要业务过程。如TeamCenter的协同平台(Collaboration Foundtion)、Windchill的CPC方案、MatrixOne的智能协同业务(Intelligent C-Commerce)等。
同时,针对不同的行业,各个公司专门提供了行业解决方案。如TeamCenter公司针对汽车行业,Windchill公司针对船舶行业;Smarteam针对电子行业都提出了专门的行业解决方案。
受各种条件的限制,国内还没有一家软件供应商提出并发布真正意义上的PLM完整解决方案或产品开发信息化的完整解决方案,往往以PDM为主体简单集成相关管理软件,达到实现产品数据全生命周期管理的目的,相比而言,无论完整性还是应用性都存在欠缺。
3.2 实施方法学
国外PDM供应商非常重视软件的实施,在大量成功范例的基础上,总结了众多的成功经验,形成适合本公司特点的实施方法学。这些实施方法学的主要特点如下:
* 具有完整的实施模型和阶段划分,能覆盖实施的全生命周期;
* 把需求收集、分析阶段作为整个实施过程的重点,切实了解用户需求;
* 在关键阶段设置质量检验点,可进行全面质量控制;
* 建立在大量的成功范例基础上,包含业界经验与Know-How技术;
* 注重与用户的交流,容易被用户接受;
* 提供了大量的实施文档模板,有效地引导和规范了实施人员的工作。
结合现代项目管理理论,经过不断的摸索和实践,国内PDM供应商已经逐渐摸索出一套适合中国国情、行之有效的实施方法,并在具体的项目实施中得到良好应用,如CAXA和武汉开目的分阶段实施理论。
4 结束语
从体系架构、信息模型、功能模块、解决方案、实施方法学等几个方面的比较可以看出,国产PDM软件与国外PDM软件相比还有一定距离,特别是体系结构和解决方案方面尤其如此。随着国内企业信息化工程的不断深入,国内PDM软件供应商的投入不断加大,相信在体系结构、信息模型、功能模块等基础研究获得突破,并在项目实践中得到验证并发展后,国产PDM软件的水平会有一个大的发展。
分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。
下面简单介绍分布式文件系统的历史与现状:
对象存储突破了高性能计算环境中存储系统的瓶颈,由此也引发了人们对分布式文件系统的关注。最初的分布式文件系统应用发生在20世纪70年代,之后逐渐扩展到各个领域。从早期的NFS到现在的StorageTank,分布式文件系统在体系结构、系统规模、性能、可扩展性、可用性等方面经历了较大的变化。
文件系统是操作系统的一个重要组成部分,通过对操作系统所管理的存储空间的抽象,向用户提供统一的、对象化的访问接口,屏蔽对物理设备的直接操作和资源管理。
根据计算环境和所提供功能的不同,文件系统可划分为四个层次,从低到高依次是:单处理器单用户的本地文件系统,如DOS的文件系统;多处理器单用户的本地文件系统,如OS/2的文件系统;多处理器多用户的文件系统,如Unix的本地文件系统;多处理器多用户的分布式文件系统。
本地文件系统(Local File System)是指文件系统管理的物理存储资源直接连接在本地节点上,处理器通过系统总线可以直接访问。分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。上述按照层次的分类中,高层次的文件系统都是以低层次的文件系统为基础,实现了更高级的功能。比如多处理器单用户的本地文件系统需要比单处理器单用户的本地文件系统多考虑并发控制(Concurrency Control),因为可能存在多个处理器同时访问文件系统的情况;多处理器多用户的文件系统需要比多处理器单用户的本地文件系统多考虑数据安全访问方面的设计,因为多个用户存在于同一个系统中,保证数据的授权访问是一个关键;多处理器多用户的分布式文件系统需要比多处理器多用户的文件系统多考虑分布式体系结构带来的诸多问题,比如同步访问、缓冲一致性等。
随着层次的提高,文件系统在设计和实现方面的难度也会成倍提高。但是,现在的分布式文件系统一般还是保持与最基本的本地文件系统几乎相同的访问接口和对象模型,这主要是为了向用户提供向后的兼容性,同时保持原来的简单对象模型和访问接口。但这并不说明文件系统设计和实现的难度没有增加。正是由于对用户透明地改变了结构,满足用户的需求,以掩盖分布式文件操作的复杂性,才大大增加了分布式文件系统的实现难度。
在计算机性能不断提升的同时,计算机部件的平均价格却在不断下降。用户可以用更低的成本,购买更好、更快、更稳定的设备。存储系统、文件系统面临的新挑战也随之而来:如何管理更多的设备,提供更好的性能,更加有效地降低管理成本等。各种新的存储技术和分布式文件技术层出不穷,以满足用户日益增长的需求。因此,有必要简要回顾分布式文件系统发展的历史,分析对比当前主流的分布式文件系统在体系结构、缓存一致性、安全等方面的长处和不足。
文件系统最初是用来管理本地磁盘,提供用户访问接口的。某些数据的集合叫做一个文件(File),并赋予每个文件一定的属性,以标识该数据集合的某些属性。文件按照树(Tree)结构层次进行管理和检索。最初的文件系统只能管理本地磁盘空间。主机之间的文件共享与传输则通过文件传输协议(FTP,File Transfer Protocol)实现。但FTP没有提供与本地文件系统一致的访问接口和对象模型。
随着计算机应用范围的扩展,通过文件访问接口在不同主机之间共享文件的需求日益增强。下面分为几个阶段介绍分布式文件系统的发展过程。
1
1980~1990年
早期的分布式文件系统一般以提供标准接口的远程文件访问为目的,更多地关注访问的性能和数据的可靠性。
早期的文件系统以NFS和AFS(Andrew File System)最具代表性,它们对以后的文件系统设计也具有十分重要的影响。
NFS从1985年出现至今,已经经历了四个版本的更新,被移植到了几乎所有主流的操作系统中,成为分布式文件系统事实上的标准。NFS利用Unix系统中的虚拟文件系统(Virtual File System,VFS)机制,将客户机对文件系统的请求,通过规范的文件访问协议和远程过程调用,转发到服务器端进行处理;服务器端在VFS之上,通过本地文件系统完成文件的处理,实现了全局的分布式文件系统。Sun公司公开了NFS的实施规范,互联网工程任务组(The Internet Engineering Task Force,IETF)将其列为征求意见稿(RFC-Request for Comments),这很大程度上促使NFS的很多设计实现方法成为标准,也促进了NFS的流行。NFS不断发展,在第四版中提供了基于租赁(Lease)的同步锁和基于会话(Session)语义的一致性等。
Carnegie Mellon大学在1983年设计开发的AFS将分布式文件系统的可扩展性放在了设计和实现的首要位置,并且着重考虑了在不安全的网络中实现安全访问的需求。因此,它在位置透明、用户迁移、与已有系统的兼容性等方面进行了特别设计。AFS具有很好的扩展性,能够很容易地支持数百个节点,甚至数千个节点的分布式环境。同时,在大规模的分布式文件系统中,AFS利用本地存储作为分布式文件的缓存,在远程文件无法访问时,依然可以部分工作,提高了系统可用性。后来的Coda File System、Inter-mezzo File System都受到AFS的影响,更加注重文件系统的高可用性(High Availability)和安全性,特别是Coda,在支持移动计算方面做了很多的研究工作。
Sprite File System也是早期比较有特色的分布式文件系统。它是Sprite Network Operation System的组成部分,为分布式计算环境提供全局文件访问。与NFS相比,Sprite File System在服务器端和客户端都设置缓存,大大提高了系统性能。它通过简单的读写锁保证整个系统的缓存一致性,并且通过和虚拟存储部分交互,尽量多地缓存数据。
早期的分布式文件系统一般以提供标准接口的远程文件访问为目的,在受网络环境、本地磁盘、处理器速度等方面限制的情况下,更多地关注访问的性能和数据的可靠性。AFS在系统结构方面进行了有意义的探索。它们所采用的协议和相关技术,为后来的分布式文件系统设计提供了很多借鉴。
PanFS的系统结构图
2
1990~1995年
20世纪90年代初,面对广域网和大容量存储需求,加利福尼亚大学设计开发的xFS借鉴了当时先进的高性能对称多处理器的设计思想。
20世纪90年代初,面对广域网和大容量存储应用的需求,借鉴当时先进的高性能对称多处理器的设计思想,加利福尼亚大学设计开发的xFS,克服了以前的分布式文件系统一般都运行在局域网(LAN)上的弱点,很好地解决了在广域网上进行缓存,以减少网络流量的难题。它所采用的多层次结构很好地利用了文件系统的局部访问的特性,无效写回(Invalidation-based Write Back)缓存一致性协议,减少了网络负载。对本地主机和本地存储空间的有效利用,使它具有较好的性能。
Tiger Shark并行文件系统是针对大规模实时多媒体应用设计的。它采用了多种技术策略保证多媒体传输的实时性和稳定性:采用资源预留和优化的调度手段,保证数据实时访问性能;通过加大文件系统数据块的大小,最大限度地发挥磁盘的传输效率;通过将大文件分片存储在多个存储设备中,取得尽量大的并行吞吐率;通过复制文件系统元数据和文件数据,克服单点故障,提高系统可用性。
基于虚拟共享磁盘Petal的Frangipani分布式文件系统,采用了一种新颖的系统结构—分层次的存储系统。Petal提供一个可以全局统一访问的磁盘空间。Frangipani基于Petal的特性提供文件系统的服务。这种分层结构使两者的设计实现都得到了简化。在Frangipani中,每个客户端也是文件系统服务器,参与文件系统的管理,可以平等地访问Petal提供的虚拟磁盘系统,并通过分布式锁实现同步访问控制。分层结构使系统具有很好的扩展性,可以在线动态地添加存储设备,增加新用户、备份等,同时系统具有很好的机制来处理节点失效、网络失效等故障,提高了系统的可用性。
Slice File System(SFS)考虑标准的NFS在容量、性能方面存在的限制,采用在客户机和服务器之间架设一个μproxy中间转发器,以提高性能和可扩展性。它将客户端的访问分为小文件、元数据服务、大文件数据三类请求。通过μproxy将前两种请求转发到不同的文件服务器上,将后者直接发送到存储服务器上。这样SFS系统就可以支持多个存储服务器,提高整个系统的容量和性能。μproxy根据请求内容的转发是静态的,对于整个系统中负载的变化难以做出及时反应。
3
1995~2000年
网络技术的发展和普及应用极大地推动了网络存储技术的发展,基于光纤通道的SAN、NAS得到了广泛应用。这也推动了分布式文件系统的研究。
Cluster FS的Lustre系统结构图
在这个阶段,计算机技术和网络技术有了突飞猛进的发展,单位存储的成本大幅降低。而数据总线带宽、磁盘速度的增长无法满足应用对数据带宽的需求,存储子系统成为计算机系统发展的瓶颈。
网络技术的发展和普及应用极大地推动了网络存储技术的发展,基于光纤通道的SAN、NAS得到了广泛应用。这也推动了分布式文件系统的研究。这个阶段,出现了多种体系结构,充分利用了网络技术。
Global File System(GFS)吸取了对称多处理器(SMP)系统设计和实现的原理,将系统中的每一个客户机类比于SMP中的一个处理器。客户机间没有任何区别,可以平等地访问系统中的所有存储设备,就像处理器可以机会均等地访问主存一样。这样的设计可以更好地利用系统中的资源,消除单个服务器带来的性能瓶颈和单点失效问题。客户端之间无需通信,因此可以很好地消除客户机失效带来的威胁。GFS采用特殊设计的DLOCK锁机制,同步多个客户机对同一设备的访问,具有很高的效率。
General Parallel File System(GPFS)是从Tiger Shark发展过来的,是目前应用范围较广的一个系统。GPFS在系统设计中采用了多项先进技术。它是一个共享磁盘(Shared-disk)的分布式并行文件系统,客户端采用基于光纤通道或者iSCSI与存储设备相连,也可以通过通用网络相连。GPFS的磁盘数据结构可以支持大容量的文件系统和大文件,通过采用分片存储、较大的文件系统块、数据预读等方法获得了较高的数据吞吐率;采用扩展哈希(Extensible Hashing)技术支持含有大量文件和子目录的大目录,提高文件的查找和检索效率。GPFS采用分布式锁解决系统中的并发访问和数据同步问题:字节范围的锁用于用户数据的同步,动态选择元数据节点(Metanode)进行元数据的集中管理;分布式锁管理整个系统的空间分配等。GPFS采用日志技术对系统进行在线灾难恢复。每个节点都有各自独立的日志,且单个节点失效时,系统中的其他节点可以代替失效节点检查文件系统日志,进行元数据恢复操作。
惠普的DiFFS和SGI公司的CXFS都是基于SAN的分布式文件系统。DiFFS通过将存储系统划分成不同的区域,把对资源的共享访问冲突限制在各个区域内部,以解决机群文件系统的可扩展性问题。DiFFS采用了动态分配策略和文件级的负载平衡等多项技术。CXFS是在XFS的基础上开发的,实现了元数据服务器内置的失效接替和恢复功能;采用快速元数据算法,提高元数据的访问性能。
此外,还有多种体系结构,如EMC的HighRoad、Sun的qFS、XNFS等。数据容量、性能和共享的需求使得这一时期的分布式文件系统管理的系统规模更大、系统更复杂,对物理设备的直接访问、磁盘布局和检索效率的优化、元数据的集中管理等都反映了对性能和容量的追求。规模的扩展使得系统的动态性,如在线增减设备、缓存的一致性、系统可靠性的需求逐渐增强,更多的先进技术应用到系统实现中,如分布式锁、缓存管理技术、SoftUpdates技术、文件级的负载平衡等。
4
2000年以后
随着SAN和NAS两种结构逐渐成熟,研究人员开始考虑如何将两种结构结合起来。网格的研究成果等也推动了分布式文件系统体系结构的发展。
随着SAN和NAS两种体系结构逐渐成熟,研究人员开始考虑如何将两种体系结构结合起来,以充分利用两者的优势。另一方面,基于多种分布式文件系统的研究成果,人们对体系结构的认识不断深入,网格的研究成果等也推动了分布式文件系统体系结构的发展。这一时期,IBM的StorageTank、Cluster的Lustre、Panasas的PanFS、蓝鲸文件系统(BWFS)等是这种体系结构的代表。各种应用对存储系统提出了更多的需求:
大容量—现在的数据量比以前任何时期更多,生成的速度更快;
高性能—数据访问需要更高的带宽;
高可用性—不仅要保证数据的高可用性,还要保证服务的高可用性;
可扩展性—应用在不断变化,系统规模也在不断变化,这就要求系统提供很好的扩展性,并在容量、性能、管理等方面都能适应应用的变化;
可管理性—随着数据量的飞速增长,存储的规模越来越庞大,存储系统本身也越来越复杂,这给系统的管理、运行带来了很高的维护成本;
按需服务—能够按照应用需求的不同提供不同的服务,如不同的应用、不同的客户端环境、不同的性能等。
IBM公司在GPFS的基础上发展进化来的Storage Tank,以及基于Storage Tank的TotalStorage SAN File System,又将分布式文件系统的设计理念和系统架构向前推进了一步。它们除了具有一般的分布式文件系统的特性之外,还采用SAN作为整个文件系统的数据存储和传输路径。它们采用带外(out-of-band)结构,将文件系统元数据在高速以太网上传输,由专门的元数据服务器来处理和存储。文件系统元数据和文件数据的分离管理和存储,可以更好地利用各自存储设备和传输网络的特性,提高系统的性能,有效降低系统的成本。在TotalStorage中,块虚拟层将整个SAN的存储进行统一的虚拟管理,为文件系统提供统一的存储空间。SAN File System采用了基于策略的文件数据位置选择方法,能有效地利用系统的资源,提高性能,降低成本。
处于这个阶段的系统都在研究中,但从中也可以看出一些发展趋势:体系结构的研究逐渐成熟,表现在不同文件系统的体系结构趋于一致;系统设计的策略基本一致,如采用专用服务器方式等;每个系统在设计的细节上各自采用了很多特有的先进技术,也都取得了很好的性能和扩展性。另外,在协议方面的探索也是研究的热点之一,如Direct Access File System利用了远程内存直接访问的特性,借鉴了NFS第四版本和Common Internet File System等协议,设计了一套新的网络文件访问协议。