参数化查询(Parameterized Query 或 Parameterized Statement)是指在设计与数据库链接并访问数据时,在需要填入数值或数据的地方,使用参数 (Parameter) 来给值,这个方法目前已被视为最有效可预防SQL注入攻击 (SQL Injection) 的攻击手法的防御方式。

数据库参数化规律:在参数化SQL中参数名的格式跟其在存储过程中生命存储过程参数一致,例如在Oracle中存储过程参数一律以”:”开头,在MS SQL Server中存储过程参数一律以”@”开头,而在MySQL中存储过程(MySQL从5.0以后版本支持存储过程)参数一律以“?”开头,所以在参数化SQL语句中参数名有些不一样(记得在csdn上有朋友提到过不知道为什么MySQL中参数化SQL语句中要用“?”而不是和SQL Server一样使用”@”),如果那位朋友看过本文,我想他就会解开这个疑虑了。

在使用参数化查询的情况下,数据库服务器不会将参数的内容视为SQL指令的一部份来处理,而是在数据库完成 SQL 指令的编译后,才套用参数运行,因此就算参数中含有恶意的指令,由于已经编译完成,就不会被数据库所运行。 有部份的开发人员可能会认为使用参数化查询,会让程序更不好维护,或者在实现部份功能上会非常不便,然而,使用参数化查询造成的额外开发成本,通常都远低于因为SQL注入攻击漏洞被发现而遭受攻击,所造成的重大损失。

在撰写 SQL 指令时,利用参数来代表需要填入的数值,例如:

MicrosoftSQLServer

Microsoft SQL Server 的参数格式是以 "@" 字符加上参数名称而成,SQL Server 亦支持匿名参数 "?"。

SELECT * FROM myTable WHERE myID = @myID

INSERT INTO myTable (c1, c2, c3, c4) VALUES (@c1, @c2, @c3, @c4)

在客户端代码中撰写使用参数的代码,例如:

ADO.NET

SqlCommand sqlcmd = new SqlCommand("INSERT INTO myTable (c1, c2, c3, c4) VALUES (@c1, @c2, @c3, @c4)", sqlconn);

sqlcmd.Parameters.AddWithValue("@c1", 1); // 设定参数 @c1 的值。

sqlcmd.Parameters.AddWithValue("@c2", 2); // 设定参数 @c2 的值。

sqlcmd.Parameters.AddWithValue("@c3", 3); // 设定参数 @c3 的值。

sqlcmd.Parameters.AddWithValue("@c4", 4); // 设定参数 @c4 的值。

sqlconn.Open();

sqlcmd.ExecuteNonQuery();

sqlconn.Close();

说来惭愧,工作差不多4年了,直到前些日子被DBA找上门让我优化一个CPU占用很高的复杂SQL语句时,我才突然意识到了参数化查询的重要性。

相信有很多开发者和我一样对于参数化查询认识比较模糊,没有引起足够的重视

错误认识1.不需要防止sql注入的地方无需参数化
  参数化查询就是为了防止SQL注入用的,其它还有什么用途不知道、也不关心,原则上是能不用参数就不用参数,为啥?多麻烦,我只是做公司内部系统不用担心SQL注入风险,使用参数化查询不是给自己找麻烦,简简单单拼SQL,万事OK

错误认识2.参数化查询时是否指定参数类型、参数长度没什么区别
  以前也一直都觉的加与不加参数长度应该没有什么区别,仅是写法上的不同而已,而且觉得加参数类型和长度写法太麻烦,最近才明白其实两者不一样的,为了提高sql执行速度,请为SqlParameter参数加上SqlDbType和size属性,在参数化查询代码编写过程中很多开发者忽略了指定查询参数的类型,这将导致托管代码在执行过程中不能自动识别参数类型,进而对该字段内容进行全表扫描以确定参数类型并进行转换,消耗了不必要的查询性能所致。根据MSDN解释:如果未在size参数中显式设置Size,则从dbType参数的值推断出该大小。如果你认为上面的推断出该大小是指从SqlDbType类型推断,那你就错了,它实际上是从你传过来的参数的值来推断的,比如传递过来的值是"username",则size值为8,"username1",则size值为9。那么,不同的size值会引发什么样的结果呢?且经测试发现,size的值不同时,会导致数据库的执行计划不会重用,这样就会每次执行sql的时候重新生成新的执行计划,而浪费数据库执行时间。

