PHP脚本木马防范经验 - IT梦工厂 - CSDN博客

mikel阅读(848)

来源: PHP脚本木马防范经验 – IT梦工厂 – CSDN博客

1、首先修改httpd.conf,如果你只允许你的php脚本程序在web目录里操作,还可以修改httpd.conf文件限制php的操作路径。比如你的 web目录是/usr/local/apache/htdocs,那么在httpd.conf里加上这么几行:

php_admin_value open_basedir /usr/local/apache/htdocs

这样,如果脚本要读取/usr/local/apache/htdocs以外的文件将不会被允许,如果错误显示打开的话会提示这样的错误:

Warning: open_basedir restriction in effect. File is in wrong directory in /usr/local/apache/htdocs/open.php on line 4 等等。

2、防止php木马执行webshell

打开safe_mode, 在php.ini中设置 disable_functions= passthru,exec,shell_exec,system 二者选一即可,也可都选

3、防止php木马读写文件目录

在php.ini中的 disable_functions= passthru,exec,shell_exec,system 后面加上php处理文件的函数

主要有

 

fopen,mkdir,rmdir,chmod,unlink,dir

fopen,fread,fclose,fwrite,file_exists

closedir,is_dir,readdir.opendir

fileperms.copy,unlink,delfile

 

即成为

 

disable_functions = passthru,exec,shell_exec,system,fopen,mkdir,rmdir,chmod,unlink,dir , fopen,fread,fclose,fwrite,file_exists ,closedir,is_dir,readdir.opendir , fileperms.copy,unlink,delfile

 

ok,大功告成,php木马拿我们没辙了,遗憾的是这样的话,利用文本数据库的那些东西就都不能用了。

如果是在windos平台下搭建的apache我们还需要注意一点,apache默认运行是system权限,这很恐怖,这让人感觉很不爽.那我们就给apache降降权限吧。

 

net user apache fuckmicrosoft /add

net localgroup users apache /del

ok.我们建立了一个不属于任何组的用户apche。

我们打开计算机管理器,选服务,点apache服务的属性,我们选择log on,选择this account,我们填入上面所建立的账户和密码,重启apache服务,ok,apache运行在低权限下了。

实际上我们还可以通过设置各个文件夹的权限,来让apache用户只能执行我们想让它能干的事情,给每一个目录建立一个单独能读写的用户。这也是当前很多虚拟主机提供商的流行配置方法哦,不过这种方法用于防止这里就显的有点大材小用了。

注:来自www.itmgc.info

AngularJS+ASP.NET MVC+SignalR实现消息推送 - i鱿鱼 - 博客园

mikel阅读(1124)

来源: AngularJS+ASP.NET MVC+SignalR实现消息推送 – i鱿鱼 – 博客园

背景

OA管理系统中,员工提交申请单,消息实时通知到相关人员及时进行审批,审批之后将结果推送给用户。

技术选择

最开始发现的是firebase,于是很兴奋的开始倒腾起来。firebase用起来倒是简单:引用一个js即可,按官网上的教程很快便应用到了项目中。第二天打开项目发现推送功能不好使了,这是为何?最后发现firebase官网打不开了。。。难道firebase被google收了也会被天朝给墙掉?也许是firebase自己挂掉了,总之是用不了了。因为要完全把推送数据存放在firebase服务器上来实现推送功能,就会有以下几个需要担心的问题:

1.数据不安全

2.对firebase依赖性太强

3.firebase收费(免费版太弱)

于是果断放弃firebase,朋友推荐有个叫SignalR的东东可以试试,这是专门为ASP.NET开发人员准备的一款消息推送类库,且不依赖别的服务器、免费。

使用方法

1.在nuget中搜索并添加SignalR最新版本

2.在页面中引用JQuery.signalR-2.2.0.min.js文件(依赖JQuery),再添加<script src=”/signalr/hubs”></script>用于自动生成signalr的脚本

