制作类似下图中的拖拽排序功能:
1. 首先数据库该表中添加字段 sort,类型为 double(MySQL 中为 double(0, 0))。
2. 页面输出绑定数据(以 ASP.NET MVC 控制器为例):
public ActionResult EditSort()
{
if (!zConsole.Logined) { return RedirectToAction("", "SignIn", new { redirect = Request.Url.OriginalString }); }
db_auto2018Entities db = new db_auto2018Entities();
return View(db.dt_dealer.Where(c => c.enabled).OrderBy(c => c.sort));
}
这里可以加条件列出,即示例中 enabled == true 的数据。
3. 前台页面引用 jQuery 和 jQuery UI。
4. 使用 <ul /> 列出数据:
<ul id="sortable" class="list-group gutter list-group-lg list-group-sp">
@foreach (var d in Model)
{
<li class="list-group-item" draggable="true" data-id="@d.id">
<span class="pull-left"><i class="fa fa-sort text-muted fa m-r-sm"></i> </span>
<div class="clear">
【id=@d.id】@d.name_full
</div>
</li>
}
</ul>
5. 初始化 sortable,当拖拽结束时保存次序:
<script>
var url_SaveSort = '@Url.Action("SaveSort")';
</script>
<script>
$("#sortable").sortable({
stop: function (event, ui) {
// console.log('index: ' + $(ui.item).index())
// console.log('id: ' + $(ui.item).data('id'))
// console.log('prev_id: ' + $(ui.item).prev().data('id'))
$.post(url_SaveSort, {
id: $(ui.item).data('id'),
prev_id: $(ui.item).prev().data('id')
}, function (json) {
if (json.result.success) {
// window.location.reload();
} else {
toastr["error"](json.result.info);
}
}, 'json');
}
});
$("#sortable").disableSelection();
</script>
这里回传到服务端的参数为:当前项的 id 值、拖拽后其前面一项的 prev_id 值(若移至首项则 prev_id 为 undefined)。
不使用 $(ui.item).index() 是因为,在有筛选条件的结果集中排序时,使用该索引值配合 LINQ 的 .Skip 会引起取值错误。
6. 控制器接收并保存至数据库:
[HttpPost]
public ActionResult SaveSort(int id, int? prev_id)
{
if (!zConsole.Logined)
{
return Json(new { result = new { success = false, msg = "请登录后重试!" } }, JsonRequestBehavior.AllowGet);
}
db_auto2018Entities db = new db_auto2018Entities();
dt_dealer d = db.dt_dealer.Find(id);
// 拖拽后其前项 sort 值(若无则 null)(此处不需要加 enabled 等筛选条件)
double? prev_sort = prev_id.HasValue
? db.dt_dealer.Where(c => c.id == prev_id).Select(c => c.sort).Single()
: null as double?;
// 拖拽后其后项 sort 值(若无前项则取首项作为后项)(必须强制转化为 double?,否则无后项时会返回 0,导致逻辑错误)
double? next_sort = prev_id.HasValue
? db.dt_dealer.Where(c => c.sort > prev_sort && c.id != id).OrderBy(c => c.sort).Select(c => (double?)c.sort).FirstOrDefault()
: db.dt_dealer.Where(c => c.id != id).OrderBy(c => c.sort).Select(c => (double?)c.sort).FirstOrDefault();
if (prev_sort.HasValue && next_sort.HasValue)
{
d.sort = (prev_sort.Value + next_sort.Value) / 2;
}
if (prev_sort == null && next_sort.HasValue)
{
d.sort = next_sort.Value - 1;
}
if (prev_sort.HasValue && next_sort == null)
{
d.sort = prev_sort.Value + 1;
}
db.SaveChanges();
return Json(new { item = new { id = d.id }, result = new { success = true } });
}
需要注意的是,当往数据库添加新项时,必须将 sort 值设置为已存在的最大 sort 值 +1 或最小 sort 值 -1。
var d = new dt_dealer
{
name_full = "新建项",
sort = (db.dt_dealer.Max(c => (double?)c.sort) ?? 0) + 1,
};
一: 执行sql语句,返回受影响的行数
在mysql里面,如果没有影响,那么返回行数为 -1 ,sqlserver 里面 还没有测试过
(var ctx = MyDbContext()) { ctx.Database.ExecuteSqlCommand(""); }
二 : Database.SqlQuery<T> EF5执行sql查询语句 Database.SqlQuery 带返回值
这个准确的说是 IEnumerable<T> SqlQuery<T>(string sql, params object[] parameters) ,注意返回值是 IEnumerable
这个是执行sql语句,返回你想要的类型的列表
dbMain.Database.SqlQuery<int>("select max(UserId) from tb_user_account").First();
或者假如你自己有个类别
PersonView { PersonID { ; ; } Name { ; ; } }
那么就可以直接返回这个 PersonView类
(var ctx = MyDbContext()) { var peopleViews = ctx.SqlQuery<PersonView>("").ToList(); }
直接返回你想要的数据. 例如这里是 List<PersonView> 列表
“/”应用程序中的服务器错误。
无法为具有固定名称“MySql.Data.MySqlClient”的 ADO.NET 提供程序加载在应用程序配置文件中注册的实体框架提供程序类型“MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.10.7.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d”。请确保使用限定程序集的名称且该程序集对运行的应用程序可用。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=260882。
打开 nuget,卸载并重新安装 MySql.Data 和 MySql.Data.Entity
如果没有效果,打开 web.config,检查是否多次定义了 MySql.Data.MySqlClient
“/”应用程序中的服务器错误。
未能加载文件或程序集“MySql.Data.Entity.EF6, Version=6.10.7.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
6.10.7 版本的 MySql.Data.Entity.EF6.dll 文件发布后会自动变成 6.10.6,文件大小一样,只要用 FTP 上传覆盖一下即可。
“/”应用程序中的服务器错误。
The host xxx does not support SSL connections.
说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: MySql.Data.MySqlClient.MySqlException: The host xxx does not support SSL connections.
在 Web.config 的 connectionStrings 节点找到相关数据库连接,在 connectionString 中增加属性值:SslMode=none
本文未完成,部分测试方法、条件或结果可能有误,请谨慎参考! :)
本文基于 MySQL 的 InnoDB BTREE 方法的索引进行测试。
以一张包含 2000 万条记录的表做实验:
CREATE TABLE `dt_read` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`time` datetime(0) NOT NULL,
`a_id` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
);
这张表是用于记录文章点击量的,
`id` 为主键,int(11) 自增;
`time` 为非空 datetime,表示文章打开时间,测试数据是从 2017-03-11 至 2018-04-28;
`a_id` 为非空 int(11),表示文章 ID,在此表中不唯一,测试数据是从 1 至 260218。
体验“全表扫描”
首先来体验一下什么是全表扫描,执行下面语句:
SELECT * FROM `dt_read` WHERE `time` < '2020-1-1' LIMIT 10
> 时间: 0.012s
SELECT * FROM `dt_read` WHERE `time` < '2000-1-1' LIMIT 10
> 时间: 7.317s
表中数据是按主键从小到大排列的,当查询条件为 `time` < '2020-1-1' 时,能很快地从表的前端找到 10 条满足条件的数据,所以不再继续判断后面的记录,立刻返回结果,耗时 0.012 秒;但当条件改为 `time` < '2000-1-1' 时,同样逐条判断,直到最后一条也没有找到,这种情况就是所谓的“全表扫描”,耗时 7 秒。
索引对 ORDER BY 的 ASC 和 DESC 的影响
我们给 `time` 建一个索引,同样执行刚才需要全表扫描的语句:
SELECT * FROM `dt_read` WHERE `time` < '2000-1-1' LIMIT 10
> 时间: 0.012s
创建 `time` 的索引后,相当于生成了一张按 `time` 字段排列的新表,这时 MySQL 就能够很快地定位并找到符合条件的记录,避免了全表扫描。
试试按 `time` 倒序排:
SELECT * FROM `dt_read` WHERE `time` < '2000-1-1' ORDER BY `time` DESC LIMIT 10
> 时间: 0.013s
结论:索引对 ORDER BY 的顺序(ASC)和倒序(DESC)都是有效的。
索引字段的次序对 WHERE 和 ORDER BY 的影响
删除所有索引,创建一个新的索引,字段依次为 `time`, `a_id`。
分别执行以下查询:
SELECT * FROM `dt_read` WHERE `time` < '2000-1-1' AND `a_id` < 0 LIMIT 10
> 时间: 0.013s
SELECT * FROM `dt_read` WHERE `a_id` < 0 AND `time` < '2000-1-1' LIMIT 10
> 时间: 0.013s
结论:MySQL 会自动优化 WHERE 条件的次序来匹配最合适的索引。
但在 ORDER BY 中却不是这么回事了:
SELECT * FROM `dt_read` ORDER BY `time`, `a_id` LIMIT 10
> 时间: 0.013s
SELECT * FROM `dt_read` ORDER BY `a_id`, `time` LIMIT 10
> 时间: 14.066s
原因也很好理解,对两个字段进行排序,先后次序肯定会影响结果集,因此只能以 SQL 语句指定的字段次序来 ORDER BY,这样,按索引的字段次序进行 ORDER BY 查询无疑是更快的。
索引中的字段必须依次使用
保持上例创建的索引不变,即 `time`, `a_id`。
SELECT * FROM `dt_read` WHERE `time` < '2000-1-1' AND `a_id` < 0 LIMIT 10
> 时间: 0.013s
SELECT * FROM `dt_read` WHERE `a_id` < 0 LIMIT 10
> 时间: 6.438s
上句合理利用了索引的字段,而下句跳过了 `time`,直接 WHERE 了 `a_id`,这是不受该索引支持的。
我们可以想象一下这张由索引生成的虚拟表,其实就是一张普通的平面二维表格,按索引指定的字段次序进行了排序,所以全表中仅仅是索引指定的第一个字段是按大小排列的,第二个字段是在第一个字段值相同的区域内按大小排列,后同。所以,跳过索引指定的第一个字段直接对第二个字段进行检索,是无法应用该索引的。这个结论也同样也体现在 ORDER BY 语句中:
SELECT * FROM `dt_read` ORDER BY `time`, `a_id` LIMIT 10
> 时间: 0.013s
SELECT * FROM `dt_read` ORDER BY `a_id` LIMIT 10
> 时间: 29.566s
WHERE 和 ORDER BY 混合
保持上例创建的索引不变,即 `time`, `a_id`。
先来执行这两句:
SELECT * FROM `dt_read` ORDER BY `a_id` LIMIT 10
> 时间: 12.29s
SELECT * FROM `dt_read` WHERE `time` < '2000-1-1' ORDER BY `a_id` LIMIT 10
> 时间: 0.013s
仅仅 WHERE 了一个 `time`,对 ORDER BY `a_id` 的效率却有质的提升,是因为 WHERE 中的 `time` 和 ORDER BY 中的 `a_id` 一起找到了索引吗?答案是否定的。
我们把时间改大,让它能马上找到符合条件的数据:
SELECT * FROM `dt_read` WHERE `time` < '2020-1-1' ORDER BY `a_id` LIMIT 10
> 时间: 22.34s
为什么这个语句就不走索引了呢?
其实,一个简单的 SELECT 查询语句,首先执行 WHERE,然后 ORDER BY,最后是 LIMIT。每一步都独自去找了索引,而非 WHERE 和 ORDER BY 混在一起去找索引。必须保证每一步是快的,最终才是快的。
当 `time` < '2000-1-1' 时,WHERE 用到了索引,所以很快,ORDER BY 却没有用到索引,但为什么也很快呢?因为 WHERE 的结果集非常小(示例中为 0 条)。
当 `time` < '2020-1-1' 时,WHERE 也用到了索引,但其结果集非常大(示例中为所有记录),再 ORDER BY `a_id` 就非常慢了,因为我们没有创建以 `a_id` 开头的索引。
现在把索引改成只有 `time` 一个字段。
SELECT * FROM `dt_read` WHERE `time` < '2020-1-1' ORDER BY `a_id` LIMIT 10
> 时间: 6.033s
因为索引里有 `
SELECT * FROM `dt_read` WHERE `time` < '2000-1-1' ORDER BY `a_id` LIMIT 10
> 时间: 0.013s
SELECT * FROM `dt_read` WHERE `a_id` < 0 ORDER BY `time` LIMIT 10
> 时间: 6.033s
第二句先 WHERE `a_id`,后 ORDER BY `time` 是不能匹配所建的索引的。
索引中的字段越多越好
分别在创建索引(`time`)和索引(`time`, `a_id`)的情况下执行下面语句:
本例使用 ORDER BY 而不是 WHERE 来测试是因为,在 WHERE 的多个条件下,如果符合前一条件的筛选结果集过小会导致判断第二条件时数据量不足,无法判断索引是否起作用。
SELECT * FROM `dt_read` ORDER BY `time` LIMIT 10
仅创建索引(`time`)的情况下:
> 时间: 0.013s
仅创建索引(`time`, `a_id`)的情况下:
> 时间: 0.013s
SELECT * FROM `dt_read` ORDER BY `time`, `a_id` LIMIT 10
仅创建索引(`time`)的情况下:
> 时间: 15.015s
仅创建索引(`time`, `a_id`)的情况下:
> 时间: 0.014s
可以看到,在索引字段依次使用的前提下,索引字段数不少于查询字段数才能避免全表扫描。
虽然索引中的字段越多越好,但必须依次使用,否则也是无效索引。
索引对 INSERT / UPDATE / DELETE 的效率影响
分别在创建索引(`time`)和索引(`time`, `a_id`)的情况下执行下面语句:
INSERT INTO `dt_read` (`time`, `a_id`) VALUES ('2018-4-28', 260218)
不建索引的情况下:
> 时间: 0.01s
仅创建索引(`time`)的情况下:
> 时间: 0.01s
同时创建索引(`time`)和索引(`time`, `a_id`)的情况下:
> 时间: 0.01s
UPDATE `dt_read` SET `time` = '2018-4-28' WHERE `id` = 20000000(注:存在该 id 值的记录)
不建索引的情况下:
> 时间: 0.01s
仅创建索引(`time`)的情况下:
> 时间: 0.01s
同时创建索引(`time`)和索引(`time`, `a_id`)的情况下:
> 时间: 0.01s
虽然在 INSERT / UPDATE / DELETE 时数据库会更新索引,但从实测数据来看,索引对其效率的影响可忽略不计。
一些误区
“in 语法效率很低”?
in 语法也是应用索引的,网传 in 会比一个一个 WHERE OR 要慢得多的说法是不靠谱的。in 主键和 in 索引同理。
另外:
对于字符串类型,LIKE '%abc%' 是不能应用索引的,但 LIKE 'abc%' 可以。更多关于字符串类型的索引,请查阅全文索引(FULLTEXT)。
索引的字段是可以指定长度的,类似字符串索引指定前面若干唯一字符就可以优化效率。
本文系个人实践总结,欢迎批评指正!
在 MySQL 中,int 的取值范围是 [-2147483648, 2147483647],占用 4 个字节。
int(M) 中 M 的默认值为 11,该值不影响取值范围和占用字节,仅表示最大显示宽度。
以某 int 字段存储的记录值为 2147483647 为例:
类型为 int(1) 时,SELECT 结果为 214
类型为 int(2) 时,SELECT 结果为 2147
……
类型为 int(7) 时,SELECT 结果为 214748364
类型为 int(8) ~ int(11) 时,SELECT 结果为 2147483647
测试结果跟网上的说法不同
如果添加了 zerofill 属性,当然是填充零的效果,仍以上述值为例:
类型为 int(1) 时,SELECT 结果为 214
类型为 int(2) 时,SELECT 结果为 2147
……
类型为 int(7) 时,SELECT 结果为 214748364
类型为 int(8) ~ int(10) 时,SELECT 结果为 2147483647
类型为 int(11) 时,SELECT 结果为 02147483647
结论,既然 M 值不影响取值范围和占用字节,那么何必去改它呢。除非有特殊业务需求,否则很容易引起逻辑混乱,特别是当你误认为它是用来限定取值范围或节省存储空间的时候。
重点提前看:
https://xoyozo.net/Blog/Details/MySQL-on-Windows
Connector/Net 6.10.4(暂)不支持在 Visual Studio 2015/2017 添加 ADO.NET 实体数据模型时选择 MySQL Data Provider 数据源。并且,在编译项目时会出现“违反了继承安全性规则”的异常。
只能降回 Connector/Net 6.9.10,NuGet 中将 MySql.Data 和 MySql.Data.Entity 用 6.9.10 和 6.10.4 版本皆可(除非遇到下文中提到的异常)。
重新生成或重启 VS 使之生效。
另外,MySql.Data 和 MySql.Data.Entity 6.9.10 可以配合 EnityFramework 和 EntityFramework.zh-Hans 6.1.x 和 6.2.0 使用。
MySql.Data 和 MySql.Data.Entity 6.9.9 在创建/更新实体模型向导中会出现闪退,更换为 6.9.10 试试。
或重新安装 Connector/NET 和 MySQL for Visual Studio 并重启电脑试试。
仍然闪退,参此文:https://xoyozo.net/Blog/Details/mysql-for-vs-flashback。
重要提醒,如果在添加 EF 模型时报错:
您的项目引用了最新版的实体框架;但是,找不到进行数据连接所需的与此版本兼容的实体框架数据库提供程序。请退出向导,安装兼容提供程序,重新生成您的项目,然后再执行此操作。
那么在菜单中选择:生成 - 清理解决方案,然后直接添加数据模型。如果在添加前生成了项目(bin 目录下有相关 .dll 文件),那么将出现上述错误。
类型“MySql.Data.MySqlClient.MySqlProviderServices”违反了继承安全性规则。派生类型必须与基类型的安全可访问性匹配或者比基类型的安全可访问性低。
将 MySql.Data 和 MySql.Data.Entity 从 6.10.4 退回 6.9.10 版本即可。
以上问题出现在:
VS 15.4.*
VS 15.4.4
VS 15.4.5
----------------
VS 15.5 已经可以在 EF 向导中连接 MySQL 数据库(Connector/Net 6.10.4),但 MySql.Data.Entity 6.10.4 编译仍然出现“违反了继承安全性规则”。另:MySQL 官网下架了 Connector/Net 6.10.4,退回提供 6.9.10 版本。
2017-12-9 发布的 Connector/Net 6.10.5 仍然存在无法在 EF 向导中连接 MySQL 数据源的问题。
服务器上安装 Connector/Net 6.10.4 没有任何问题。
在 Windows 上安装 MySQL Server,提示需要安装 Microsoft Visual C++ 2013 Redistributable,但 MySQL Installer 安装向导中提供的 C++ 是 12.0.30501 版本:
导致无法正常安装 MySQL Server,仍然提示:
This application requires Visual Studio 2013 Redistributable. Please install the Redistributable then run this installer again.
解决办法是下载安装 Update for Visual C++ 2013 and Visual C++ Redistributable Package,即 12.0.40649.5 版本
安装成功
更新 .edmx 文件时若提示:
生成模型时出现意外错误。有关详细信息,请参阅输出窗口。异常消息:“StrongTypingException: 表“TableDetails”中列“IsPrimaryKey”的值为 DBNull。”。
可以通过以下方法来解决:
1,控制面板中打开“服务”(或执行命令 services.msc),重新启动“MySQL57”
2,在 MySQL 中执行:
use <<database name>>;
set global optimizer_switch='derived_merge=OFF';
如果提示“1227 - Access denied; you need (at least one of) the SUPER privilege(s) for this operation”,请使用超级用户进行操作。
3,搞定,试试更新 .edmx 吧。