下面来看具体测试

首先清空查询计划

DBCC FREEPROCCACHE

传值username,不指定参数长度,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserName=@UserName";
//传值 username,不指定参数长度
//查询计划为(@UserName varchar(8))select * from Users where UserName=@UserName
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar) { Value = "username" });
comm.ExecuteNonQuery();
}

传值username1,不指定参数长度,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserName=@UserName";
//传值 username1,不指定参数长度
//查询计划为(@UserName varchar(9))select * from Users where UserName=@UserName
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar) { Value = "username1" });
comm.ExecuteNonQuery();
}

传值username,指定参数长度为50,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserName=@UserName";
//传值 username,指定参数长度为50
//查询计划为(@UserName varchar(50))select * from Users where UserName=@UserName
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,50) { Value = "username" });
comm.ExecuteNonQuery();
}

传值username1,指定参数长度为50,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserName=@UserName";
//传值 username1,指定参数长度为50
//查询计划为(@UserName varchar(50))select * from Users where UserName=@UserName
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,50) { Value = "username1" });
comm.ExecuteNonQuery();
}

使用下面语句查看执行的查询计划

SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects
WHERE sql LIKE '%Users%' and sql not like '%syscacheobjects%'

结果如下图所示

可以看到指定了参数长度的查询可以复用查询计划,而不指定参数长度的查询会根据具体传值而改变查询计划,从而造成性能的损失。

这里的指定参数长度仅指可变长数据类型,主要指varchar,nvarchar,char,nchar等,对于int,bigint,decimal,datetime等定长的值类型来说,无需指定(即便指定了也没有用),详见下面测试,UserID为int类型,无论长度指定为2、20、-1查询计划都完全一样为(@UserIDint)select*from Users whereUserID=@UserID

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserID=@UserID";
//传值 2,参数长度2
//执行计划(@UserID int)select * from Users where UserID=@UserID
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, 2) { Value = 2 });
comm.ExecuteNonQuery();
}
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserID=@UserID";
//传值 2,参数长度20
//执行计划(@UserID int)select * from Users where UserID=@UserID
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, 20) { Value = 2 });
comm.ExecuteNonQuery();
}
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserID=@UserID";
//传值 2,参数长度-1
//执行计划(@UserID int)select * from Users where UserID=@UserID
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, -1) { Value = 2 });
comm.ExecuteNonQuery();
}

这里提一下,若要传值varchar(max)或nvarchar(max)类型怎么传,其实只要设定长度为-1即可

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users where UserName=@UserName";
//类型为varchar(max)时,指定参数长度为-1
//查询计划为 (@UserName varchar(max) )select * from Users where UserName=@UserName
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,-1) { Value = "username1" });
comm.ExecuteNonQuery();
}

当然了若是不使用参数化查询,直接拼接SQL,那样就更没有查询计划复用一说了,除非你每次拼的SQL都完全一样

总结,参数化查询意义及注意点

1.可以防止SQL注入

2.可以提高查询性能(主要是可以复用查询计划),这点在数据量较大时尤为重要

3.参数化查询参数类型为可变长度时(varchar,nvarchar,char等)请指定参数类型及长度,若为值类型(int,bigint,decimal,datetime等)则仅指定参数类型即可

4.传值为varchar(max)或者nvarchar(max)时,参数长度指定为-1即可

5.看到有些童鞋对于存储过程是否要指定参数长度有些疑惑,这里补充下,若调用的是存储过程时,参数无需指定长度,如果指定了也会忽略,以存储过程中定义的长度为准,不会因为没有指定参数长度而导致重新编译,不过还是建议大家即便时调用存储过程时也加上长度,保持良好的变成习惯

一、以往的防御方式

 
以前对付这种漏洞的方式主要有三种:

  • 字符串检测:限定内容只能由英文、数字等常规字符,如果检查到用户输入有特殊字符,直接拒绝。但缺点是,系统 中不可避免地会有些内容包含特殊字符,这时候总不能拒绝入库。
  • 字符串替换:把危险字符替换成其他字符,缺点是危险字符可能有很多,一一枚举替换相当麻烦,也可能有漏网之 鱼。
  • 存储过程:把参数传到存储过程进行处理,但并不是所有数据库都支持存储过程。如果存储过程中执行的命令也是通 过拼接字符串出来的,还是会有漏洞。