3.添加MsgHub.cs类,用于处理对应用户信息和消息推送实现

 

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace ZmeiOA.Services
{
   [Authorize]
    [HubName("ZmHub")]
    public class MsgHub : Hub
    {/// <summary>
        /// 连接
        /// </summary>
        /// <returns></returns>
        public override System.Threading.Tasks.Task OnConnected()
        {
            Groups.Add(Context.ConnectionId, Context.User.Identity.Name);
            return base.OnConnected();
        }

        /// <summary>
        /// 重新连接
        /// </summary>
        /// <returns></returns>
        public override System.Threading.Tasks.Task OnReconnected()
        {
            Groups.Add(Context.ConnectionId, Context.User.Identity.Name);
            return base.OnReconnected();
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="stopCalled"></param>
        /// <returns></returns>
        public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
        {
            Groups.Remove(Context.ConnectionId, Context.User.Identity.Name);
            return base.OnDisconnected(stopCalled);
        }
    }
}
复制代码

 

说明:SignalR的推送消息是基于用户连接(ConnectionId)的,SignalR会为每个会话自动生成一个ConnectionId。但是我们的推送是基于用户的(权限体系),也就是只有登录之后才注册到此Hub。在这里我用到的是SignalR中的Groups,把登录用户的ConnectionId与对应的UserId添加到Groups中,推送的时候只要指定Groups的Name,SignalR便会自动找出其对应的ConnectionId并发送消息(这种方式可能不是最好的,因为每个用户的UserId都会作为Groups的Key添加进去,当用户量很大的时候Groups也会很庞大,但我还没找到更好的替代方案)。

4.消息推送有两种形式:a.服务端直接推送;b.客户端推送。

区别在于,服务端推送是在持久化数据之后便可以直接把消息推送给相关人;而客户端推送是持久化数据之后,客户端根据返回值的情况,使用SignalR的JS方法调用服务端的推送功能。我使用的是服务器端直接推送数据,因为在持久化数据之后就完全可以根据业务通知相关人,如果返回到前台之后再调用服务端的推送方法只是多此一举。

如:保存申请单成功之后立刻通知审批人

在Service中获取Hub的上下文

  /// <summary>
  /// 消息推送上下文
  /// </summary>
  protected static IHubContext ZmHubContext = GlobalHost.ConnectionManager.GetHubContext<MsgHub>();

在保存申请单之后给相关人员推送消息(注意:dynamic方法broadcastTodo就是在客户端需要接收消息的方法)

复制代码
public static ApplyForm Save(FormView view)
{  
   //省略业务操作...
   //...
    //通知待办事项
    ZmHubContext.Clients.Groups(app.AuditorIds.Split(',')).broadcastTodo(app.AuditorIds, new { type = "new", data = app });
    return app;
}
复制代码

5.在注册Angular模块时连接Hub并作为value传入到模块中,这样每个controller都可以使用此连接:

  var zmHub = $.connection.ZmHub;

  var zmApp = angular.module('zmApp', ['ngRoute', 'ngResource', 'ngSanitize', 'ngMessages', 'ngSVGAttributes']).value('zmHub', zmHub);

6.在首页的controller中接收推送的消息,并提供两种推送体验:a.桌面通知;b.页面内消息。桌面通知很酷,即使是浏览器最小化的时候,在桌面右下角也可以收到提示(Chrome和Firefox支持,IE不支持)

 

复制代码
zmHub.client.broadcastTodo = function (userIds, obj) {
    //通知下级控制器有待办事项
    $scope.$broadcast('todoschanged', obj);
    //显示桌面通知
    if (obj.type == 'new') {
     //桌面通知标题
        var title = '来自[' + obj.data.ApplicantName + ']的申请单';
        //申请单类型名称
        var formTypeName = DefaultService.getEnumText(17, obj.data.Type);
        var msg = '[' + formTypeName + ']' + obj.data.Name;
     //桌面通知方法
        NotifyService.Notify('todos', title, msg);
    }
}
复制代码

 

下级控制器的接收方法(关于angularjs的broadcast不多解释,不明白的可以到官网查阅):

复制代码
//接收推送的待办事项
$scope.$on('todoschanged', function (d, obj) {
    $scope.$apply(function () {
     //如果是新增数据,在当前列表中添加一条
        if (obj.type == 'new') {
            $scope.todoApps.unshift(obj.data);
        }
        else if (obj.type == 'delete') {//如果是撤销申请,则把当前列表中那条数据删除
            for (var j = 0; j < $scope.todoApps.length; j++) {
                if ($scope.todoApps[j].Id == obj.data.Id) {
                    $scope.todoApps.splice(j, 1);
                    break;
                }
            }
        }
    });
});
复制代码

桌面通知服务:

复制代码
//桌面通知服务
zmApp.factory('NotifyService', function () {

    return {
        Notify: function (icon, title, msg) {
            // At first, let's check if we have permission for notification
            // If not, let's ask for it
            if (window.Notification && Notification.permission !== "granted") {
                Notification.requestPermission(function (status) {
                    if (Notification.permission !== status) {
                        Notification.permission = status;
                    }
                });
            }
            var iconPath = '/Content/images/icons/' + (icon || 'info') + '.png';
            var options = {
                lang: 'zh-CN',
                body: msg,
                icon: iconPath
            };
            var notify;
            // If the user agreed to get notified
            if (window.Notification && Notification.permission === "granted") {
                notify = new Notification(title, options);
            }
            else if (window.Notification && Notification.permission !== "denied") {
                Notification.requestPermission(function (status) {
                    if (Notification.permission !== status) {
                        Notification.permission = status;
                    }
                    if (status === "granted") {
                        notify = new Notification(title, options);
                    }
                    else {
                        console.log('您禁止了桌面通知,无法推送到您的桌面!');
                    }
                });
            }
            else {
                console.log('您禁止了桌面通知,无法推送到您的桌面!');
            }
            if (notify) {
                notify.onclose = function (evt) {

                };
                //点击切换到浏览器
                notify.onclick = function () {
                    window.focus();
                };
            }
        }
    };
});
复制代码

桌面通知效果:

 

总结:

  用SignalR推送消息总的来说比较简单,只需要简单几步便可实现,而且是selfhost,不必担心对其它服务器的依赖和数据安全问题,感兴趣的朋友可以试试

WebSocket与消息推送 - 张果 - 博客园

mikel阅读(1248)

来源: WebSocket与消息推送 – 张果 – 博客园

B/S结构的软件项目中有时客户端需要实时的获得服务器消息,但默认HTTP协议只支持请求响应模式,这样做可以简化Web服务器,减少服务器的负担,加快响应速度,因为服务器不需要与客户端长时间建立一个通信链接,但不容易直接完成实时的消息推送功能,如聊天室、后台信息提示、实时更新数据等功能,但通过polling、Long polling、长连接、Flash Socket以及HTML5中定义的WebSocket能完成该功能需要。

一、Socket简介

Socket又称”套接字”,应用程序通常通过”套接字”向网络发出请求或者应答网络请求。Socket的英文原义是“孔”或“插座”,作为UNIX的进程通信机制。Socket可以实现应用程序间网络通信。

Socket可以使用TCP/IP协议或UDP协议。

TCP/IP协议

TCP/IP协议是目前应用最为广泛的协议,是构成Internet国际互联网协议的最为基础的协议,由TCP和IP协议组成:
TCP协议:面向连接的、可靠的、基于字节流的传输层通信协议,负责数据的可靠性传输的问题。

IP协议:用于报文交换网络的一种面向数据的协议,主要负责给每台网络设备一个网络地址,保证数据传输到正确的目的地。

UDP协议

UDP特点:无连接、不可靠、基于报文的传输层协议,优点是发送后不用管,速度比TCP快。

二、WebSocket简介与消息推送

B/S架构的系统多使用HTTP协议,HTTP协议的特点:

1 无状态协议
2 用于通过 Internet 发送请求消息和响应消息
3 使用端口接收和发送消息,默认为80端口
底层通信还是使用Socket完成。

HTTP协议决定了服务器与客户端之间的连接方式,无法直接实现消息推送(F5已坏),一些变相的解决办法:

双向通信与消息推送

轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。  优点:后端程序编写比较容易。  缺点:请求中有大半是无用,浪费带宽和服务器资源。  实例:适于小型应用。

长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。  优点:在无消息的情况下不会频繁的请求,耗费资小。  缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。 Comet异步的ashx, 实例:WebQQ、Hi网页版、Facebook IM。

长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。  优点:消息即时到达,不发无用请求;管理起来也相对便。  缺点:服务器维护一个长连接会增加开销。  实例:Gmail聊天

Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。  优点:实现真正的即时通信,而不是伪即时。  缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。  实例:网络互动游戏。

Websocket:
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。
特点:
事件驱动
异步
使用ws或者wss协议的客户端socket

能够实现真正意义上的推送功能

缺点:

少部分浏览器不支持,浏览器支持的程度与方式有区别。

三、WebSocket客户端

websocket允许通过JavaScript建立与远程服务器的连接,从而实现客户端与服务器间双向的通信。在websocket中有两个方法:
1、send() 向远程服务器发送数据
2、close() 关闭该websocket链接
websocket同时还定义了几个监听函数
1、onopen 当网络连接建立时触发该事件
2、onerror 当网络发生错误时触发该事件
3、onclose 当websocket被关闭时触发该事件
4、onmessage 当websocket接收到服务器发来的消息的时触发的事件,也是通信中最重要的一个监听事件。msg.data
websocket还定义了一个readyState属性,这个属性可以返回websocket所处的状态:
1、CONNECTING(0) websocket正尝试与服务器建立连接
2、OPEN(1) websocket与服务器已经建立连接
3、CLOSING(2) websocket正在关闭与服务器的连接
4、CLOSED(3) websocket已经关闭了与服务器的连接

websocket的url开头是ws,如果需要ssl加密可以使用wss,当我们调用websocket的构造方法构建一个websocket对象(new WebSocket(url))的之后,就可以进行即时通信了。

复制代码
<!DOCTYPE html>
<html>

    <head>
        <meta name="viewport" content="width=device-width" />
        <title>WebSocket 客户端</title>
    </head>

    <body>
        <div>
            <input type="button" id="btnConnection" value="连接" />
            <input type="button" id="btnClose" value="关闭" />
            <input type="button" id="btnSend" value="发送" />
        </div>
        <script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            var socket;
            if(typeof(WebSocket) == "undefined") {
                alert("您的浏览器不支持WebSocket");
                return;
            }

            $("#btnConnection").click(function() {
                //实现化WebSocket对象,指定要连接的服务器地址与端口
                socket = new WebSocket("ws://192.168.1.2:8888");
                //打开事件
                socket.onopen = function() {
                    alert("Socket 已打开");
                    //socket.send("这是来自客户端的消息" + location.href + new Date());
                };
                //获得消息事件
                socket.onmessage = function(msg) {
                    alert(msg.data);
                };
                //关闭事件
                socket.onclose = function() {
                    alert("Socket已关闭");
                };
                //发生了错误事件
                socket.onerror = function() {
                    alert("发生了错误");
                }
            });
            
            //发送消息
            $("#btnSend").click(function() {
                socket.send("这是来自客户端的消息" + location.href + new Date());
            });
            
            //关闭
            $("#btnClose").click(function() {
                socket.close();
            });
        </script>
    </body>

</html>
复制代码

四、WebSocket服务器端

JSR356定义了WebSocket的规范,Tomcat7中实现了该标准。JSR356 的 WebSocket 规范使用 javax.websocket.*的 API,可以将一个普通 Java 对象(POJO)使用 @ServerEndpoint 注释作为 WebSocket 服务器的端点。

复制代码
@ServerEndpoint("/push")
 public class EchoEndpoint {

 @OnOpen
 public void onOpen(Session session) throws IOException {
 //以下代码省略...
 }
 
 @OnMessage
 public String onMessage(String message) {
 //以下代码省略...
 }

 @Message(maxMessageSize=6)
 public void receiveMessage(String s) {
 //以下代码省略...
 } 

 @OnError
 public void onError(Throwable t) {
 //以下代码省略...
 }
 
 @OnClose
 public void onClose(Session session, CloseReason reason) {
 //以下代码省略...
 } 
 
 }
复制代码

上面简洁代码即建立了一个WebSocket的服务端,@ServerEndpoint(“/push”)的annotation注释端点表示将WebSocket服务端运行在ws://[Server端IP或域名]:[Server端口]/项目/push的访问端点,客户端浏览器已经可以对WebSocket客户端API发起HTTP长连接了。
使用ServerEndpoint注释的类必须有一个公共的无参数构造函数,@onMessage注解的Java方法用于接收传入的WebSocket信息,这个信息可以是文本格式,也可以是二进制格式。
OnOpen在这个端点一个新的连接建立时被调用。参数提供了连接的另一端的更多细节。Session表明两个WebSocket端点对话连接的另一端,可以理解为类似HTTPSession的概念。
OnClose在连接被终止时调用。参数closeReason可封装更多细节,如为什么一个WebSocket连接关闭。
更高级的定制如@Message注释,MaxMessageSize属性可以被用来定义消息字节最大限制,在示例程序中,如果超过6个字节的信息被接收,就报告错误和连接关闭。

复制代码
package action;

import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

//ws://127.0.0.1:8087/Demo1/ws/张三
@ServerEndpoint("/ws/{user}")
public class WSServer {
    private String currentUser;
    
    //连接打开时执行
    @OnOpen
    public void onOpen(@PathParam("user") String user, Session session) {
        currentUser = user;
        System.out.println("Connected ... " + session.getId());
    }

    //收到消息时执行
    @OnMessage
    public String onMessage(String message, Session session) {
        System.out.println(currentUser + ":" + message);
        return currentUser + ":" + message;
    }

    //连接关闭时执行
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        System.out.println(String.format("Session %s closed because of %s", session.getId(), closeReason));
    }

    //连接错误时执行
    @OnError
    public void onError(Throwable t) {
        t.printStackTrace();
    }
}
复制代码

