xposed框架 微信群发源码_qiaoxin6666的博客-CSDN博客

mikel阅读(1157)

来源: xposed框架 微信群发源码_qiaoxin6666的博客-CSDN博客

基于xposed框架的微信二次开发模块,方便开发者用微信做一些扩展功能(如微信机器人、微信群发、多群转播(直播)等。。。)
目前支持功能:
发文本消息
发图片消息
发语音消息
获取微信好友列表
群列表
支持群发消息
[注:本模块为开发版,是用于开发者做二次开发的,所提到的功能只是提供接口,并不是安装模块就可以使用这些功能,开发者可以使用这些接口来开发相应功能的软件,如果你不是开发者,请下载提供的示例软件测试相关功能]
更多功能更新中。。。

微信模块 http://repo.xposed.info/module/com.easy.wtool

微信机器人demo下载地址:https://github.com/weechatfly/wtoolsdkrobot/raw/master/%E5%BE%AE%E4%BF%A1%E6%9C%BA%E5%99%A8%E4%BA%BA-wtoolsdkDemo.apk

加QQ群:153770839 讨论群
————————————————
版权声明:本文为CSDN博主「qiaoxin6666」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qiaoxin6666/article/details/55211861

Xposed框架的使用--简单入门_xingkong_hdc的博客-CSDN博客

mikel阅读(1242)

来源: Xposed框架的使用–简单入门_xingkong_hdc的博客-CSDN博客

##Xposed框架的使用

###1.xposed是什么?
框架是一款开源框架,其功能是可以在不修改APK的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。Xposed 就好比是 Google 模块化手机的主体,只是一个框架的存在,在添加其他功能模块(Modules)之前,发挥不了什么作用,但是没了它也不行。也正因为如此,Xposed 具有比较高的可定制化程度。Moto X 用户可定制手机的外观、壁纸、开机动画等,Xposed 则允许用户自选模块对手机功能进行自定义扩充。

###2.xposed框架的使用
xposed的主要作用是hook应用方法,动态劫持方法的运行。xposed的使用需要xposed框架.apk和XposedBridgeApi-54.jar配合使用 。而xposed框架apk可以看成是个Xposed 框架的管理工具,在这里可以安装、更新Xposed 框架,也可以卸载Xposed 框架,查看安装日志。也可设置Xposed 框架安装器是否联网更新框架及模块。
xposed框架.apk需要root权限,所以需要一台root后的手机或者模拟器,这里推荐使用模拟器即可。首先下载一个模拟器,常用的模拟器有bluestacks蓝叠,夜神模拟器,genemotion等,由于genemotion的cpu框架是x86的,很多应用无法运行,所以选择了夜神模拟器。下载地址:https://www.yeshen.com/。下载安装之后如下界面:

模拟器是自带root权限的,下面下载xposed框架.apk
链接: https://pan.baidu.com/s/1QjQ2CG1Br2SUoGbgHoRfTA 密码: imt4!

下载完成后直接拖到模拟器上安装,安装之后打开apk,点击框架,进去点击安装更新:

授予root权限,然后重启模拟器。xposed框架apk可以工作了,但是现在只是安装好了xposed框架apk,并没有任何hook模块工作。hook模块可以自己去下载,也可以自己编写模块。
###3.xposed模块的编写
下面通过简单的demo演示如何编写一个xposed模块,来hook住我们想要hook的方法。这里就hook自己的app的一个加载广告的代码,来动态拦截广告的加载。

1.使用AS新建一个项目XposedDemo:

2.在MainActivity 模拟加载广告的代码,正常点击加载广告按钮,会加载广告,但是使用xposed对该方法进行hook之后,可以改变这个方法的执行。:

public class MainActivity extends AppCompatActivity {

private TextView tv_ad;
private Button btn_load_ad;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_ad = findViewById(R.id.tv_ad);
btn_load_ad = findViewById(R.id.btn_load_ad);

btn_load_ad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv_ad.setText(getTTAd());
}
});
}

