最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来。

十年河东十年河西,莫欺少年穷

学无止境,精益求精

   上篇博客我们学习了EF 之 MVC 排序,查询,分页 Sorting, Filtering, and Paging For MVC About EF,本节继续学习

标题中的:连接弹性(微软解释:瞬态错误自动重试连接)和命令拦截(捕捉所有 SQL 查询发送到数据库,以便登录或改变它们)

上网查了大量的资料,网友们基本都是直接翻译原文:Connection Resiliency and Command Interception with the Entity Framework in an ASP.NET MVC Application

在解释连接弹性之前,我们来看一段代码:

        /// <summary>
/// 释放数据库资源 断开连接
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}

上述代码意思是SQL操作执行后,及时断开数据库连接,释放数据库资源

SQL操作的过程:SQL操作-->执行时发生异常-->执行Dispose-->断开连接,释放资源。在本次操作中,程序和数据库连接了一次,因为发生异常,及时释放了数据库资源,这样的执行过程看似没问题,但是用户体验不太好。

如果SQL本身没有什么问题,由于断开了数据库连接,用户得不到数据结果,岂不是用户体验差吗?

我们再来看看微软的解读:连接弹性(微软解释:瞬态错误自动重试连接的次数)

微软的意思是,在执行一个SQL的过程中,如果第一次执行错误,还可以通过代码控制来实现重连,进行第二次数据库连接,同理,如果第二次数据连接依然发生异常,还会执行第三次数据库连接等等,而在数据库访问策略中,这样的重试连接默认是四次。

回到刚才的话题:如果SQL语句本身没有什么问题,SQL第一次执行失败,那么第二次就可能成功,这样就提高了用户体验。

在此:举一些例子,例如SQL执行过程中突然断网,访问的资源临时被占用等导致的执行失败都是可以尝试重连的。

OK,关于连接弹性的说明就到这儿,下面我们探讨下命令拦截,首先看微软的解释<捕捉所有 SQL 查询发送到数据库,以便登录或改变它们>

看完微软的解释,相信你和我一样也是丈二的和尚,摸不着头脑。而本人的理解是这样的,当然,我的理解也可能不对,希望大家在评论区指出,谢谢。

我的理解如下:

EF代码很少使用SQL语句,在我们写EF时,基本都用Linq To Sql代替了,而我们访问数据库的最基本单元就是SQL语句,那么你书写的linq To Sql 会转化成什么样的SQL语句呢?如果我们能看到这些SQL语句,我们就可以根据这些SQL语句做一些改变,从而提高程序的效率。

例如:下面的EF代码语句:

        private StudentContext db = new StudentContext();
/// <summary>
/// 简单分页演示
/// </summary>
/// <param name="page">页码</param>
/// <returns></returns>
public ActionResult Index2(int page = )//查询所有学生数据
{
return View(db.Students.OrderBy(item=>item.Id).ToPagedList(page,));
}

上述代码是个简单的分页程序,如果你看不懂,请参照我的上篇博客:EF 之 MVC 排序,查询,分页 Sorting, Filtering, and Paging For MVC About EF

那么上述代码在执行的过程中会生成什么样的SQL语句呢?

在程序运行的输出窗口中,我们可以看到如上输出,其输出的完整SQL如下:

SELECT TOP ()
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Sex] AS [Sex],
[Extent1].[StudentNum] AS [StudentNum]
FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Sex] AS [Sex], [Extent1].[StudentNum] AS [StudentNum], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
FROM [dbo].[Student] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] >
ORDER BY [Extent1].[Id] ASC

那么,我们怎样才能捕捉到这些SQL语句呢?

在MVC EF 默认的输出窗口中,这些SQL语句是不会输出的,我们需要增加一个‘捕捉器’来捕捉这些SQL语句。

综上所言,我们就基本了解了连接弹性和命令拦截的概念和基本意思。注:如有个人理解不对的地方,谨防误人子弟,希望大家在评论区指出,小弟拜谢

那么,我们需要写什么代码来达到连接弹性和命令拦截的功效呢?

如下<大家也可参考:Connection Resiliency and Command Interception with the Entity Framework in an ASP.NET MVC Application>

首先:如何启用弹性连接