url中的字符张三是的路径参数,响应请求的方法将自动映射。

五、测试运行

六、小结与消息推送框架

Socket在应用程序间通信被广泛使用,如果需要兼容低版本的浏览器,建议使用反向ajax或长链接实现;如果纯移动端或不需考虑非现代浏览器则可以直接使用websocket。Flash实现推送消息的方法不建议使用,因为依赖插件且手机端支持不好。关于反向ajax也有一些封装好的插件如“Pushlet”

6.1、开源Java消息推送框架 Pushlet

Pushlet 是一个开源的 Comet 框架,Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。

源码地址:https://github.com/wjw465150/Pushlet

Pushlet是一种comet实现:在Servlet机制下,数据从server端的Java对象直接推送(push)到(动态)HTML页面,而无需任何Javaapplet或者插件的帮助。它使server端可以周期性地更新client的web页面,这与传统的request/response方式相悖。浏览器client为兼容JavaScript1.4版本以上的浏览器(如InternetExplorer、FireFox),并使用JavaScript/DynamicHTML特性。而底层实现使用一个servlet通过Http连接到JavaScript所在的浏览器,并将数据推送到后者。

6.2、开源DotNet消息推送框架SignalR

SignalR是一个ASP .NET下的类库,可以在ASP .NET的Web项目中实现实时通信。在Web网页与服务器端间建立Socket连接,当WebSockets可用时(即浏览器支持Html5)SignalR使用WebSockets,当不支持时SignalR将使用长轮询来保证达到相同效果。

官网:http://signalr.net/

源码:https://github.com/SignalR/SignalR

七、代码下载

7.1、Java实现的服务器端代码与客户端代码下载

点击下载服务器端代码

点击下载客户端代码