二、什么是参数化查询?

 
    一个简单理解参数化查询的方式是把它看做只是一个T-SQL查询,它接受控制这个查询返回什么的参数。通过使用不同的参数,一个参数化查询返回不同的结果。要获得一个参数化查询,你需要以一种特定的方式来编写你的代码,或它需要满足一组特定的标准。
    有两种不同的方式来创建参数化查询。第一个方式是让查询优化器自动地参数化你的查询。另一个方式是通过以一个特定方式来编写你的T-SQL代码,并将它传递给sp_executesql系统存储过程,从而编程一个参数化查询。
 
        这样的解释还是有点模糊,先看一例:

例一:参数化查询

 
    参数化查询(Parameterized Query 或 Parameterized Statement)是访问数据库时,在需要填入数值或数据的地方,使用参数 (Parameter) 来给值。

在使用参数化查询的情况下,数据库服务器不会将参数的内容视为SQL指令的一部份来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有指令,也不会被数据库运行。Access、SQL Server、MySQL、SQLite等常用数据库都支持参数化查询。

  1. //在ASP.NET程序中使用参数化查询
  2. //ASP.NET环境下的查询化查询也是通过Connection对象和Command对象完成。如果数据库是SQL Server,就可以用有名字的参数了,格式是“@”字符加上参数名。
  3. SqlConnection conn = new SqlConnection("server=(local)\\SQL2005;user id=sa;pwd=12345;initial catalog=TestDb");
  4. conn.Open();
  5. SqlCommand cmd = new SqlCommand(“SELECT TOP 1 * FROM [User] WHERE UserName = @UserName AND Password = @Password“);
  6. cmd.Connection = conn;
  7. cmd.Parameters.AddWithValue(”UserName”, “user01″);
  8. cmd.Parameters.AddWithValue(”Password”, “123456″);
  9. SqlDataReader reader = cmd.ExecuteReader();
  10. reader.Read();
  11. int userId = reader.GetInt32(0);
  12. reader.Close();
  13. conn.Close();
 

参数化查询被喻为最有效防止SQL注入的方法,那么存储过程一定是参数化过后的吗?

如果存储过得利用传递进来的参数,再次进行动态SQL拼接,这样还算做是参数化过后的吗?如果存储过程一定是参数化过后的,那么是不是意味着,只要使用存储过程就具有参数化查询的全部优点了?
如下存储过程:

  1. create procedure pro_getCustomers
  2. (
  3. @whereSql nvarchar(max)
  4. )
  5. as
  6. declare @sql nvarchar(max)
  7. set @sql=N'select * from dbo.Customer ' + @whereSql
  8. exec(@sql)
  9. Go
  10. --如果我要在ADO.NET中参数化查询这个存储过程,以防止SQL注入,我该怎么办呢?比如:
  11. exec pro_getCustomers 'where Name=@name'

这种方法没有办法防止注入,你能做的就是对字符串进行过滤.

拼接SQL是:
  1. "select * from customer where 1=1"   + " and name=@name" + " and sex=@sex"

也就是判断参数化查询。只不过是动态地组装查询限制条件。

动态拼接SQL,而且是参数化查询的SQL语句是没有问题的。