在我们的EF项目中创建一个名称为:Configuration 的文件夹,在文件夹中首先添加一个数据库重连类:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.SqlServer;
using System.Linq;
using System.Web; namespace EF_Test.Configuration
{ public class StudentConfiguration : DbConfiguration { /// <summary> /// 需要引入命名空间:using System.Collections.Generic;和using System.Data.Entity.SqlServer; /// </summary> public StudentConfiguration() { //设置 SQL 数据库执行策略 默认重连四次 SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy()); } } }

上文中提到,如果不是SQL本身的异常,我们重新连接数据库,可能会得到我们想要的结果。例如查询数据时,突然断网,第一次查询失败,在数据库重连后,第二次查询成功,系统将查询结果反馈给客户,提高了客户体验。

但是,如果您写的SQL本身就是错误的,那无论重连几次数据都将是无用之功,这时,我们可以通过如下代码来捕获SQL执行异常:

在控制器代码中引用:using System.Data.Entity.Infrastructure;

            try
{
//有异常的SQL操作,SQL语句本身异常
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}

至此:数据库弹性连接的启用就完成了,下面我们继续命令拦截:

 如何启用命令拦截:

   首先在项目中创建文件夹:ILogger

1、创建日志接口和类:在日志记录文件夹中,创建一个名为ILogger.cs的类文件︰

    public interface ILogger
{
void Information(string message);
void Information(string fmt, params object[] vars);
void Information(Exception exception, string fmt, params object[] vars); void Warning(string message);
void Warning(string fmt, params object[] vars);
void Warning(Exception exception, string fmt, params object[] vars); void Error(string message);
void Error(string fmt, params object[] vars);
void Error(Exception exception, string fmt, params object[] vars); void TraceApi(string componentName, string method, TimeSpan timespan);
void TraceApi(string componentName, string method, TimeSpan timespan, string properties);
void TraceApi(string componentName, string method, TimeSpan timespan, string fmt, params object[] vars); }

2、在日志记录文件夹中,创建一个名为Logger.cs的类文件︰

    public class Logger : ILogger
{ public void Information(string message)
{
Trace.TraceInformation(message);
} public void Information(string fmt, params object[] vars)
{
Trace.TraceInformation(fmt, vars);
} public void Information(Exception exception, string fmt, params object[] vars)
{
Trace.TraceInformation(FormatExceptionMessage(exception, fmt, vars));
} public void Warning(string message)
{
Trace.TraceWarning(message);
} public void Warning(string fmt, params object[] vars)
{
Trace.TraceWarning(fmt, vars);
} public void Warning(Exception exception, string fmt, params object[] vars)
{
Trace.TraceWarning(FormatExceptionMessage(exception, fmt, vars));
} public void Error(string message)
{
Trace.TraceError(message);
} public void Error(string fmt, params object[] vars)
{
Trace.TraceError(fmt, vars);
} public void Error(Exception exception, string fmt, params object[] vars)
{
Trace.TraceError(FormatExceptionMessage(exception, fmt, vars));
} public void TraceApi(string componentName, string method, TimeSpan timespan)
{
TraceApi(componentName, method, timespan, "");
} public void TraceApi(string componentName, string method, TimeSpan timespan, string fmt, params object[] vars)
{
TraceApi(componentName, method, timespan, string.Format(fmt, vars));
}
public void TraceApi(string componentName, string method, TimeSpan timespan, string properties)
{
string message = String.Concat("Component:", componentName, ";Method:", method, ";Timespan:", timespan.ToString(), ";Properties:", properties);
Trace.TraceInformation(message);
} private static string FormatExceptionMessage(Exception exception, string fmt, object[] vars)
{
// Simple exception formatting: for a more comprehensive version see
// http://code.msdn.microsoft.com/windowsazure/Fix-It-app-for-Building-cdd80df4
var sb = new StringBuilder();
sb.Append(string.Format(fmt, vars));
sb.Append(" Exception: ");
sb.Append(exception.ToString());
return sb.ToString();
}
}

3、在日志文件夹中创建拦截器类

using System;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure.Interception;
using System.Data.Entity.SqlServer;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Reflection;
using System.Linq; namespace EF_Test.ILogger
{
public class StudentInterceptorLogging : DbCommandInterceptor
{
private ILogger _logger = new Logger();
private readonly Stopwatch _stopwatch = new Stopwatch(); public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
base.ScalarExecuting(command, interceptionContext);
_stopwatch.Restart();
} public override void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
_stopwatch.Stop();
if (interceptionContext.Exception != null)
{
_logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
}
else
{
_logger.TraceApi("SQL Database", "SchoolInterceptor.ScalarExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
}
base.ScalarExecuted(command, interceptionContext);
} public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
base.NonQueryExecuting(command, interceptionContext);
_stopwatch.Restart();
} public override void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
_stopwatch.Stop();
if (interceptionContext.Exception != null)
{
_logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
}
else
{
_logger.TraceApi("SQL Database", "SchoolInterceptor.NonQueryExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
}
base.NonQueryExecuted(command, interceptionContext);
} public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
base.ReaderExecuting(command, interceptionContext);
_stopwatch.Restart();
}
public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
_stopwatch.Stop();
if (interceptionContext.Exception != null)
{
_logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
}
else
{
_logger.TraceApi("SQL Database", "SchoolInterceptor.ReaderExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
}
base.ReaderExecuted(command, interceptionContext);
}
}
}