7.2、DotNet服务器端手动连接实现代码下载

点击下载DotNet服务器端手动连接实现代码

7.3、DotNet下使用SuperWebSocket三方库实现代码下载

点击下载DotNet下使用SuperWebSocket三方库实现代码

sqlserver触发器insert,delete,update - Coder_小菜 - 博客园

mikel阅读(2874)

来源: sqlserver触发器insert,delete,update – Coder_小菜 – 博客园


-- =============================================
-- Author: 王伟
-- Create date: 2017-07-06
-- Description: 增、删、改开放时间表时,同步数据至考勤安排表
-- =============================================
CREATE TRIGGER [dbo].[TR_LM_SYS_KaiFangShiJian_IDU]
ON [dbo].[LM_SYS_KaiFangShiJian]
FOR INSERT,DELETE,UPDATE
AS
BEGIN
--新增
if(exists(select 1 from inserted) and not exists(select 1 from deleted))
begin
INSERT INTO LM_KQ_KaoQingAnPai
( KaoQingAnPaiID ,
ShiYanShiID ,
KaiShiRiQi ,
JieShuRiQi ,

XingQingBianHao ,
KaiShiShiJian ,
JieShuShiJian ,
SystemType ,

CreateOn ,
CreateDate ,
UpdateOn ,
UpdateDate
)
SELECT CONVERT(VARCHAR(100), A.KaiFangShiJianID) AS KaiFangShiJianID ,
CONVERT(VARCHAR(100), A.ShiYanShiID) AS ShiYanShiID ,
A.KaiShiRiQi ,
A.JieShuRiQi ,

A.XingQingBianHao ,
A.KaiShiShiJian ,
A.JieShuShiJian ,
AS SystemType ,

NULL ,
GETDATE() ,
NULL ,
GETDATE()
FROM Inserted AS A
INNER JOIN LM_SYS_ShiYanShiXinXi AS B ON A.ShiYanShiID = B.ShiYanShiID
INNER JOIN LM_MJ_Men AS C ON B.MenID = C.MenID
WHERE A.ShiFouMoRen = 0
AND B.IsDelete = 0
AND C.IsDelete = 0;
end

–删除
if(not exists(select 1 from inserted) and exists(select 1 from deleted))
begin
delete from LM_KQ_KaoQingAnPai
where KaoQingAnPaiID in(select KaiFangShiJianID from deleted)
end

–更新
if(exists(select 1 from inserted) and exists(select 1 from deleted))
begin
update LM_KQ_KaoQingAnPai set ShiYanShiID=a.KaiFangShiJianID
,KaiShiRiQi=a.KaiShiRiQi,JieShuRiQi=a.JieShuRiQi
,XingQingBianHao=a.XingQingBianHao,KaiShiShiJian=a.KaiShiShiJian
,JieShuShiJian=a.JieShuShiJian,UpdateDate=GETDATE()
from inserted a
where KaoQingAnPaiID=a.KaiFangShiJianID
end

END

GO

SQL Server审计功能入门:CDC(Change Data Capture) - Joe.TJ - 博客园

mikel阅读(1132)

SQL Server审计功能入门:CDC(Change Data Capture)

来源: SQL Server审计功能入门:CDC(Change Data Capture) – Joe.TJ – 博客园

介绍

SQL Server 2008引入了CDC(Change Data Capture),它能记录:

1. 哪些数据行发生了改变

2. 数据行变更的历史记录,而不仅仅是最终值。

跟CT(Change Tracking)相比,它通过作业实现异步变更跟踪(像事务复制),而CT是同步实现的。因此它对性能的影响较轻并且不会影响事务。

典型应用是在提取、传输和加载数据到其它数据源,就像图中的数据仓库。

clip_image001

实现

微软建议CDC结合快照快照隔离级别使用,可以避免读取变更数据与变更数据写入时的读写阻塞。

需要注意:快照隔离级别会有额外的开销,特别是Tempdb(所有的数据更改都会被版本化存到tempdb)。

复制代码
use master

go

create database CDCTest

go

alter database CDCTest set allow_snapshot_isolation on

go

--enable CDC on database CDCTest

use CDCTest

go

exec sys.sp_cdc_enable_db

go
复制代码

启用CDC之后会新增一个叫CDC的Schema和一系列的系统表、SP和View。官方建议不要直接查询系统表而是使用对应的系统SP/FN来获取CDC数据。

clip_image002

系统对象 说明 建议使用的对象
cdc.captured_columns 为在捕获实例中跟踪的每一列返回一行 sys.sp_cdc_get_source_columns
cdc.change_tables 为数据库中的每个更改表返回一行 sys.sp_cdc_help_change_data_capture
cdc.ddl_history 针对启用了变更数据捕获的表所做的每一数据定义语言 (DDL) 更改返回一行 sys.sp_cdc_get_ddl_history
cdc.lsn_time_mapping 为每个在更改表中存在行的事务返回一行 sys.fn_cdc_map_lsn_to_time (Transact-SQL) ,sys.fn_cdc_map_time_to_lsn (Transact-SQL)
cdc.index_column 为与更改表关联的每一索引列返回一行 sys.sp_cdc_help_change_data_capture
msdb.dbo.cdc_jobs 存储用于捕获和清除作业的变更数据捕获配置参数 NA
cdc.<capture_instance>_CT 对源表启用变更数据捕获时创建的更改表。 该表为对源表执行的每个插入和删除操作返回一行,为对源表执行的每个更新操作返回两行.capture_instance格式=SchameName_TableName cdc.fn_cdc_get_all_changes_<capture_instance> ,

cdc.fn_cdc_get_net_changes_<capture_instance>

创建测试表并对期启用CDC。使用sys.sp_cdc_enable_table 对表启用CDC。

复制代码
--Create a test table for CDC

use CDCTest

GO

create table tb(ID int primary key ,name varchar(20),weight decimal(10,2));

go

EXECUTE sys.sp_cdc_enable_table

    @source_schema = N'dbo'

  , @source_name = N'tb'

  , @role_name = null;

GO
复制代码

如果源表是数据库中第一个要启用变更数据捕获的表,并且数据库不存在事务发布,则 sys.sp_cdc_enable_table 还将为数据库创建捕获和清理作业。 它将 sys.tables 目录视图中的 is_tracked_by_cdc 列设置为 1。

对应的跟踪表cdc.dbo_tb_CT包含了源表所有的变更数据。它包含原来所有的列和5个新的列,结构如图:

clip_image003

验证

当在源表中操行数据更改操作,表cdc.dbo_tb_CT会记录下来。试一下:

clipboard[22]