public String getTTAd(){
return “广告加载成功”;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在MainActivity 的布局文件如下:

<?xml version=”1.0″ encoding=”utf-8″?>
<Android.support.constraint.ConstraintLayout xmlns:Android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.MainActivity”>

<TextView
android:id=”@+id/tv_ad”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”等待广告加载”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />

<Button
android:id=”@+id/btn_load_ad”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”加载广告”/>
</android.support.constraint.ConstraintLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2.下载XposedBridgeApi-54.jar的依赖包,地址
https://pan.baidu.com/s/1HfDapCF0rLxKa_HIZ9orew
密码:iy2p 下载完成后在app目录下创建文件夹mylib(名字随意),并把XposedBridgeApi-54.jar复制到mylib下面,注意不能直接放到lib里面,然后再app的build.gradle的dependencies加上provided fileTree(dir: ‘mylib’, include: [’.jar’]),或者compileOnly fileTree(dir: ‘mylib’, include: [’.jar’]),根据你的gradle版本选择。

dependencies {
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation ‘com.android.support:appcompat-v7:27.1.1’
implementation ‘com.android.support.constraint:constraint-layout:1.1.3’
testImplementation ‘junit:junit:4.12’
androidTestImplementation ‘com.android.support.test:runner:1.0.2’
androidTestImplementation ‘com.android.support.test.espresso:espresso-core:3.0.2’

compileOnly fileTree(dir: ‘mylib’, include: [‘*.jar’])

}
1
2
3
4
5
6
7
8
9
10
11
3.修改AndroidManifest.xml文件,在applicatio标签下面加入以下标签:

<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />

<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<meta-data
android:name=”xposedmodule”
android:value=”true”/>
<meta-data
android:name=”xposeddescription”
android:value=”这是一个xposed应用”/>
<meta-data
android:name=”xposedminversion”
android:value=”54″/>
</application>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
注意这里面的三个meta-data标签的name不能错误,不然xposed框架apk无法识别自定义编写的xposed模块。

4.编写hook工具类XposedHookUtil对getTTAd方法进行拦截替换,XposedHookUtil实现IXposedHookLoadPackage接口,复写handleLoadPackage方法,并替换原有的getTTAd方法,来进行拦截。

public class XposedHookUtil implements IXposedHookLoadPackage {
String class_name = “com.hdc.xposeddemo.MainActivity”;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

Class clazz = loadPackageParam.classLoader.loadClass(class_name);
XposedHelpers.findAndHookMethod(clazz, “getTTAd”, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
return “广告被拦截了”;
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
5.在main文件夹下创建文件夹assets,并在assets下面创建xposed_init文本文件,注意这里文件名必须是xposed_init。并在xposed_init里面添加hook工具类的完整包名路径:com.hdc.xposeddemo.xposed.XposedHookUtil

6.运行apk
点击run app按钮运行apk,如果android studio 没有找到夜神模拟器,可能是模拟器还没有关联起来。关联方法:cmd 进入命令窗口,执行
cd C:\Program Files\Nox\bin,注意cd到你的夜神模拟器的安装路径,之后执行:nox_adb.exe connect 127.0.0.1:62001
然后AS可以关联成功,运行apk。

点击加载广告,这时候显示广告加载成功,因为还有使这个xposed模块工作。

7.安装xposed模块
打开Xposed框架apk,

点击模块,看到里面有刚刚编写的模块,勾选之后重启:

重启之后,xposed模块生效,点击加载广告,显示广告被拦截了:

###总结
总的来说此demo也借鉴了许多前人的经验,至此xposed框架的基本使用已经完成。如有错误,欢迎指正。
————————————————
版权声明:本文为CSDN博主「大神温酒煮咖啡」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xingkong_hdc/article/details/82531505

重磅!VirtualXposed,让你无需Root也能使用Xposed框架! - Xposed框架中文站

mikel阅读(2142)

来源: 重磅!VirtualXposed,让你无需Root也能使用Xposed框架! – Xposed框架中文站

VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~8.1)。一直以来Xposed框架最大的入门难度就在于设备需要Root,然后还要Recovery,还有承担变砖的各种搞基风险,现在这一切都不用再担心了!感谢Xposed作者rov89,感谢VirtualApp作者asLody@github!目前来看VirtualXposed的稳定性已经相当出色了!

v 0.17.3版本更新内容:

  • 修复Google服务无法在VirtualXposed中使用的问题
  • 修复相同Apps启动失败的问题
  • 适配 Android Q

VirtualXposed v0.17.3版本直接下载:

重磅!VirtualXposed,让你无需Root也能使用Xposed框架!-Xposed框架中文站此处内容已经被作者隐藏,请输入验证码查看内容

验证码:

请关注本站微信公众号,回复“验证码”,获取验证码。在微信里搜索“Appfound”或者“Manyapps”或者微信扫描右侧二维码都可以关注本站微信公众号。

v0.9.9版本更新内容:
Xposed底层调整,提升对插件的兼容性。
保守模式默认开启,如果你使用没问题;需要主动前往高级设置关闭。
新增常用模块的功能,推荐一些VXP使用稳定的模块。
QQ群/微信群反馈隐藏,请使用邮件反馈。
修复若干Xposed相关的Bug。

v0.9.8版本更新该内容:
提高 Android 7.0/7.1系统上的稳定性。
解决某些应用界面跳转时闪动的问题。
兼容全面屏(支持18:9屏幕)
新增“保守模式”(设置-高级设置)

v0.9.0版本修复三星8.0上无法使用的问题。改善后台接受消息的问题。修复闪退,提升稳定性。特别对于三星设备应该有不少体验的提升。

重磅!VirtualXposed,让你无需Root也能使用Xposed框架!-Xposed框架中文站

VirtualXposed

源码地址:https://github.com/android-hacker/VirtualXposed,欢迎大家去围观,去贡献!

敬告大家,VirtualXposed还处于初始的阶段,如果不能够像原生的Xposed那般好使也请多多包容。Xposed本身就挑机器,挑ROM,VirtualXposed可能更是如此,但好处是即便是无法工作也不会造成变砖之类的,所以能用就用吧,不能用就卸载了!放平心态,祝大家搞基愉快。

VirtualXposed使用准备

首先在 VirtualXposed发布页面 下载最新的VAExposed安装包安装到手机。

VirtualXposed安装模块

打开 VirtualXposed,在里面安装要使用的APP,以及相应的Xposed模块即可。

注意:所有的工作(安装Xposed模块,安装APP)必须在 VirtualXposed中进行,否则Xposed模块不会有任何作用!比如,将微信直接安装在系统上(而非VirtualXposed中),防撤回安装在VirtualXposed中;或者把微信安装在VirtualXposed上,防撤回插件直接安装在系统上;或者两者都直接安装在系统上,均不会起任何作用。

重磅!VirtualXposed,让你无需Root也能使用Xposed框架!-Xposed框架中文站

VirtualXposed

在VirtualXposed中安装App的方式

直接复制已经在系统中安装好的APP,比如如果你系统中装了微信,那么可以直接复制一份。
通过外置存储直接安装APK文件;点主界面的➕,然后选择后面两个TAB即可。
在VirtualXposed中安装Xposed模块,可以跟安装正常的APK一样,以上两种安装App的方式也适用于安装Xposed模块。不过,你也可以通过VirtualXposed中内置的XposedInstaller来安装和管理模块,跟通常的XposedInstaller使用方式一样;去下载页面,下载安装即可。

VirtualXposed框架已经支持的模块

微X模块
微信巫师
MDWechat
应用变量
冲顶助手
情迁抢红包
步数修改器
指纹支付

VirtualXposed已知问题

由于暂不支持资源HOOK,因此资源钩子不会起任何作用;使用资源HOOK的模块,相应的功能不会生效。部分插件的兼容性有问题,比如QX模块。

支持和加入
目前VirtualXposed 还不完善,如果你对非ROOT下实现Xposed感兴趣;欢迎加入!你可以通过如下方式来支持:

直接贡献代码,提供Feature,修复BUG!
使用你拥有的手机,安装你常用的Xposed模块,反馈不可用情况;协助帮忙解决兼容性问题!
提出体验上,功能上的建议,帮助完善VirtualXposed!

下载地址

  • 名称:VirtualXposed框架
  • 格式:.apk
  • 版本:0.16.1
  • 大小:7.15M
  • 点击下载

基于动态代理的WebAPI/RPC/webSocket框架,一套接口定义,多个通讯方式 - hubro - 博客园

mikel阅读(1047)

来源: 基于动态代理的WebAPI/RPC/webSocket框架,一套接口定义,多个通讯方式 – hubro – 博客园

API/RPC/webSocket三个看起来好像没啥相同的地方,在开发时,服务端,客户端实现代码也大不一样

最近整理了一下,通过动态代理的形式,整合了这些开发,都通过统一的接口约束,服务端实现和客户端调用

基于这样的形式,WebAPI/RPC/webSocket只需要定义一套接口,就能达到通用的效果

 

示例接口约束

复制代码
    public class TestObj
    {
        public string Name { get; set; }
    }
    public interface ITestService
    {
        void Login();
        bool Test1(int a,int? b,out string error);
        TestObj Test2(TestObj obj);
    }
    public class TestService : AbsService, ITestService
    {
        [LoginPoint]
        public void Login()
        {
            SaveSession("hubro", "7777777777", "test");
        }

        public bool Test1(int a, int? b, out string error)
        {
            var user = CurrentUserName;
            var tag = CurrentUserTag;

            error = "out error";
            Console.WriteLine(a);
            Console.WriteLine(b);
            return true;
        }

        public TestObj Test2(TestObj obj)
        {
            Console.WriteLine(obj.ToJson());
            return obj;
        }
    }
复制代码

上面是一个标准接口和接口实现,并继承了AbsService,

Login方法标注了LoginPoint特性,表示登录切入点,调用此接口时首先得调用此方法,方法内SaveSession存储登录状态

Test1方法内使用了CurrentUserName,以获取登录方法保存的Session

特点:

  • 通过接口约束服务端和客户端调用,参数定义透明化,out也支持
  • 真正的方法远程调用,任意参数类型,个数
  • 集成登录认证逻辑,自定义登录认证过程,也可以自定义Session实现
  • 集成简单数据签名,不用费心数据安全问题

技术实现:

  • Dynamitey实现接口类型代理
  • DotNetty实现TCP通讯
  • 对象二进制序列化

RPC调用

服务端

复制代码
var server = new ServerCreater().CreatetRPC(805);
            server.CheckSign();
            server.SetSessionManage(new SessionManage());
            server.Register<ITestService, TestService>();
            server.Start();
复制代码

CreateRPC是一个扩展方法,引用CRL.RPC获取

SetSessionManage,自定义Session存储,默认是内存里

CheckSign 处理请求时,进行参数签名验证

客户端接口调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var clientConnect = new RPCClientConnect("127.0.0.1", 805);
            clientConnect.UseSign();
            var service = clientConnect.GetClient<ITestService>();
        label1:
            service.Login();
            Console.WriteLine("loginOk");
            int? a = 1;
            string error;
            service.Test1(1, a, out error);
            Console.WriteLine("error:" + error);
            var obj2 = service.Test2(new TestObj() { Name = "test" });
            Console.WriteLine("obj2:" + obj2.ToJson());
            Console.ReadLine();
            goto label1;

 

客户端先调用login方法进行登录,并记录服务端返回的token

Test1方法将token回传给服务器以验证登录状态,并进行远程调用

当调用了UseSign方法,就会对提交的参数进行签名,签名KEY为登录后服务端返回的TOKEN,服务端同样按此对签名进行比较

 

动态webApi

同样基于上文结构,接口定义就不粘贴了

服务端定义

1
2
3
4
5
6
var server = new ServerCreater().CreatetApi();
            server.CheckSign();
            server.SetSessionManage(new SessionManage());
            server.Register<ITestService, TestService>();
            var listener = new ServerListener();
            //listener.Start("http://localhost:809/");//自定义监听

 

如果宿主是.NET网站 在web.config增加处理module

1
2
3
4
5
<system.webServer>
    <modules>
      <add name="DynamicModule" type="CRL.DynamicWebApi.DynamicModule" />
    </modules>
  </system.webServer>

 

如果是单独程序,启动ServerListener即可

客户端调用

复制代码
var clientConnect = new CRL.DynamicWebApi.ApiClientConnect("http://localhost:53065");
            //var clientConnect = new CRL.DynamicWebApi.ApiClientConnect("http://localhost:8022");
            clientConnect.UseSign();
            var service = clientConnect.GetClient<ITestService>();
        
        label1:
            service.Login();
            Console.WriteLine("loginOk");
            int? a = 1;
            string error;
            service.Test1(1, a, out error);
            Console.WriteLine("error:" + error);
            var obj2 = service.Test2(new TestObj() { Name = "test" });
            Console.WriteLine("obj2:" + obj2.ToJson());
            Console.ReadLine();
            goto label1;
复制代码

 

WebSocket

WebSocket是一个比较特殊的方式,常用来做双工通讯的方式,客户端能往服务端发送数,服务端也能往客户端发送据

除去服务端往客户端发数据,也可以采用接口调用的形式实现,在这里,服务端往客户端发送数据,客户端采用了订阅的方式

服务端实现

1
2
3
4
5
6
7
8
9
10
11
12
var server = new ServerCreater().CreatetWebSocket(8015);
            server.CheckSign();
            server.SetSessionManage(new SessionManage());
            server.Register<ITestService, TestService>();
            server.Start();
            new CRL.Core.ThreadWork().Start("send", () =>
            {
                var socket = server.GetServer() as CRL.WebSocket.WebSocketServer;
                socket.SendMessage("hubro"new socketMsg() { name = DateTime.Now.ToString() }, out string error);
                Console.WriteLine("send msg");
                return true;
            }, 10);

 

上面演示代码,服务端开启了一个线程,定时往客户端”hubro”发送数据 socket.SendMessage

客户端实现

复制代码
var clientConnect = new CRL.WebSocket.WebSocketClientConnect("127.0.0.1", 8015);
            clientConnect.UseSign();
            clientConnect.SubscribeMessage<socketMsg>((obj) =>
            {
                Console.WriteLine("OnMessage:" + obj.ToJson());
            });
            clientConnect.StartPing();
            var service = clientConnect.GetClient<ITestService>();
        label1:

            service.Login();
            Console.WriteLine("loginOk");
            int? a = 1;
            string error;
            service.Test1(1, a, out error);
            Console.WriteLine("error:" + error);
            var obj2 = service.Test2(new TestObj() { Name = "test" });
            Console.WriteLine("obj2:" + obj2.ToJson());
            Console.ReadLine();
            goto label1;
复制代码

clientConnect.SubscribeMessage就是订阅消息了,通过订阅的方式,处理服务端发送的数据

 

可以看到以上各种形式,服务端实现和客户端调用基本相同,定义的接口能重复使用,做接口通讯效果杠杠的

具体实现方式和细节功能参见源码和demo,已经开源,请自行下载

源码地址:

CRL:

https://github.com/hubro-xx/CRL5

RCP:

https://github.com/hubro-xx/CRL5/tree/master/RPC

WebAPI:

https://github.com/hubro-xx/CRL5/tree/master/DynamicWebApi

WebSocket:

https://github.com/hubro-xx/CRL5/tree/master/WebSocket

ORM之炀,打造自已独特的开发框架CRL - hubro - 博客园

mikel阅读(692)

来源: ORM之炀,打造自已独特的开发框架CRL – hubro – 博客园

ORM一直是长久不衰的话题,各种重复造轮子的过程一直在进行,轮子都一样是圆的,你的又有什么特点呢?

CRL这个轮子造了好多年,功能也越来越标准完备,在开发过程中,解决了很多问题,先上一张脑图描述CRL的功能

开发框架的意义在于

  • 开发更标准,更统一,不会因为不同人写的代码不一样
  • 开发效率更高,无需重新造轮子,重复无用的代码,同时简化开发流程
  • 运行效率得到控制,程序稳定性得到提高

围绕这几点,抛开常规的增删改查,我们来讲些与众不同的

1.与众不同之一,动态数据源,天生适合分库分表

可动态配置的功能总比静态的灵活,扩展性强

目前看到的框架多数访问对象实例化都类似于

var context = new MsSqlContext(ConnectionString);

在对象初始时,就绑定上了数据库连接串, 这样写没什么问题,但是不好扩展
如:需要动态切换库,表,根据租户信息访问不同的数据库,或不同类型的数据库,或是读写分离,这时,急需处理的技术问题就来了,分库分表的解决方案,读写分离的方案
在数据连接绑定的情况下,这种问题很不好解决
又或者传入多个连接串,在调用时,手动选择调用的库或表,对于这种方式,只能说耦合太严重,得关心配置,又得关心调用,在CRL之前的版本里,有这样实现过,弃用了

然而根据IOC的理念,这种问题也不是不好解决,让数据访问对象抽象化实现就能办到了
数据查询方法不再直接调用数据访问对象,而是调用抽象工厂方法,由抽象工厂方法来实例化访问对象,过程表示为

数据查询方法(组件内) => 抽象工厂(组件内) => 抽象实现(组件外)

基于这样的理念,CRL在设计之初,就使用了的这样的方式,以代码为例

!数据访问实现

以下实现了分库分表和mongoDB切换

以下在程序启动时初始

复制代码
var builder = new CRL.SettingConfigBuilder();
            builder.UseMongoDB();//引用CRL.Mongo 使用MongoDB
                                 //注册自定义定位,按MemberSharding传入数据定义数据源位置
                                 //注册一
            builder.RegisterLocation<Code.Sharding.MemberSharding>((t, a) =>
            {
                var tableName = t.TableName;
                if (a.Name == "hubro")//当名称为hubro,则定位到库testdb2 表MemberSharding1
                {
                    tableName = "MemberSharding1";
                    return new CRL.Sharding.Location("testdb2", tableName);
                }
                //返回定位库和表名
                return new CRL.Sharding.Location("testdb", tableName);
            });
            //注册二
            builder.RegisterDBAccessBuild(dbLocation =>
            {
                if (dbLocation.ManageName == "mongo")
                {
                    var conn = CRL.Core.CustomSetting.GetConfigKey("mongodb");
                    return new CRL.DBAccessBuild(DBType.MongoDB, conn);
                }
                return null;
            });
            //注册三
            builder.RegisterDBAccessBuild(dbLocation =>
            {
                //自定义定位,由注册一传入
                if (dbLocation.ShardingLocation != null)
                {
                    return new CRL.DBAccessBuild(DBType.MSSQL, "Data Source=.;Initial Catalog=" + dbLocation.ShardingLocation.DataBaseName + ";User ID=sa;Password=123");
                }
                return new CRL.DBAccessBuild(DBType.MSSQL, "server=.;database=testDb; uid=sa;pwd=123;");
            });
复制代码

 

!数据访问类,类似于仓储的形式,根据实际业务实现
定位使用示例

复制代码
public class MemberManage : CRL.Sharding.BaseProvider<MemberSharding>
{
}
var instance=new MemberManage();
instance.Add(new MemberSharding(){Name="hubro"});
复制代码

根据定位规则 运行到注册一,此数据将会插入到 库testdb2 表MemberSharding1

常规切换示例

1
2
3
4
5
6
public class MongoDBTestManage : CRL.BaseProvider<MongoDBModel2>
{
    public override string ManageName => "mongo";
}
var instance=new MongoDBTestManage();
instance.Add(new MongoDBModel2(){name="hubro"});

根据数据访问规则,运行到注册二,此数据将会插入mongodb

可以看到,在上面代码中,没有看到任何数据连接串的传入,数据的访问都由初始时动态分配,对于方法调用是不透明的,调用者不用关心数据源的问题

2.与众不同之二,表结构自动维护

在新技术的支持下,程序和数据库的绑定关系越来越模糊,现在可能是用的SQLServer,回头可能改成MySQL了,或者改成mongoDB
依赖数据库开发变成越来越不可取,效率也很低
再后来出现了DBFirst方式,虽解决了部份问题,但也很麻烦,如:

建立数据库模型=>导入数据库=>T4模版生成代码(修修补补)

而使用CRL后,过程一步到位,在别人还在用PM设计表结构索引时,你已经设计好了业务结构,效率杠杠的

编写实体类,实现对象访问=>调试运行,自动创建表结构(关键字,长度,索引)

同时,CRL还提供了手动维护方法,使能够按实体结构重建/检查数据表
也提供了对象结构文档导出,不用提心文档的问题
详细介绍看这里
https://www.cnblogs.com/hubro/p/6038118.html

3.与众不同之三,动态缓存

使用缓存可以大大提高程序的运行效率,使用REDIS或MONGODB之类的又需要额外维护
对于单应用程序,程序集内缓存非常有用
CRL内置了缓存实现和维护
只需按方法调用就行了,缓存创建维护全自动
如:
从数据库查

var item = instance.QueryItem(b => b.Id==1)

从缓存查

var item = instance.QueryItemFromCache(b=>b.Id==1);

也支持按查询自定义缓存

复制代码
var query = Code.ProductDataManage.Instance.GetLambdaQuery();
//缓存会按条件不同缓存不同的数据,条件不固定时,慎用
query.Where(b => b.Id < 700);
int exp = 10;//过期分钟
query.Expire(exp);
var list = query.ToList();
复制代码

 

基于这样的形式,可以将所有查询都走缓存,再也不用担心数据库查询效率了,简值中小项目开发利器
详细介绍看这里
https://www.cnblogs.com/hubro/p/6038540.html

4.与众不同之四,应对复杂查询

因为没有查询分支的概念,处理复杂的查询,一票ORM估计得退场了,虽然合理的结构设计会减少查询复杂度,但谁能保证呢
CRL查询分支过程如下

主查询 => CreateQuery子查询 => 返回匿名对象筛选LambdaQueryResultSelect => 主查询嵌套子查询 => 返回结果

理论上只要符合调用逻辑,可以无限嵌套
示例:

复制代码
var q1 = Code.OrderManage.Instance.GetLambdaQuery();//主查询
            var q2 = q1.CreateQuery<Code.ProductData>();//创建一个子查询
            q2.Where(b => b.Id > 0);
            var view = q2.CreateQuery<Code.Member>().GroupBy(b => b.Name).Where(b => b.Id > 0).Select(b => new { b.Name, aa = b.Id.COUNT() });//GROUP查询
            var view2 = q2.Join(view, (a, b) => a.CategoryName == b.Name).Select((a, b) => new { ss1 = a.UserId, ss2 = b.aa });//关联GROUP
            q1.Join(view2, (a, b) => a.Id == b.ss1).Select((a, b) => new { a.Id, b.ss1 });//再关联
            var result = view2.ToList();
            var sql = q1.ToString();
复制代码

生成SQL打印如下

复制代码
SELECT t1.[Id] AS Id,
t2.[ss1] AS ss1
FROM [OrderProduct] t1 with(nolock)
INNER JOIN
(SELECT t2.[UserId] AS ss1,
t3.[aa] AS ss2
FROM [ProductData] t2 with(nolock)
INNER JOIN
(SELECT t3.[Name] AS Name,
COUNT(t3.Id) AS aa
FROM [Member] t3 with(nolock)
WHERE (t3.[Id]>@par1)
GROUP BY t3.[Name]) t3 ON (t2.[CategoryName]=t3.[Name])
WHERE (t2.[Id]>@par0) ) t2 ON (t1.[Id]=t2.[ss1])
复制代码

 

不管是JOIN后再GROUP,还是GROUP后再GROUP,还是GROUP后再JOIN,通通不是问题
详细介绍看这里
https://www.cnblogs.com/hubro/p/6096544.html

5.与众不同之五,查询抽象,非关系型数据库支持

通过对Lambda表达式的解析,可以实现不同的查询转换,如MongoDB,或ElasticSearch(目前只实现了MongoDB)
有人问,这样有什么用呢?
好处就是,在CRL框架下,一套LambdaQuery走天下,不用写各种差异很大的查询方法了,在动态数据源的支持下,数据拆分游刃有余
如:
之前有个报表存在MSSQL里,发现数据量太大了,查询慢,改由MongoDB,程序不用怎么调整,直接在配置里改为MongoDB即可

以MongoDB为例

CRLLambdaQuery=>CRLExpression=>BsonDocument=>MongoDB

在[数据访问实现]示例中,演示了如何切换到MongoDB
代码实现见项目:CRL.Mongo

6.题外之六,请使用仓储模式

在上文提到,好多框架会直接返回一个数据访问对象,如

var obj1context.Query<TestEntity>(b=>b.Id==1).ToSingle();

然而这样会导致滥用,直接在WEB层用,在Service层随意用,如

var obj2=context.Query<TestEntity2>(b=>b.Id==1).ToSingle();
var obj3=context.Query<TestEntity3>(b=>b.Id==1).ToSingle();

某一天,TestEntity3要换库了,查找一下引用,傻眼了,上百个引用(接手别人的项目,亲身体验过这种痛苦,一个个改)
好在CRL开始就杜绝了这种情况发生,对据的访问必须通过BaseProvider实现,而BaseProvider就是一个仓储的形式

7.题外之七,查询效率

ORM效率无非分两点,实体映射效率和语法解析效率,

对于映射反映在,一次返回多行数据,转换为实体集合

对于语法解析效率,按参数调用多次,返回一行数据,转换为实体

测式程序和SQL为本机,CPU空闲正常,2核6G服务器

一张图表明一切(不同机器实际情况可能有差异)

CRL效率虽不是最高的,但也不是最差的,测试项目见:

https://github.com/hubro-xx/CRL5/tree/master/Test/TestConsole

 

大概列举了以上几项,还有好多特有的东西,轮子好不好,东西南北滚滚试试

CRL开发框架虽然写好长时间,但一直在Debug状态中, 最近又升级了,分离了数据访问层,不同数据库引用不同的数据访问层,数据访问层实现也很简单,只需要写两个文件,如MySql,实现MySqlHelper和MySQLDBAdapter
见:https://github.com/hubro-xx/CRL5/tree/master/CRL.Providers/CRL.MySql
同时,版本也升级到5.1,项目结构发生了改变

源码地址:https://github.com/hubro-xx/CRL5

CRL目前.NET版本为.net 4.5, 有时间了再整理整理netstandard版本

除了ORM,CRL还带 动态API,RPC,WebSocket,api客户端代理实现
https://www.cnblogs.com/hubro/p/11652687.html
微服务注册,发现,调用集成参见:
https://github.com/hubro-xx/CRL5/blob/master/Consul/ConsulTest/Program.cs

嵌套事务(一):.NET中嵌套事务运用的一点思考_邓福勇-CSDN博客

mikel阅读(582)

来源: 嵌套事务(一):.NET中嵌套事务运用的一点思考_邓福勇-CSDN博客

说实际情形:

由于一个操作中分两步完成,不妨定义为 Method1(),Method2()。只有当一两步都成功执行时,整个操作才算完成,否则要回滚所有数据。

 

一开始,我的实现思路是:

Operate()

{

创建事务连接,开始事务…..

 

If(Method1() 成功&& Method2() 成功)

{ 事务执行成功 }

Else

{

事务回滚,执行不成功

}

 

关闭事务,连接….

}

说明:Method1(),Method2()是独立的方法,里面也是事务。

一开始,想当然的认为没问题,简单测试也没什么问题。

但实际上是有问题的。问题在哪儿呢?

 

不多说,假设Method1()成功,Method2()失败。会出现什么后果:Operate()

回滚,Method2()回滚,Method1()不回滚(为什么,每个方法内,都new了一个新的事务对象,是独立于外部事务的)。

 

所以换了一下思路,只有硬来了

 

Operate()

{

创建事务连接,开始事务…..

//1 将Method1()的实现代码写在这儿

…….

成功(不提交),继续下走;不成功,回滚。

 

//2 将Method2()的实现代码写在这儿–有问题的(2011-1-17注)

…….

成功(不提交),继续下走;不成功,回滚。

// 3

在这里才确定了整个事务成功。

……

}

 

 

所以在.NET 中嵌套事务实现几个动作同时进行时,一定要小心。本文的思路是,将所有的分步操作,在一个事务中实现,也就没了嵌套事务,但实际的结果是一样,并提高了性能。只是以代码的长度作为代价的,呵呵。

当然,有时允许成功了的部分不回滚,那第一种情形也就适用了。

———————————–2011分隔线——————————————–

 

现在再来看上来的东西,可以改进: 接着讲更实际些的。

1:跨库操作(来源于两个不同的数据库) demo:

SQLConnection connConsDB = dboConsDB.CreateConnection as SQLConnection;
SqlConnection connStatDB = dboStatDB.CreateConnection as SqlConnection;

connConsDB.Open();
connStatDB.Open();

using (SqlTransaction tranConsDB = connConsDB.BeginTransaction())
{
using (SqlTransaction tranStatDB = connStatDB.BeginTransaction())
{
try
{
//ip数据操作
OperatIp(tranConsDB, tranStatDB);
//cookie数据操作
OperatCookie(tranConsDB, tranStatDB);

tranConsDB.Commit();
tranStatDB.Commit();

result.IsSuccess = true;
result.Message = “广告注册用户激活游戏数据操作成功”;
}
catch (Exception ex)
{
exceptionDBLog.InsertExceptionLog(ex);

tranConsDB.Rollback();
tranStatDB.Rollback();

result.IsSuccess = false;
result.Message = “广告注册用户激活游戏数据操作失败”;
}
}
}

 

2:多步操作为一事务(即我们刚开始讨论的东西)

SqlConnection con = dbo.CreateConnection as SqlConnection;
con.Open();
SqlTransaction tran = con.BeginTransaction();

try
{
//获取数据源
DataResult resultADClickIp = GetADClickIpResult();
DataResult resultRegisterTmp = GetRegisterTmpResult();
DataResult resultRechargeExchangeTmp = GetRechargeExchangeTmpResult();
DataResult resultWenDaoGameActiveTmp = GetWenDaoGameActiveTmp();
DataResult resultMHSSGameActivateTmp = GetMHSSGameActivateTmp();
DataResult resultJDFJGameActivateTmp = GetJDFJGameActivateTmp();
DataResult resultDanGameActivateTmp = GetDanGameActivateTmp();
DataResult resultAdVisitLogTmp = GetAdVisitLog();
DataResult resultPageViewLogTmp = GetPageViewLog();

//转移数据
MoveADClickIpTbl(resultADClickIp, tran);//广告点击IP表(第二层核心数据)[定期转移])

MoveRegisterTmpTbl(resultRegisterTmp, tran); //注册用户临时表(第二层)[保留3个月] 数据

MoveMHSSGameActivateTmpTbl(resultMHSSGameActivateTmp, tran);// 转移**游戏激活日志临
MoveJDFJGameActivateTmpTbl(resultJDFJGameActivateTmp, tran);// 转移**游戏激活日志临时
MoveDanGameActivateTmpTbl(resultDanGameActivateTmp, tran);// 转移**游戏激活日志临时表
MoveAdVisitLogTmpTbl(resultAdVisitLogTmp, tran);//转移广告访问日志数据源
MovePageViewLogTmpTbl(resultPageViewLogTmp, tran);//站点日志部分
tran.Commit();
return true;
}
catch (Exception ex)
{
tran.Rollback();
exceptionDBLog.InsertExceptionLog(ex);
return false;
}

 

SQLServer 事务相关:《嵌套事务(一):.NET中嵌套事务运用的一点思考》

《嵌套事务(二):SQLServer中嵌套事务使用》

SQLServer 嵌套事务机制 测试 》
————————————————
版权声明:本文为CSDN博主「xiaoyong322」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dfyong/article/details/5684381

SqlServer嵌套事务机制 测试_邓福勇-CSDN博客

mikel阅读(598)

来源: SqlServer嵌套事务机制 测试_邓福勇-CSDN博客

SQLServer 事务相关:《嵌套事务(一):.NET中嵌套事务运用的一点思考》

《嵌套事务(二):SQLServer中嵌套事务使用》

《 SQLServer嵌套事务机制 测试  》

今天我们主研究一下SQLServer中嵌套事务使用。代码能说明大多数问题,看代码。
1.嵌套事务提交原理测试

结果:

Trancount before transaction: 0
After first BEGIN TRAN: 1
After second BEGIN TRAN: 2
After first COMMIT TRAN: 1
After second COMMIT TRAN: 0

我们可以得出:

1.可以看到每一个BEGIN TRAN语句都会使@@TRANCOUNT增加1;

2.每一个COMMIT TRAN语句都会使@@TRANCOUNT减少1;

3.如前所述,一个值为0的@@TRANCOUNT意味着没有打开的事务;

4.因此,在@@TRANCOUNT值从1降到0时结束的事务发生在最外层事务提交的时候。

2. 嵌套事务回滚
2.1  嵌套事务回滚最外面事务,对内部事务有什么影响?
我们再来看一段代码:

结果:没有数据。

现在看来:无论数据是否提交,只要最外层回滚了就会导致所有内部所有嵌套类回滚。

2.2 嵌套事务回滚内部嵌套事务呢?
再看一段代码:

结果:

有异常信息:

(1 行受影响)
消息 6401,级别 16,状态 1,第 9 行
无法回滚 InProc。找不到该名称的事务或保存点。

(1 行受影响)

(2 行受影响)

我们可以看到:ROLLBACK TRANSACTION InProc 是错误的。原因是没有保存还原点 InProc。代码应该改为如下(具体原因请往下看):

BEGIN TRANSACTION InProc
save tran InProc;
INSERT INTO #TestTrans VALUES (1,’aaaa’);
ROLLBACK TRANSACTION InProc;
操作前保存好回滚点(save tran InProc),回滚时指定当时保存的位置,SQLServer才知道回滚到哪儿去。

3. 事务原理
往下读之前必须了解:全局变量@@trancount 可以确定是否存在打开的事务及其嵌套的深度。

 

提交的事务不能撤销或回滚。

当不存在打开的事务时,@@trancount 等于 0。

执行 begin tran [tranName]语句将 @@trancount 增加 1。

执行commit tran [tranName]语句将 @@trancount 减小 1。

执行 rollback tran  会回滚整个事务并设置@@trancount 为 0。

 

执行 ” rollback tran  tranName”语句时有两种情况:

if(tranName 之前 是用 ” Save Tran tranName” )  @@trancount值不变

否则,@trancount 减小1

 

具体测试代码:

— 创建临时表
CREATE TABLE #TestTrans(Cola INT PRIMARY KEY,
Colb varchar(20) NOT NULL);

select @@TRANCOUNT ‘未开外部事务’;

/* 外部事务 */
BEGIN TRANSACTION OutOfProc;
select @@TRANCOUNT ‘开外部事务’;

–内部事务
BEGIN TRANSACTION InProc

select @@TRANCOUNT ‘开内部事务1’;

save tran InProc;

select @@TRANCOUNT ‘保存内部事务1′;
INSERT INTO #TestTrans VALUES (1,’aaaa’);
ROLLBACK TRANSACTION InProc;

select @@TRANCOUNT ‘回滚内部事务1′;

–内部事务2
BEGIN TRANSACTION InProc2
INSERT INTO #TestTrans VALUES (2,’222’);

–内部事务21
BEGIN TRANSACTION InProc21
select @@TRANCOUNT ‘开内部事务21′;
INSERT INTO #TestTrans VALUES (3,’2422’);
COMMIT TRANSACTION InProc21;
select @@TRANCOUNT ‘提交内部事务21’;

COMMIT TRANSACTION InProc2;
select @@TRANCOUNT ‘提交内部事务2’;

/* 提交外部事务 */
COMMIT TRANSACTION OutOfProc;

select @@TRANCOUNT ‘提交外部事务’;

SELECT * FROM #TestTrans;

drop table #TestTrans
结果:

其他:
保存事务回滚点–可以有选择的提交或回滚内部嵌套事务。
思路

检查@@TRANCOUNT的值,以此来确定是否需要开始一个事务。如果@@TRANCOUNT大于0,则不开启新事务,只需要保存的个回滚位置即可;否则,开启新事务。下面是操作:

 

1.保存回滚点:

declare @trancount int –commit,rollback只控制本存储过程
set @trancount = @@trancount
if (@trancount=0) /*判断事务记数,根据情况确定使用保存点或者新建一个事务*/
begin tran curtran–当前事务点,rollback、commit都从这里开始
else
save tran curtran
2. 回滚指定“回滚点”:

if(@error <> 0 or @pay_way_error = 0) –@pay_way_error 决定了是否需要回滚
begin
rollback tran curtran
set @result = -1 –异常
end

if(@error <> 0 or @pay_way_error = 0) –@pay_way_error 决定了是否需要回滚
begin
rollback tran curtran
set @result = -1 –异常
end

————————————————
版权声明:本文为CSDN博主「xiaoyong322」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dfyong/article/details/7355426

嵌套事务(二):SqlServer中嵌套事务使用--事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配 --根本问题_邓福勇-CSDN博客

mikel阅读(569)

来源: 嵌套事务(二):SqlServer中嵌套事务使用–事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配 --根本问题_邓福勇-CSDN博客

今天又遇见了以前遇到的问题,一个聪明的人不应该将同一个错误出现两次错误,但事实上来,在我身上出了好几次,就其原因是对SQLServer 事务的提交与回滚没有深刻的认识。

问题:
1. System.Data.SQLClient.SQLException (0x80131904): EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。

2. EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。

后面的内容,是我之前写的东西,主要是一些测试代码,但是呢,我没有很深入的理解。现在直接说清楚本质的东西,把后面的精华再提上来说。

提交的事务不能撤销或回滚。
当不存在打开的事务时,@@trancount 等于 0。
执行 begin tran [tranName]语句将 @@trancount 增加 1。
执行commit tran [tranName]语句将 @@trancount 减小 1。
执行 rollback tran  会回滚整个事务并设置@@trancount 为 0。
执行 ” rollback tran  tranName”语句时有两种情况:
if(tranName 之前 是用 ” Save Tran tranName” 建立的 )  @@trancount值不变

否则,@trancount 减小1

注意:save tran 命令,不会使@@trancount加1

分析:
只要提交或者回滚事务后,程序内部改变了事务参数@@TRANCOUNT,就会出上述的错误,无一例外。

试图直接用Sql ”  set @@trancount = 1;”,这是SQLServer 不允许做的。

各位,出现上面的错误,最多的可能是在嵌套事务中。

 

如果不嵌套:没有begin tran前,@@trancount为0;  begin tran后,@@trancount  此时为1;完事后就commit或rollback,@@trancount  此时为0;--不般我们是写不错的。

嵌套呢:

看看我之前写的一个存储过程:

 

declare @trancount int –commit,rollback只控制本存储过程
set @trancount = @@trancount;

if (@trancount=0) /*判断事务记数,根据情况确定使用保存点或者新建一个事务*/
begin tran current_tran–当前事务点,rollback、commit都从这里开始
else
save tran current_tran
…….

….做事去了

…….

 

if @error_code != 0 or @logErrorCode != 1
begin
rollback tran current_tran
set @error_code = -1; — 失败
end
else
begin
commit tran current_tran
set @error_code = 1; — 成功
end
有没有问题?(current_tran是保存点哈,不明白的,后面有比较详细的介绍)

我用了好久了(在一个项目里面),可是突然有一天,也就是今天,它出事了。原因嘛,虽然写的是嵌套的,之前都没有嵌套调到过。

我在外围开了一个事务,再来调这个存储过程,当它 commit tran current_tran 时(rollback tran current_tran是不会有事的),会出什么错误?如果你不能很明确的告诉我,说明你还没有理解得深刻。做个选择吧?

1.”…BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。”

2.”…BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。

 

答案:【2】。

线索分析:我是在外部开了一个事务的,所以在未进入该存储过程以前@@trancount的值应该为1;进入时,save tran current_tran, @@trancount值没有变;完事的,执行commit tran current_tran,@@trancount的值应该为0;--所以,进入前,出来后,@@trancount值发生了改变,SqlServer不干了(原因,自己去想吧:拆散了begin tran 配对)。

怎么解决:

1.进入子事务前先记录@@trancount,我们用变量@trancount来记录。

2. 提交子事务前,先判断之前的@trancount是否为0;为0表示”该事务”前没有事务调用,可以直接提交事务;不为0,表明进入该事务前已经有一个事务,该事务是子事务,不能提交。

— 如果当前计数为0,则提交.
— 因为Commit tran ,@@TRANCOUNT会减1。嵌套事务时,调用该存在过程(作为子过程,此时@@TRANCOUNT > 0),
— 只是保存了tran, @@TRANCOUNT没有发生改变;直接Commit会使@@TRANCOUNT减1,会打破事务对(Begin Tran)
if(@trancount = 0)
begin
commit tran current_tran
end
set @error_code = 1; — 成功

SqlServer 事务相关:《嵌套事务(一):.NET中嵌套事务运用的一点思考》

《嵌套事务(二):SqlServer中嵌套事务使用》

《SqlServer 嵌套事务机制 测试 》
————————————————
版权声明:本文为CSDN博主「xiaoyong322」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dfyong/article/details/6546138

Sqlserver——异常总结——详解嵌套事务中EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配发生的原因及处理方法_qq_40205468的博客-CSDN博客

mikel阅读(1412)

来源: Sqlserver——异常总结——详解嵌套事务中EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配发生的原因及处理方法_qq_40205468的博客-CSDN博客

下面是测试表和存储过程的代码

—创建测试表

IF EXISTS ( SELECT * FROM sys.tables WHERE name = ‘tt’ )
DROP TABLE dbo.tt ;

CREATE TABLE dbo.tt ( ID INT IDENTITY , Name NVARCHAR (100), TransCount INT ) ;
GO

—创建主存储过程
IF EXISTS ( SELECT * FROM sys.procedures WHERE name = ‘P_proc’ )
DROP PROC P_proc ;
GO

CREATE PROC P_proc
AS
BEGIN
BEGIN TRAN ;

INSERT INTO dbo.tt ( Name, TransCount ) SELECT ‘查询1’, @@TRANCOUNT ;

EXEC dbo.S_proc ;

RETURN ;
END ;
GO

—创建子存储过程

IF EXISTS ( SELECT * FROM sys.procedures WHERE name = ‘S_proc’ )
DROP PROC S_proc ;
GO

CREATE PROC S_proc
AS
BEGIN
BEGIN TRAN ;

INSERT INTO dbo.tt ( Name, TransCount ) SELECT ‘查询2’, @@TRANCOUNT ;

ROLLBACK ;

RETURN ;
END ;
执行主存储过程P_proc

分析这个错误提示,可以发现,这个错误其实是由子存储过程报错报错抛出的,

调用S_proc之前的 事务层数是1 ,而执行之后的事务层数是0 所以系统抛出执行异常。

其中 提示 上一计数 = 1,当前计数 = 0。

当单独执行时子存储过程时,可以看到。

是没有任务问题的。

我们再做个测试,独立执行子存储过程之前 手动开启两个事务,执行结果

可以看到上一计数的值正好和 事务的开启层数相同。

可以得出结论:调用存储过程,会对执行前后事务的层数作判断(查询 @@TRANCOUNT 可以看到当前会话的事务层数),如果不相等时会错误  ‘EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 =X,当前计数 =X’

——————————————–下面介绍一种解决方法

看示例代码

——Sp避免嵌套调用异常,编写格式

CREATE PROC TestSp ( @ReturnValue INT OUTPUT )
AS
BEGIN
SET XACT_ABORT ON ;

SET NOCOUNT ON ;

–开启事务–
BEGIN TRANSACTION ;

–创建事务节点(节点名格式: TRANSACTION +节点顺序(1,2,3,4)+ ‘_’+ Sp名称 )
SAVE TRANSACTION TRANSACTION1_TestSp ;

—-业务逻辑代码
BEGIN
INSERT tt SELECT 1 ;

—–异常处理1(回滚指定到事务节点)
IF 1 = 0
BEGIN
SET @ReturnValue = -1 ;

ROLLBACK TRANSACTION TRANSACTION1_TestSp ;

COMMIT ;

RETURN ;
END ;

END ;

–无异常,提交事务
SET @ReturnValue = 1 ;

COMMIT ;

RETURN ;
END ;
上面用到了一个SQL server的特性 :事务节点。

并以上述为例,讲述其用法和含义:存储过程中开启事务(@@TranCount=1),同时创建一个事务节点 TRANSACTION1_TestSp,当执行业务代码时:发生异常,并进行异常处理时,将回滚事务节点(而非回滚事务),将节点范围内的所有事务日志全部回滚(回滚之后的事务层数没有变化,@@TranCount未变化,仍然未1,只是回滚了事务日志),之后进行正常的提交,@@TranCount的层数-1;未发生异常时,正常提交@@TranCount的层数-1。

可以看到,不管最终的执行结果是成功返回还是异常返回,结果都只会将@@TranCount-1,而不会回滚整个事务层,避免了嵌套事务中的‘EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配’问题。

————原创,纯手打,觉得对您有帮助的话,帮忙点个赞哦!
————————————————
版权声明:本文为CSDN博主「志向数据库架构师的初级DBA」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40205468/article/details/100561823

C#中TransactionScope的使用方法和原理_weixin_34348805的博客-CSDN博客

mikel阅读(482)

来源: C#中TransactionScope的使用方法和原理_weixin_34348805的博客-CSDN博客

在.net 1.1的时代,还没有TransactionScope类,因此很多关于事务的处理,都交给了SQLTransaction和SQLConnection,每个Transaction是基于每个Connection的。这种设计对于跨越多个程序集或者多个方法的事务行为来说,不是非常好,需要把事务和数据库连接作为参数传入。

在.net 2.0后,TransactionScope类的出现,大大的简化了事务的设计。示例代码如下:

static void Main(string[] args)
{
using (TransactionScope ts = new TransactionScope())
{
userBLL u = new userBLL();
TeacherBLL t = new TeacherBLL();
u.ADD();
t.ADD();
ts.Complete();
}
}
只需要把需要事务包裹的逻辑块写在using (TransactionScope ts = new TransactionScope())中就可以了。从这种写法可以看出,TransactionScope实现了IDispose接口。除非显示调用ts.Complete()方法。否则,系统不会自动提交这个事务。如果在代码运行退出这个block后,还未调用Complete(),那么事务自动回滚了。在这个事务块中,u.ADD()方法和t.ADD()方法内部都没有用到任何事务类。

TransactionScope是基于当前线程的,在当前线程中,调用Transaction.Current方法可以看到当前事务的信息。具体关于TransactionScope的使用方法,已经它的成员方法和属性,可以查看MSDN。

TransactionScope类是可以嵌套使用,如果要嵌套使用,需要在嵌套事务块中指定TransactionScopeOption参数。默认的这个参数为Required。

该参数的具体含义可以参考http://msdn.microsoft.com/zh-cn/library/system.transactions.transactionscopeoption(v=vs.80).aspx

比如下面代码:

static void Main(string[] args)
{
using (TransactionScope ts = new TransactionScope())
{
Console.WriteLine(Transaction.Current.TransactionInformation.LocalIdentifier);
userBLL u = new userBLL();
TeacherBLL t = new TeacherBLL();
u.ADD();
using (TransactionScope ts2 = new TransactionScope(TransactionScopeOption.Required))
{
Console.WriteLine(Transaction.Current.TransactionInformation.LocalIdentifier);
t.ADD();
ts2.Complete();
}
ts.Complete();
}
}
当嵌套类的TransactionScope的TransactionScopeOption为Required的时候,则可以看到如下结果,他们的事务的ID都是同一个。并且,只有当2个TransactionScope都complete的时候才能算真正成功。

如果把TransactionScopeOption设为RequiresNew,则嵌套的事务块和外层的事务块各自独立,互不影响。

static void Main(string[] args)
{
using (TransactionScope ts = new TransactionScope())
{
Console.WriteLine(Transaction.Current.TransactionInformation.LocalIdentifier);
userBLL u = new userBLL();
TeacherBLL t = new TeacherBLL();
u.ADD();
using (TransactionScope ts2 = new TransactionScope(TransactionScopeOption.RequiresNew))
{
Console.WriteLine(Transaction.Current.TransactionInformation.LocalIdentifier);
t.ADD();
ts2.Complete();
}
ts.Complete();
}
}

可以看到,他们的事务id是不一样的。

TransactionScopeOption设为Suppress则为取消当前区块的事务,一般很少使用。

对于多个不同服务器之间的数据库操作,TransactionScope依赖DTC(Distributed Transaction Coordinator)服务完成事务一致性。

但是对于单一服务器数据,TransactionScope的机制则比较复杂。主要用的的是线程静态特性。线程静态特性ThreadStaticAttribute让CLR知道,它标记的静态字段的存取是依赖当前线程,而独立于其他线程的。既然存储在线程静态字段中的数据只对存储该数据的同一线程中所运行的代码可见,那么,可使用此类字段将其他数据从一个方法传递到该第一个方法所调用的其他方法,而且完全不用担心其他线程会破坏它的工作。TransactionScope 会将当前的 Transaction 存储到线程静态字段中。当稍后实例化 SqlCommand 时(在此 TransactionScope 从线程局部存储中删除之前),该 SqlCommand 会检查线程静态字段以查找现有 Transaction,如果存在则列入该 Transaction 中。通过这种方式,TransactionScope 和 SqlCommand 能够协同工作,从而开发人员不必将 Transaction 显示传递给 SqlCommand 对象。实际上,TransactionScope 和 SqlCommand 所使用的机制非常复杂。具体可以参考文章http://www.microsoft.com/china/MSDN/library/netFramework/netframework/NETMattersSep.mspx?mfr=true

Wrox出版的《Professional C# 4 and .NET 4》也有关于TransactionScope的一些使用方法的介绍。
————————————————
版权声明:本文为CSDN博主「weixin_34348805」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_34348805/article/details/85099004