4、创建记录SQL错误的拦截器类

using System;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure.Interception;
using System.Data.Entity.SqlServer;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Reflection;
using System.Linq; namespace EF_Test.ILogger
{
public class StudentInterceptorTransientErrors : DbCommandInterceptor
{
private int _counter = ;
private ILogger _logger = new Logger(); public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
bool throwTransientErrors = false;
if (command.Parameters.Count > && command.Parameters[].Value.ToString() == "%Throw%")
{
throwTransientErrors = true;
command.Parameters[].Value = "%an%";
command.Parameters[].Value = "%an%";
} if (throwTransientErrors && _counter < )
{
_logger.Information("Returning transient error for command: {0}", command.CommandText);
_counter++;
interceptionContext.Exception = CreateDummySqlException();
}
} private SqlException CreateDummySqlException()
{
// The instance of SQL Server you attempted to connect to does not support encryption
var sqlErrorNumber = ; var sqlErrorCtor = typeof(SqlError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Where(c => c.GetParameters().Count() == ).Single();
var sqlError = sqlErrorCtor.Invoke(new object[] { sqlErrorNumber, (byte), (byte), "", "", "", }); var errorCollection = Activator.CreateInstance(typeof(SqlErrorCollection), true);
var addMethod = typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic);
addMethod.Invoke(errorCollection, new[] { sqlError }); var sqlExceptionCtor = typeof(SqlException).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Where(c => c.GetParameters().Count() == ).Single();
var sqlException = (SqlException)sqlExceptionCtor.Invoke(new object[] { "Dummy", errorCollection, null, Guid.NewGuid() }); return sqlException;
}
}
}

至此,整个拦截器就建立完毕。

如果正确的使拦截器发挥作用呢?我们还需在全局应用文件中添加如下代码:

代码如下:

        protected void Application_Start()
{
// Database.SetInitializer<StudentContext>(new DropCreateDatabaseIfModelChanges<StudentContext>()); AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//
DbInterception.Add(new StudentInterceptorTransientErrors());
DbInterception.Add(new StudentInterceptorLogging());
}

当然,我们如果不想写在全局应用文件中,我们可以在数据库重连策略类中添加,如下:

        public StudentConfiguration()
{
//设置 SQL 数据库执行策略 默认重连四次
SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
//注册拦截器 using System.Data.Entity.Infrastructure.Interception;
DbInterception.Add(new StudentInterceptorTransientErrors());
DbInterception.Add(new StudentInterceptorLogging());
}

下面是我的文件代码目录结构:

运行程序,测试下我们的拦截器及输出的SQL语句:

程序效果图为:

上述SQL语句其实就是一个简单的分页SQL语句。

我们输入学号进行查询,看看会输出什么样的SQL语句:

输出的SQL语句为:

我们把SQL语句放入数据库中执行,如下:

至此:本节内容也就讲完了,谢谢!

@陈卧龙的博客