为什么没有数据呢?因为之前介绍过了,CDC是靠作业来捕获变更数据的,我的Agent还没有运行。

手动启用后,就有数据了。

clip_image005

结果列的含义:

列名 数据类型 说明
__$start_lsn binary(10) 更改提交的LSN。在同一事务中提交的更改将共享同一个提交 LSN 值。
__$seqval binary(10) 一个事务内可能有多个更改发生,这个值用于对它们进行排序。
__$operation int 更改操作的类型:

1 = 删除

2 = 插入

3 = 更新(捕获的列值是执行更新操作前的值)。

4 = 更新(捕获的列值是执行更新操作后的值)。

__$update_mask varbinary(128) 位掩码,源表中被CDC跟踪的每一列对应一个位。如果__$operation = 1 或 2,该值将所有已定义的位设置为 1。如果__$operation = 3 或 4,则只有那些对应已更改列的位设置为 1。

现在再插入一行,并更新它,然后再删除ID=1的行。再查看结果:

clip_image006

简单说明一下跟踪的查询结果:总共5行,第一行和第二行是插入数据,第三行和第四行是更新前后的数据,第五行是删除数据。操作类型由_$operation值可得知。

简单应用

前文中创建的tb表,记录了每个人的姓名和体重变化信息。另外某一个数据库(表tb_rs),它是体重变化趋势报表的数据源。它每天同步一次数据,更新自己的数据。怎么用CDC来实现这个需求呢?

CDC中记录了start_lsn,如果能知道tb_rs上次同步完成时,tb中被同步的最大LSN。那下次同步时,只需要同步tb表中大于此LSN的变更记录即可。

问题就简单:获取上次同步完成tb的最大LSN,获取大于此LSN的所有变更记录,更新tb_rs。

复制代码
insert into tb

values(1,'Ken',70.2),(3,'Joe',66),(4,'Rose',50)

update tb

set weight=70

where ID=3;

delete from tb where name='Rose';

go

DECLARE @begin_time datetime, @end_time datetime, @begin_lsn binary(10), @end_lsn binary(10); 

--get the interval

select @begin_time=GETDATE()-1,@end_time=GETDATE();

--map the time to LSN of the CDC table tb

select  @begin_lsn = sys.fn_cdc_map_time_to_lsn('smallest greater than or equal', @begin_time),

  @end_lsn = sys.fn_cdc_map_time_to_lsn('largest less than or equal', @end_time);

--get the net changes within the specified LSNs

SELECT * FROM cdc.fn_cdc_get_net_changes_dbo_tb(@begin_lsn, @end_lsn, 'all');
复制代码

clip_image007

居然没有Rose的记录?Joe的信息被更新过,怎么才一条记录?

这是因为这里得到是净变更行,也就是最终结果的意思。新增然后又删除,不影响最终结果,所以没有。多次更新同一行的某一列数据,只返回最后更新的结果。

得到这个结果,我们就可以根据__$operation和实际数据定义同步数据的逻辑了。比如:

复制代码
--generate sync statements

SELECT (case __$operation when 2 then 'insert into tb_rs values ('+cast(ID as varchar(2))+', '+Name+', '+cast(weight as varchar(10))+')'

        when 4 then 'update tb_rs set name='+name+',weight='+cast(weight as varchar(10))+' where ID='++cast(ID as varchar(2)) END)

FROM cdc.fn_cdc_get_net_changes_dbo_tb(@begin_lsn, @end_lsn, 'all');
复制代码

对于更新过的行,同步数据时,我想要先判断出列是否被更改过和被更改的时间。更改过的列才需要被同步,而不是所有列同步一次。以name为例:

复制代码
DECLARE @begin_time datetime, @end_time datetime, @begin_lsn binary(10), @end_lsn binary(10); 

--get the interval

select @begin_time=GETDATE()-1,@end_time=GETDATE();

--map the time to LSN of the CDC table tb

select  @begin_lsn = sys.fn_cdc_map_time_to_lsn('smallest greater than or equal', @begin_time),

  @end_lsn = sys.fn_cdc_map_time_to_lsn('largest less than or equal', @end_time);

--get the all changes within the specified LSNs

SELECT *,

(Case sys.fn_cdc_has_column_changed('dbo_tb','name',__$update_mask) when 1 then 'Yes' when 0 then 'No' End) as isNameUpdated,

sys.fn_cdc_map_lsn_to_time(__$start_lsn) as updateTime

FROM cdc.fn_cdc_get_all_changes_dbo_tb(@begin_lsn, @end_lsn, 'all')

where __$operation in(3,4);

go
复制代码

CDC不仅能记录DML操作,还能记录DDL操作。查询cdc.ddl_history。

clip_image008

但有一点要格外注意:新增的列,能被CDC DDL跟踪到,但是新列的数据变更却不能被CDC跟踪到。如果需要跟踪它,先禁用表上的CDC,再启用即可。

 

CDC Agent Job

在指定的数据库中首次启用CDC,并且不存在事务复制,则会创建capture和cleanup两个作业:

clip_image009

capture作业是用于扫描日志文件,把变更记录写到变更表中。调用sp_MScdc_capture_job来实现,可以根据当前库的实际事务吞吐量来设置扫描参数和扫描间隔,使得在性能开销和跟踪需求间达到合理平衡。

cleanup作业是清理变更变表中的数据,默认三天的数据。

所以合理设定cleanup的间隔是非常重要的。

这两个作业的相关的配置存储在msdb.dbo.cdc_jobs中。当前的默认配置如图:

clip_image010

总结

1. CDC使用方便,易于配置,能与同步抽取等应用结合使用。

2. CDC能满足大多数对数据审计的要求,但不能告诉你“谁”更改了数据。

3. 虽说CDC是异步的,对应性能影响小,但还是会增加开销,特别是IO读写和容量方面的。开启CDC,每次更改,都至少会额外增加一次数据文件写和日志文件写操作。

————————————-

作者:Joe.TJ

Joe’s Blog:http://www.cnblogs.com/Joe-T/

 

transactionscope报“此操作对该事务的状态无效”问题 - 左正 - 博客园

mikel阅读(1129)

来源: transactionscope报“此操作对该事务的状态无效”问题 – 左正 – 博客园

编写的保存方法里面有个transactionscope代码一直报“此操作对该事务的状态无效”,弄了半天,原来是超时问题(transactionscope默认超时时间是1分钟)

经过修改,设置了超时时间为10分钟,目前已能正常保存:

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(6000000000)))
{

//保存内容

}

C# 线程手册 第六章 线程调试与跟踪 代码跟踪 - DanielWise - 博客园

mikel阅读(954)