ADO.NET中被SQL注入的问题,必须过于关键字。原作者的测试代码如下:
  1. USE [B2CShop]
  2. GO
  3. SET ANSI_NULLS ON
  4. GO
  5. SET QUOTED_IDENTIFIER ON
  6. GO
  7. ALTER procedure [dbo].[pro_getCustomers]
  8. (
  9. @whereSql nvarchar(max),
  10. @paramNameList nvarchar(max),
  11. @paramValueList nvarchar(max)
  12. )
  13. as
  14. declare @sql nvarchar(max)
  15. set @sql=N'select * from dbo.Customer ' + @whereSql
  16. exec sp_executesql @sql, @paramNameList , @paramValueList
  17. go
  1. /// <summary>
  2. /// 动态执行存储过程
  3. /// </summary>
  4. /// <param name="searchedName">要查询的姓名的关键字</param>
  5. /// <returns>实体集合</returns>
  6. public static List<Customer> ExecDynamicProc(string searchedName)
  7. {
  8. SqlParameter[] values = new SqlParameter[]
  9. {
  10. new SqlParameter("@whereSql", "where name like @name"),
  11. new SqlParameter("@paramNameList","@name nvarchar(50)"),
  12. new SqlParameter("@paramValueList","@name='%"+ searchedName +"%'")
  13. };
  14. return DBHelper.ExecuteProc("proc_GetCustomerPagerBySearch",values);
  15. }
  1. /// <summary>
  2. /// 从搜索类里面拼接参数化的SQL字符串
  3. /// </summary>
  4. /// <param name="search">搜索类</param>
  5. /// <param name="sqlParams">搜索的参数,不能传入Null</param>
  6. /// <returns>安全的SQL语句</returns>
  7. private static string GetSafeSqlBySearchItem(CustomerSearch search, ref List<SqlParameter> sqlParams)
  8. {
  9. StringBuilder safeSqlAppend = new StringBuilder();
  10. if (search != null)
  11. {
  12. if (!string.IsNullOrEmpty(search.NameEquals))
  13. {
  14. safeSqlAppend.Append(" and Name=@nameEquals");
  15. sqlParams.Add(new SqlParameter("@nameEquals", search.NameEquals));
  16. }
  17. if (!string.IsNullOrEmpty(search.NameContains))
  18. {
  19. safeSqlAppend.Append(" and Name like @nameContains");
  20. sqlParams.Add(new SqlParameter("@nameContains", "%" + search.NameContains + "%"));
  21. }
  22. }
  23. return safeSqlAppend.ToString();
  24. }
  1. /// <summary>
  2. /// 得到分页用的SQL语句
  3. /// </summary>
  4. /// <param name="columnNameItems">要查询的列名,多个列名用逗号分隔。传入Empty或Null时,则默认查询出所有的列</param>
  5. /// <param name="tableName">表名,不能为Null和Empty,默认的SQL别名为a</param>
  6. /// <param name="joinOtherTable">连接其他的表,可以传入Null或Empty。调用的时候,可以类似如:inner join departInfo as b on a.departInfoId=b.Id</param>
  7. /// <param name="whereSql">搜索条件,即在“where 1=1 ”后面写条件,可以传入Null或Empty。调用的时候,可以类似如:and b.Price=@beginPrice </param>
  8. /// <param name="orderColumnNameAndAscOrDesc">排序的列名以及Asc或Desc,即在“order by”后面写排序项,不能为Null和Empty。比如“Id asc, name desc”</param>
  9. /// <param name="pageNumber">当前页的页码,最小值应该为1</param>
  10. /// <param name="pageSize">每页显示的记录数,最小值应该为1</param>
  11. /// <returns>SQL语句</returns>
  12. internal static string GetPagerTSql(string columnNameItems, string tableName, string joinOtherTable, string whereSql, string orderColumnNameAndAscOrDesc, int pageNumber, int pageSize)
  13. {
  14. if (string.IsNullOrEmpty(tableName))
  15. {
  16. throw new ArgumentNullException("tableName", String.Format(CultureInfo.CurrentCulture, DALResource.Common_NullOrEmpty));
  17. }
  18. if (string.IsNullOrEmpty(orderColumnNameAndAscOrDesc))
  19. {
  20. throw new ArgumentNullException("orderColumnNameAndAscOrDesc", String.Format(CultureInfo.CurrentCulture, DALResource.Common_NullOrEmpty));
  21. }
  22. if (string.IsNullOrEmpty(columnNameItems))
  23. {
  24. columnNameItems = "a.*";
  25. }
  26. if (pageNumber < 1)
  27. {
  28. pageNumber = 1;
  29. }
  30. if (pageSize < 1)
  31. {
  32. pageSize = 1;
  33. }
  34. int beginNumber = (pageNumber - 1) * pageSize + 1;
  35. int endNumber = pageNumber * pageSize;
  36. string sqlPager = string.Format("select * from (select row_number() over(order by {1}) as __MyNewId, {0} from {2} as a {3} where 1=1 {4}) as __MyTempTable where __MyNewId between {5} and {6} order by __MyNewId asc;", columnNameItems, orderColumnNameAndAscOrDesc, tableName, joinOtherTable, whereSql, beginNumber, endNumber);
  37. string sqlPagerCount = string.Format("select @__returnCount=COUNT(*) from {0} as a {1} where 1=1 {2};",tableName, joinOtherTable, whereSql);
  38. return sqlPager + sqlPagerCount;
  39. }