连接弹性和命令拦截的 ASP.NET MVC 应用程序中的实体框架的更多相关文章

  1. 在ASP.NET MVC应用程序中实现Server.Transfer()类似的功能

    在ASP.NET MVC应用程序中,如果使用Server.Transfer()方法希望将请求转发到其它路径或者Http处理程序进行处理,都会引发“为xxx执行子请求时出错”的HttpException ...

  2. 在ASP.NET MVC应用程序中随机获取一个字符串

    在开发ASP.NET MVC应用程序时,有可能需要一个随机字符串,作为密码或是验证码等. 如果你需要的是SQL版本,可以参考<密码需要带特殊字符(二)>http://www.cnblogs ...

  3. ASP.NET MVC应用程序中支持用户使用腾讯QQ和微信以及新浪微博的第三方登录

    什么是第三方授权登录,就是一些大家都会有的帐号如QQ.微信.淘宝.微博等账户.通过那些巨头公司提供的api直接实现登录. 当然,我们是不可能得到你的用户名和密码的.不了解的人,可能会存在这个疑虑.我们 ...

  4. 为ASP.NET MVC应用程序使用高级功能

    为ASP.NET MVC应用程序使用高级功能 这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译, ...

  5. 使用区域组织 ASP.NET MVC 应用程序

    MVC 模式可将应用程序的模型(数据)逻辑与其呈现逻辑和业务逻辑分离. 在 ASP.NET MVC 中,这种逻辑分离还在项目结构中以物理方式实现,在该项目结构中,控制器和视图保存在使用命名约定定义关系 ...

  6. ASP。NET Core Blazor CRUD使用实体框架和Web API

    下载source code - 1.7 MB 介绍 *请查看我的Youtube视频链接来学习ASP.NET Core Blazor CRUD使用实体框架和Web API. 在本文中,我们将了解如何为A ...

  7. ASP.NET MVC应用程序展示RDLC报表

    原文:ASP.NET MVC应用程序展示RDLC报表 学习ASP.NET MVC这样久,在学习,练习与应用过程中,觉得很多知识与以前的ASP.NET多有区别,但是实现操作起来,细处又有许多相近的地方. ...

  8. ASP.NET MVC开发学习过程中遇到的细节问题以及注意事项

    1.datagrid中JS函数传值问题: columns: { field: 'TypeName', title: '分类名称', width: 120, sortable: true, format ...

  9. [渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:MVC程序中实体框架的连接恢复和命令拦截

    这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第四篇:MVC程序中实体框架的连接恢复和 ...

随机推荐

  1. [知识点]SPFA算法

    // 此博文为迁移而来,写于2015年4月9日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102vx93.html 1.前言 ...

  2. 【ORACLE】记录通过执行Oracle的执行计划查询SQL脚本中的效率问题

    记录通过执行Oracle的执行计划查询SQL脚本中的效率问题   问题现象: STARiBOSS5.8.1R2版本中,河北对帐JOB执行时,无法生成发票对帐文件.   首先,Quartz表达式培植的启 ...

  3. Lable得到自定义高度!

    方法1(系统):CGSize declabesize = [_questionDecLabel.text boundingRectWithSize:CGSizeMake(CGRectGetWidth( ...

  4. Java_一致性哈希算法与Java实现

    摘自:http://blog.csdn.net/wuhuan_wp/article/details/7010071 一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具 ...

  5. Flex条件判断中注意事项

    1:等于判断条件,一定注意写两个==等号, if (obj.ProcessType="Relation") 如果只写一个等号,编译不会报错,并且Flex会认为是赋值操作,并且该语句 ...

  6. String之“==”与equals

    有时候String类型用“==”判断相等时无法成功,经过实验,用string.equals方法可以判断成功!! for (int i = 0; i < 10000; i++) {   Strin ...

  7. iOS性能优化:Instruments使用实战

    iOS性能优化:Instruments使用实战   最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instrument ...

  8. Struts 2入门案例及登录

    一:入门案例 步骤如下: 1.导入jar包 2.配置web.xml文件 3.在src下创建名称为struts.xml的配置文件 4.创建编写HelloWorldAction 5.创建index.jsp ...

  9. Odoo中的Javascript单元测试

    前端页面利用QUnit进行单元测试,本文参考官方文档:https://www.odoo.com/documentation/8.0/reference/javascript.html 访问/web/t ...

  10. JS 比较日期相隔都少天&& 比较两个日期大小&&指定日期往前后推指定天数

    //这些天常接触到有关于js操作日期事 就小结了一下,希望对你有帮助 function conversionDate(a,b){ var start =a.split('-'); var end = ...