来源: C# 线程手册 第六章 线程调试与跟踪 代码跟踪 – DanielWise – 博客园

我们下一个将要分析的代码检测技术是跟踪。在一个多线程应用程序中,这个技术非常重要。当已经启动了多个任务时,你可以跟踪一个线程的行为和相互之间的各个线程之间的影响。我们稍后将看到在这种情况下使用调试器是不现实的。.NET Framework 提供了很多有用的类来帮助开发人员轻松地实现跟踪功能。让我们看一下.NET Framework 提供的System.Diagnostics 命名空间中的跟踪类。

1. Trace: 这个类有很多向一个监听器写消息的静态方法。默认情况下,VS.NET 中的调试输出窗口将被用来作为监听程序,由于使用了监听器集合,所以你可以添加不同的监听器,比如文本监听器或者Windows事件日志监听器。

2. Debug: 这个类的方法和Trace 类的一样,都向一个监听器应用程序中写入信息。这两个类在使用上最大的不同是,Trace 用于运行阶段,Debug 用于开发阶段。

3. BooleanSwitch: 这个类允许我们定义一个开关来打开/关闭跟踪消息。

4. TraceSwitch: 这个类提供四个不同的跟踪级别来帮助开发人员选择发送不同级别的消息给监听器。

Trace 类

在这部分,我们将分析Trace 类中使用最频繁的几个方法。这些方法由.NET Framework包装好后提供给我们。Trace 类位于System.Diagnostics 命名空间中并提供很多静态方法来发送消息给监听程序。

下表列出来由Trace 类提供的一些静态方法:

2012-4-23 21-04-07

这些方法的默认行为取决于选择的监听器程序。例如,当使用默认监听器的时候,Assert() 方法会显示一个消息框。

默认监听器程序

Trace 类提供一个允许我们添加一个新的监听器程序的监听器集合。当没有向监听器集合中添加新的监听器对象时,Trace 类就会使用默认的监听器程序:调试输出窗体。这个窗体由Visual Studio.NET IDE 在调试过程中提供。让我们来看一个简单的例子,TraceExample:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TraceExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Trace.WriteLine("Enter Main()");

            for (int i = 0; i < 6; i++)
            {
                Trace.WriteLine(i);
            }

            Trace.WriteLine("Exiting from Main()");
        }
    }
}

这段代码真的非常简单;当进入和退出Main() 方法时它打出调试信息,在循环中累加变量值。在下一个截图中,你可以看到Visual Studio.NET 输出监听器是如何显示信息的:

2012-4-23 21-26-57

Trace 类也提供了两个有用的方法来断言错误提示:Assert() 和 Fail(). 前者允许开发人员检查作为参数传入的条件是否满足并在条件不满足时向监听器中写入一条消息。后者会在每次发生失败时向监听器中写入一条消息。当监听器集合中没有其他监听器对象时,Assert() 方法显示一个消息对话框来提供用户断言失败。下面的代码片段,TraceAssert.cs, 可以在SQL Server 服务被故意停止且引发一个连接错误的时候测试出来:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Data.SqlClient;

namespace TraceExample
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            //Create a thread
            Thread t = new Thread(new ThreadStart(DBThread));

            //Start the thread
            t.Start();
        }

        private static void DBThread()
        {
            //Create a connection object
            SqlConnection dbConn = 
                new SqlConnection(@"Data Source=DANIELFORWARD\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True");

            //Create a command object to execute a SQL statement
            SqlCommand dbComm = new SqlCommand("SELECT * FROM Region", dbConn);

            SqlDataReader dr = null;

            Trace.WriteLine(DateTime.Now + "- Execute SQL statement");

            try
            {
                //Open the connection to the database
                dbConn.Open();

                //Assert that the connection opened
                Trace.Assert(dbConn.State == System.Data.ConnectionState.Open, "Error", "Connection failed...");

                //Execute the SQL statement
                dr = dbComm.ExecuteReader(System.Data.CommandBehavior.CloseConnection);

                //Assert that the statement executed OK
                Trace.Assert(dr != null, "Error", "The SqlDataReader is null.");

                while (dr.Read())
                {
                    //Reading records
                    Trace.WriteLine(dr[0].ToString());
                }
            }
            catch (Exception ex)
            {
                //Log the error to the Trace application
                Trace.Fail("An error occured in database access, details: " + ex.Message);
            }
            finally
            {
                if (!dr.IsClosed && dr != null)
                {
                    dr.Close();
                }
            }
        }
    }
}

在Main() 方法中,创建并启动了一个新线程。新线程运行DBThread()中的代码。代码简单地连接了下SQL Server 的Northwind数据库,从Region表中收集所有数据。如果SQL Server 不可用,在执行代码过程中会弹出下面的断言失败窗体。

2012-4-23 21-53-30

引发异常断言的代码为:

Trace.Assert(dbConn.State == System.Data.ConnectionState.Open, "Error", "Connection failed...");

你可以看到,第一个参数检查连接状态是否打开。当连接没有打开时它将被设置为false, 然后会显示断言失败。你将会从本章的后续部分了解到也可以使用配置文件禁用消息跟踪。那样的话,你就可以在运行时决定是否显示断言消息。

Visual Studio 调试技巧 - LightSmaile - 博客园

mikel阅读(1263)

来源: Visual Studio 调试技巧 – LightSmaile – 博客园

写在前面:假定你在日常的工作中使用到了Visual Studio,并期望了解一些调试技巧来提高工作效率,也许本文适合你。以下Visual Studio简称vs。

一、入门

以最简单的控制台应用程序为例,代码如下:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         int result = Sum(2, 3);
 6         Console.WriteLine("2+3={0}", result);
 7     }
 8 
 9     private static int Sum(int a,int b)
10     {
11         return a + b;
12     }
13 }

调试的根本目的是跟踪代码、程序的状态,判断是否按照期望的行为运行。常用的跟踪手段有控制台输出、日志输出以及断点调试。

  1. 控制台输出用于开发环境,可以在vs输出窗口中查看程序输出的内容如下图所示:

由于是控制台应用程序,Console.WriteLine() 输出的内容不会显示在输出窗口,故采用Trace.WriteLine() 。对非控制台应用程序,Console.WriteLine() 输出的内容会正常显示在输出窗口。

2. 日志输出用于开发环境和生产环境,但更多用于生产环境,用来收集程序的运行信息。常用的日志组件有Log4Net、NLog以及自定义日志组件。依据问题严重程度大致分为严重错误、错误、警告、信息以及调试信息等几个级别。可结合实际需求灵活配置。