例二:登录错误次数限制及参数化传递防止SQL注入

 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using System.Configuration;
  10. using System.Data.SqlClient;
  11. namespace 复习登录
  12. {
  13. public partial class login : Form
  14. {
  15. public login()
  16. {
  17. InitializeComponent();
  18. }
  19. string str = ConfigurationManager.ConnectionStrings["sqlserver2008"].ConnectionString;
  20. DateTime dt1;
  21. private void btn_login_Click(object sender, EventArgs e)
  22. {
  23. using(SqlConnection cnn=new SqlConnection(str))
  24. {
  25. using (SqlCommand cmd=cnn.CreateCommand())
  26. {
  27. cmd.CommandText = "select * from T_User where username=@username";
  28. cmd.Parameters.AddWithValue("@username", txt_username.Text);
  29. cnn.Open();
  30. using (SqlDataReader reader = cmd.ExecuteReader())
  31. {
  32. if (reader.Read())
  33. {
  34. int Error = Convert.ToInt32(reader["Error"].ToString());
  35. if (Error >= 3)
  36. {
  37. string sqltime = reader["Errortime"].ToString();
  38. dt1 = DateTime.Parse(sqltime);
  39. DateTime dt2 = DateTime.Now;
  40. TimeSpan ts = dt2 - dt1;
  41. if (ts.TotalMinutes < 5)
  42. {
  43. MessageBox.Show("对不起,你已经输入3次连续错误密码,系统已经将账户冻结,请在五分钟后再试");
  44. return;
  45. }
  46. else
  47. {
  48. clearerror();
  49. }
  50. }
  51. string sqlpassword = reader["Password"].ToString();
  52. if (sqlpassword == txt_password.Text)
  53. {
  54. clearerror();
  55. if (txt_username.Text.ToUpper() == "ADMIN")
  56. {
  57. this.Hide();
  58. main m = new main();
  59. m.Show();
  60. }
  61. else
  62. {
  63. MessageBox.Show("登录成功");
  64. }
  65. }
  66. else
  67. {
  68. MessageBox.Show("密码错误");
  69. adderror();
  70. }
  71. }
  72. else
  73. {
  74. MessageBox.Show("用户名不存在");
  75. }
  76. }
  77. }
  78. }
  79. }
  80. private void adderror()
  81. {
  82. dt1 = DateTime.Now;
  83. using (SqlConnection cnn=new SqlConnection(str))
  84. {
  85. using (SqlCommand cmd=cnn.CreateCommand())
  86. {
  87. cnn.Open();
  88. cmd.CommandText = "update T_User set Error=Error+1,Errortime=@Errortime where username=@username";
  89. cmd.Parameters.AddWithValue("@Errortime", dt1);
  90. cmd.Parameters.AddWithValue("@username", txt_username.Text);
  91. cmd.ExecuteNonQuery();
  92. }
  93. }
  94. }
  95. private void clearerror()
  96. {
  97. using (SqlConnection cnn=new SqlConnection(str))
  98. {
  99. using (SqlCommand cmd=cnn.CreateCommand())
  100. {
  101. cnn.Open();
  102. cmd.CommandText = "update T_User set Error=0 where username=@username";
  103. cmd.Parameters.Add(new SqlParameter("username", txt_username.Text));
  104. cmd.ExecuteNonQuery();
  105. }
  106. }
  107. }
  108. }
  109. }