3. 断点调试多用于开发环境,通过设置断点,让程序在指定的位置暂停,以便观察上下文环境情况。

以上图为例,添加断点后,鼠标移动到变量名上,可以观察一些变量的值。对于复杂类型的变量,通过选中变量,右键选择快速监视的方式。避免鼠标移动后,监视的信息消失。

以上三种调试方法中,对于开发环境而言,使用最为频繁的方法当数断点调试。后面以断点调试为主,深入介绍。

二、进阶

  • 启动外部程序

要使用断点调试,需要满足一些断点调试的条件。对于可执行程序,如控制台应用程序、窗体应用程序、WPF应用程序以及Web应用程序,启动调试后,可以在期望的位置添加断点。而对于如动态库类型,不可以直接启动调试。想要调试这类项目,有两种方式。一种是可以设置项目属性中的启动操作,指定引用该动态库的可执行程序路径。

另一种方式是运行调用了动态库的可执行程序,通过附加可执行程序进程的方式来调试。

  • 附加进程

新建 DllDemo 动态库项目,添加 MyMath 类,添加静态方法 Max(int a,int b) 。代码如下:

 1 using System;
 2 
 3 namespace DllDemo
 4 {
 5     public class MyMath
 6     {
 7         public static int Max(int a,int b)
 8         {
 9             return Math.Max(a, b);
10         }
11     }
12 }

添加对 DllDemo 动态库项目引用, 并修改控制台应用程序如下。为了方便后续调试,控制台应用程序中添加 Console.Read()。

 1 using DllDemo;
 2 using System;
 3 using System.Diagnostics;
 4 
 5 namespace DebugDemo
 6 {
 7     class Program
 8     {
 9         static void Main(string[] args)
10         {
11             Console.WriteLine("等待键盘输入...");
12 
13             Console.Read();
14 
15             int result = Sum(2, 3);
16 
17             Console.WriteLine(string.Format("2+3={0}", result));
18 
19             result = MyMath.Max(2, 3);
20 
21             Console.WriteLine(string.Format("MyMath.Max(2, 3)={0}", result));
22         }
23 
24         private static int Sum(int a, int b)
25         {
26             return a + b;
27         }
28     }
29 }

运行控制台应用程序DebugDemo.exe ,附加该进程,在合适的位置添加断点。

  • 条件断点

在多层循环中,有时想要满足一定条件时命中断点。这时,条件断点会比较有效。以下面代码为例,想要index = 10(number > 10)时命中断点。

private int Sum(int number)
{
    int result = 0;

    for(int index= 0;index<number;index++)
    {
        result += index;
    }

    return result;
}

在合适的位置,按下F9设置断点。右键红色的断点,选择条件…,在条件中输入 index == 10 然后关闭。运行程序进入循环体后,会在index = 10时,命中断点

  • 即时窗口

有时,当程序运行起来后,进入了某种上下文环境中。此时,想要跟踪程序在另一种上下文环境的运行情况,可以在即时窗口中直接设置某些上下文环境。代码如下所示:

internal class People
{
    public string Name { get; set; }
    public bool IsMale { get; set; }
}

static void Main(string[] args)
{
    PrintSex(new People() { Name = "张三",IsMale =true});
}

private static void PrintSex(People people)
{
    if (people.IsMale)
    {
        Console.WriteLine("{0} is 男性", people.Name);
    }
    else
    {
        Console.WriteLine("{0} is 女性", people.Name);
    }
}

程序运行以后,会进入people为男性的分支。在 if (people.IsMale) 行设置断点,当进入断点以后,在即时窗口中更改 people.IsMale 的值后按下Enter键执行,使程序进入另一个分支。

  • 更改执行顺序

以即时窗口涉及到的代码为例,无论上下文环境如何,想要直接进入 people.IsMale 的分支,可在if (people.IsMale) 行设置断点。进入断点后,在红色断点处。直接按住鼠标左键,拖动到 Console.WriteLine(“{0} is 女性”, people.Name); 行,进入该分支。

  • 查看调用堆栈

当程序包含接口继承、抽象类继承等逻辑,导致结构过于复杂,知道功能入口以及出口,想要了解过程时,调用堆栈会比较有用。以下面代码为例:

private static void DoWork()
{
    DoWork1();
}

private static void DoWork1()
{
    DoWork2();
}

private static void DoWork2()
{
    DoWork3();
}

private static void DoWork3()
{
    Console.Write("DoWork3");
}

假设知道功能入口为DoWork,功能结果为DoWork3,想要了解DoWork3的调用逻辑,可以在DoWork3中设置断点,启动调试后打开调用堆栈窗口,如下图:

  • 异常设置

当程序运行以后,结果不是预期的。初步猜测发生了异常,由于某些原因,捕获了异常,却未妥善处理,导致异常信息被“吞”掉。此时,异常设置会格外有效。以下面代码为例:

private static void TryToDivideByZero()
{
    try
    {
        int a = 9;
        int b = 0;
        int c = a / b;
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

由于方法中存在异常,又有异常捕获,后续逻辑会被打断,此时对异常设置做如下设置:

重新调试程序会有如下结果,方便快速定位异常发生点。

三、高级

在某些场景下,开发环境运行正常,非开发环境运行异常,依赖常规手段无法定位问题原因,想要断点调试,非开发环境运行缺少VS时,远程调试会比较有效。

在VS安装目录下拷贝远程调试所需的文件夹x86,x64到非开发环境

依据远程目标机系统环境,运行x86/x64文件夹下msvsmon.exe,选择工具中的选项菜单做如下配置:

运行待调试程序后,在VS中选择调试>附加到进程(ctrl+alt+p),设置连接类型,连接目标(远程ip地址或计算机名)后查找,会自动列出相关内容。

在可用进程中选择对应的进程

在合适的位置添加断点即可开始调试了

.Net利用Newtonsoft进行解析Json的快捷方法-云栖社区-阿里云

mikel阅读(694)

来源: .Net利用Newtonsoft进行解析Json的快捷方法-云栖社区-阿里云

现在异构系统之间的数据交换多采用Json格式

.Net如何快捷地解析Json和将Object转换成json呢?

1.利用Newtonsoft解析Json字符串

在百度上查找资料,利用Newtonsoft解析Json多是把Json转换成某一特定的Object,这样的不便之处是有事需要特意为了解析该json而写一个class,对一些不规范的的Json,难以用Object来转换的,用他们的方法就不叫难行得通了。

如,需要对以下Json进行解析

{
    "total": 1,
    "rows": [
        {
            "datetime": "2017-12-19 0:00:00",
            "product": "硅树脂",
            "value": "3"
        }
    ]
}

对于该Json,如果我们先转成object再来解析,那么问题来了,这个object怎么写呢?不好写,那么,我们就用最直接暴力的方法!!

 

 

            JArray obj = (JArray)JsonConvert.DeserializeObject("["+JsonText+"]");

需要注意的是,我们上文中提到的json的最外围是没有[ ]的,所以拼接一对[ ](代码中的红色部分)

 

如果不加这对中括号,会产生以下报错:Cannot access child value on Newtonsoft.Json.Linq.JValue.

好,如何取json中的值呢?

比如  我们如果要去total的值1   怎么取?

 

obj[0]["total"].toString()

如果要去取rows下的项目的数量呢?

 

 

obj[0]["rows"].count()

如果要取rows的第一项的value的值呢?

 

 

obj[0]["rows"][0]["value"].toString()

 



学会了吗?很灵活简单,类似数组的用法。

 

最后,Newtonsoft还可以把object(包括list和枚举等)转换成json

 


JsonConvert.SerializeObject(object);

C#中使用GUID - Elliott.Dong - 博客园

mikel阅读(1019)

来源: C#中使用GUID – Elliott.Dong – 博客园

GUID(全局统一标识符)是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成GUID的API。生成算法很有意思,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。GUID的唯一缺陷在于生成的结果串会比较大。”
1. 一个GUID为一个128位的整数(16字节),在使用唯一标识符的情况下,你可以在所有计算机和网络之间使用这一整数。

2. GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。例如:337c7f2b-7a34-4f50-9141-bab9e6478cc8 即为有效的 GUID 值。

3. 世界上(Koffer注:应该是地球上)的任何两台计算机都不会生成重复的 GUID 值。GUID 主要用于在拥有多个节点、多台计算机的网络或系统中,分配必须具有唯一性的标识符。

4. 在 Windows 平台上,GUID 应用非常广泛:注册表、类及接口标识、数据库、甚至自动生成的机器名、目录名等。

 

.NET中使用GUID

GUID 在 .NET 中使用非常广泛,而且 .NET Framework 提供了专门 Guid 基础结构。
Guid 结构的常用法包括:
1) Guid.NewGUID()
生成一个新的 GUID 唯一值
2) Guid.ToString()
将 GUID 值转换成字符串,便于处理
3)构造函数 Guid(string)
由 string 生成 Guid 结构,其中string 可以为大写,也可以为小写,可以包含两端的定界符“{}”或“()”,甚至可以省略中间的“-”,Guid 结构的构造函数有很多,其它构造用法并不常用。

.NET Framework 中可以使用类 GuidConverter 提供将 Guid 结构与各种其他表示形式相互转换的类型转换器。

 

C#中生成一个GUID

处理一个唯一标识符使得存储和获得信息变得更加容易。在处理一个数据库中这一功能变得尤其有用,因为一个GUID能够操作一个主键。

同样,SQL Server也很好地集成了GUID的用途。SQL Server数据类型uniqueidentifier能够存储一个GUID数值。你可以通过使用NEWID()函数在SQL Server中生成这一数值,或者可以在SQL Server之外生成GUID,然后再手动地插入这一数值。

在.NET中,后面一种方法显得更加直接。.NET Framework中的基本System类包括GUID数值类型。除此之外,这一数值类型包含了处理GUID数值的方法。特别地,NewGUID方法允许你很容易地生成一个新的GUID。

 

 


1using System;
2namespace DisplayGUID
3{
4    class Program
5    {
6        static void Main(string[] args)
7        {
8            GenerateGUID();
9        }

10        static void GenerateGUID()
11        {
12            Console.WriteLine(“GUID:” + System.Guid.NewGuid().ToString());
13        }

14    }

15}

 

下面为这一程序的输出:(虽然不同系统之间的GUID是变化的。)

GUID: 9245fe4a-d402-451c-b9ed-9c1a04247482

以上范例使用到System.Guid空间名称的NewGuid函数来返回一个数值。在这一点上,你可以看到GUID是一个很好的功能,但在程序的什么地方使用到它们,并如何使用它们?

 

在程序中使用一个GUID

一个GUID可以在后台数据库中操作一个主键。以下代码使用一个GUID在一个后台数据库中存储信息,这一数据库包含以下的列:

pk_guid  —uniqueidentifier  数据类型
name      —nvarchar  数据类型
这样出现一个包含文本框的简单Windows窗体。当选择按钮时,文本框中的数据被插入到数据库中name列。通过程序代码可以生成一个GUID并存储在pk_guid列中:

 


1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Data;
5using System.Drawing;
6using System.Linq;
7using System.Text;
8using System.Windows.Forms;
9using System.Data.SqlClient;
10
11namespace GuidSqlDBExample
12{
13    public partial class Form1 : Form
14    {
15        public Form1()
16        {
17            InitializeComponent();
18        }

19
20        private void btnInsert_Click(object sender, EventArgs e)
21        {
22            string _str = “server=(local);Initial Catalog=TestGuid;Integrated Security=SSPI”;
23            using (SqlConnection conn = new SqlConnection(_str))
24            {
25                try
26                {
27                    string _sqlInsert = “INSERT INTO dbo.Guid(pk_guid, name) VALUES (‘”+ System.Guid.NewGuid().ToString() + “‘, ‘” + txtName.Text + “‘)”;
28                    conn.Open();
29                    SqlCommand _cmd = new SqlCommand(_sqlInsert, conn);
30                    _cmd.ExecuteNonQuery();
31                }

32                catch (Exception ex)
33                {
34                    System.Console.Write(“Error: ” + ex.Message);
35                }

36            }

37        }

38    }

39
40}

 

 

另一个GUID程序将一个唯一的标识符分配给一个.NET类或者接口,也就是说,GUID作为一个属性被分配给类或者接口。可以使用标准属性语法来实现这一过程: 本

我们可以扩展第一个范例来分配一个GUID。System.Runtime.InteropServices空间名称必须被引用来使用GUID属性。以下C#代码实现了这一过程:

 

 


1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using System.Runtime.InteropServices;
6
7namespace GuidTest
8{
9    [Guid(“9245fe4a-d402-451c-b9ed-9c1a04247482”)]
10    class Program
11    {
12        static void Main(string[] args)
13        {
14            GenerateGUID();
15        }

16        static void GenerateGUID()
17        {
18            Console.WriteLine(“GUID:” + System.Guid.NewGuid().ToString());
19        }

20    }

21}

 

GUID永远是方便的
对于程序开发的各个方面,.NET Framework简化了建立和处理GUID数值的过程。在.NET程序需要的地方,这一功能很容易地生成唯一的数值。