SQL参数化查询的更多相关文章

  1. SQL参数化查询自动生成SqlParameter列表

    string sql = @"INSERT INTO stu VALUES (@id,@name) "; 参数化查询是经常用到的,它可以有效防止SQL注入.但是需要手动去匹配参数@ ...

  2. SQL 参数化查询 应用于 Like

    在sql 进行参数化查询的时候,使用like 语句和参数的时候,错误的写法:  Participant like '%@Participant%' ,这样在数据库为解析为 '%'participant ...

  3. SQL参数化查询--最有效可预防SQL注入攻击的防御方式

    参数化查询(Parameterized Query 或 Parameterized Statement)是访问数据库时,在需要填入数值或数据的地方,使用参数 (Parameter) 来给值. 在使用参 ...

  4. SQL参数化查询的问题

    最近碰到个问题, SQL语句中的 "... like '%@strKeyword%'"这样写查不出结果, 非的写成 "... like '%" + strKey ...

  5. sql 参数化查询问题

    一.正确案例 string name=“梅”; string sql="select * from test where  Name  like @Name"; //包含 梅Sql ...

  6. sql 参数化查询

      在初次接触sql时,笔者使用的是通过字符串拼接的方法来进行sql查询,但这种方法有很多弊端 其中最为明显的便是导致了sql注入. 通过特殊字符的书写,可以使得原本正常的语句在sql数据库里可编译, ...

  7. mybatis的sql参数化查询

    我们使用jdbc操作数据库的时候,都习惯性地使用参数化的sql与数据库交互.因为参数化的sql有两大有点,其一,防止sql注入:其二,提高sql的执行性能(同一个connection共用一个的sql编 ...

  8. sql参数化查询避免注入漏洞的原因探析

    网上其他同学的都说是重用执行计划,将用户输入的作为文本查询,到底如何实现,我用下面三行代码来解析一下. DECLARE @test NVARCHAR() SET @test=' or 1='1 SEL ...

  9. sql参数化查询in的参数

    private Query setParameter(Query query, Map<String, Object> map) { if (map != null) { Set<S ...

随机推荐

  1. 关于 IIS 上运行 ASP.NET Core 站点的“HTTP 错误 500.19”错误

    昨天回答了博问中的一个问题 —— “HTTP 错误 500.19 - Internal Server Error dotnetcore”,今天在这篇随笔中时候事后诸葛亮地小结一下. 服务器是 Wind ...

  2. Tab切换顺序设置

    使用TabIndex设置顺序 <StackPanel Orientation="Vertical"> <Button Content="Button1& ...

  3. 如何清除XP的网络共享密码

    一.利用NET命令 我们知道在Windows XP中提供了“net user”命令,该命令可以添加.修改用户账户信息,其语法格式为: net user [UserName [Password | *] ...

  4. MyBatis 问题 & 解决

    # 问题 Invalid bound statement (not found) # 解决 <mappers> 标签的包括的是 SQL 语句存在的地方,此外 <mapper> ...

  5. apache本地服务器的配置流程

    安装Apache 一.目的: 1. 能够有一个测试的服务器,不是所有的特殊网络服务都能找到免费的! 二.为什么是 "Apache" 1. 使用最广的 Web 服务器 2. Mac自 ...

  6. SQLServer 可更新订阅数据冲突的一个原因

    原文:SQLServer 可更新订阅数据冲突的一个原因 可更新订阅为什么有冲突? 可更新订阅中,当升级增加一个字段时,通常在发布服务器的发布数据库中增加,对表增加字段后,发布自动同步到订阅数据库中(复 ...

  7. sklearn文本特征提取——TfidfVectorizer

    什么是TF-IDF IF-IDF(term frequency-inverse document frequency)词频-逆向文件频率.在处理文本时,如何将文字转化为模型可以处理的向量呢?IF-ID ...

  8. Mono 4.0 发布,开源跨平台 .Net 框架

    快速使用Romanysoft LAB的技术实现 HTML 开发Mac OS App,并销售到苹果应用商店中.   <HTML开发Mac OS App 视频教程> 土豆网同步更新:http: ...

  9. libevent for qt的讨论

    一直对Qt官方的QtNetwork模块抱有遗憾,Qt自带的网络模块用的是select模型,无法支持高并发的服务器开发.最近在网上看到有个libevent for qt的东西,它直接替换了Qt的sele ...

  10. Django预备知识

    http协议 url: 协议://域名(IP)+端口(80)/路径?参数(a=1&b=2) 示例:https://www.baidu.com/s/?wd=aaa MVC M:mdoel 与数据 ...