通过 nuget 安装 UEditorNetCore
从 UEditor 官网 下载最新的包 ueditorx_x_x_x-utf8-net.zip
解压包,并复制到项目的 wwwroot/lib 目录下,删除 net 目录。
根据 UEditorNetCore 官方的使用说明进行操作
步骤中,控制器 UEditorController 接替原 controller.ashx 的功能,用于统一处理上传逻辑
原 config.json 复制到项目根目录,用于配置上传相关设置(若更改文件名和路径在 services.AddUEditorService() 中处理)。
个人喜欢将 xxxPathFormat 值改为:upload/ueditor/{yyyy}{mm}{dd}/{time}{rand:6},方便日后迁移附件后进行批量替换。
记得配置 catcherLocalDomain 项。
上传相关的身份验证和授权控制请在 UEditorController 中处理,如:
public void Do()
{
if (用户未登录) { throw new System.Exception("请登录后再试"); }
_ue.DoAction(HttpContext);
}如果图片仅仅上传到网站目录下,那么这样配置就结束了,如果要上传到阿里云 OSS 等第三方图床,那么继续往下看。
因 UEditorNetCore 虽然实现了上传到 FTP 和 OSS 的功能,但未提供配置相关的账号信息的途径,所以只能(1)重写 action,或(2)下载 github 的源代码,将核心项目加入到您的项目中进行修改。
重写 action 不方便后期维护,这里记录修改源码的方式。
将源码改造为可配置账号的上传到 OSS 的功能,具体步骤如下:
将 Consts.cs 从项目中排除。
打开 Handlers/UploadHandler.cs,找到 UploadConfig 类,将
FtpUpload 改为 OssUpload,
FtpAccount 改为 OssAccessKeyId,
FtpPwd 改为 OssAccessKeySecret,
FtpIp 改为 OssAccessEndpoint,
再添加一个属性 OssBucketName。
打开 Handlers/UploadHandler.cs,找到 Process() 方法,
将 UploadConfig.FtpUpload 改为 UploadConfig.OssUpload,
将 Consts.AliyunOssServer.* 改为 UploadConfig.Oss*。
打开 Handlers/CrawlerHandler.cs,找到 Fetch() 方法,
将 Config.GetValue<bool>("catcherFtpUpload") 改为 Config.GetValue<bool>("OssUpload"),
将 Consts.AliyunOssServer.* 改为 Config.GetString("Oss*")。
打开 UEditorActionCollection.cs,找到 UploadImageAction,将
FtpUpload = Config.GetValue<bool>("imageFtpUpload"), FtpAccount = Consts.ImgFtpServer.account, FtpPwd = Consts.ImgFtpServer.pwd, FtpIp = Consts.ImgFtpServer.ip,替换为
OssUpload = Config.GetValue<bool>("OssUpload"), OssAccessKeyId = Config.GetString("OssAccessKeyId"), OssAccessKeySecret = Config.GetString("OssAccessKeySecret"), OssAccessEndpoint = Config.GetString("OssAccessEndpoint"), OssBucketName = Config.GetString("OssBucketName"),其余 3 个 Action(UploadScrawlAction、UploadVideoAction、UploadFileAction)按同样的方式修改。
在所有创建 UploadHandler 对象时补充添加 SaveAbsolutePath 属性。
打开 config.json,添加相关配置项(注:配置文件中的 *FtpUpload 全部废弃,统一由 OssUpload 决定)
"OssUpload": true, "OssAccessEndpoint": "", "OssAccessKeyId": "", "OssAccessKeySecret": "", "OssBucketName": "",将 xxxUrlPrefix 的值改为 OSS 对应的 CDN 网址(以 / 结尾,如://cdn.xoyozo.net/)。
其它注意点:
若使用 UEditorNetCore github 提供的源代码类库代替 nuget,且使用本地存储,那么需要将 Handlers 目录中与定义 savePath 相关的代码(查找 var savePath)替换成被注释的行
核心文件路径:/theme/html/demo*/src/js/components/core.datatable.js
所有参数的默认值见该文件 3369 行起。
data:
| 属性 | 功能 | 值 |
| type | 数据源类型 | local / remote |
| source | 数据源 | 链接或对象(见下方) |
| pageSize | 每页项数 | 默认 10 |
| saveState | 刷新、重新打开、返回时仍保持状态 | 默认 true |
| serverPaging | 是否在服务端实现分页 | 默认 false |
| serverFiltering | 是否在服务端实现筛选 | 默认 false |
| serverSorting | 是否在服务端实现排序 | 默认 false |
| autoColumns | 为远程数据源启用自动列功能 | 默认 false |
| attr |
data.source:
| 属性 | 功能 | 值 |
| url | 数据源地址 | |
| params | 请求参数 |
|
| headers |
自定义请求的头 |
|
| map | 数据地图,作用是对返回的数据进行整理和定位 |
|
layout:
| 属性 | 功能 | 值 |
| theme | 主题 | 默认 default |
| class | 包裹的 CSS 样式 | |
| scroll | 在需要时显示横向或纵向滚动条 | 默认 false |
| height | 表格高度 | 默认 null |
| minHeight | 表格最小高度 | 默认 null |
| footer | 是否显示表格底部 | 默认 false |
| header | 是否显示表头 | 默认 true |
| customScrollbar | 自定义的滚动条 | 默认 true |
| spinner |
Loading 样式 |
|
| icons | 表格中的 icon |
|
| sortable | 是否支持按列排序 | 默认 true |
|
resizable |
是否支持鼠标拖动改变列宽 | 默认 false |
| filterable | 在列中过滤 | 默认 false |
|
pagination |
显示分页信息 | 默认 true |
|
editable |
行内编辑 | 默认 false |
|
columns |
列 | 见本文下方 |
|
search |
搜索 |
|
layout.columns:
| 属性 | 功能 | 解释 |
| field | 字段名 | 对应 JSON 的属性名,点击表头时作为排序字段名 |
| title | 表头名 | 显示在表格头部 |
| sortable | 默认排序方式 | 可选:'asc' / 'desc' |
| width | 单元格最小宽度 | 值与 CSS 值一致,填数字时默认单位 px |
| type | 数据类型 | 'number' / 'date' 等,与本地排序有关 |
| format | 数据格式化 | 例格式化日期:'YYYY-MM-DD' |
| selector | 是否显示选择框 | 布尔值或对象,如:{ class: '' } |
| textAlign | 文字对齐方式 | 'center' |
| overflow | 内容超过单元格宽度时是否显示 | 'visible':永远显示 |
| autoHide | 自适应显示/隐藏 | 布尔值 |
| template | 用于显示内容的 HTML 模板 | function(row) { return row.Id; } |
| sortCallback | 排序回调 | 自定义排序方式,参 local-sort.js |
其它:
| 属性 | 功能 | 解释 |
| translate | 翻译 |
参 core.datatable.js 3512 行,简体中文示例:
|
| extensions |
暂时没有找到对字符串内容进行自动 HTML 编码的属性,这可能带来 XSS 攻击风险,在 remote 方式中必须在服务端预先 HtmlEncode。即使在 layout.columns.template 中进行处理也是无济于事,恶意代码会在 ajax 加载完成后立即执行。
方法和事件:待完善。
更多信息请查询官方文档:https://keenthemes.com/keen/?page=docs§ion=html-components-datatable
最近,Microsoft 将其针对Web API 的默认序列化从 Newtonsoft JsonConvert 更改为 System.Text.Json JsonSerializer。
using System.Text.Json;
string s = JsonSerializer.Serialize(object);
var obj = JsonSerializer.Deserialize<T>(string);System.Text.Json 命名空间:https://docs.microsoft.com/zh-cn/dotnet/api/system.text.json?view=net-5.0
如何从 Newtonsoft.Json 迁移到 System.Text.Json:https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0
不序列化属性
[System.Text.Json.Serialization.JsonIgnore]当值为 null 时不序列化
[System.Text.Json.Serialization.JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]格式化/美化
System.Text.Json.JsonSerializer.Serialize(Obj, new System.Text.Json.JsonSerializerOptions {
WriteIndented = true
})不编码中文(建议默认需要编码,可防止页面显示乱码,除非需要放在 <pre /> 标签中直接显示,可友好显示中文)
System.Text.Json.JsonSerializer.Serialize(Obj, new System.Text.Json.JsonSerializerOptions {
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})The cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
在 EF 查询数据库时发生:
var list = db.TableA.Select(a => new
{
a.Id,
bSum = a.TableB.Sum(b => b.Num),
}).ToList();本例中 TableB 有外键关联到 TableA.Id,TableB.Num 为 int,而非 int?。
原因是 EF 以为 Sum 的结果可能为 Nullable<int>,将代码修改如下即可正常:
var list = db.TableA.Select(a => new
{
a.Id,
bSum = (int?)a.TableB.Sum(b => b.Num) ?? 0,
}).ToList();在升级 kernel 后若无法启动系统,网站无法打开,SSH 无法连接,无法 ping 通。
使用 VNC 进入操作界面:

一种界面是可选择次新的内核版本:

应该能正常启动。
另一种界面提示:
Give root password for maintenance
输入密码后可启动。
阿里云工程师的建议:
1、当前默认是以最新内核启动的,由于新版内核文件存在异常无法正常运行,在手动选择低内核版本启动后,可以先更改下默认内核引导顺序,配置默认使用低版本内核运行,避免重启再次出现问题。 修改内核引导顺序 https://help.aliyun.com/knowledge_detail/41463.html 2、升级内核本身属于高危操作,建议操作前先做好快照备份,同时更新时可以参考下文档方案 避免Linux实例升级内核系统无法启动的方法 https://help.aliyun.com/knowledge_detail/59360.html

Unify Template 提供三种文件上传样式(如上图),“Plain File Input”和“File input”使用传统 <form /> 提交,“Advanced File input”调用组件实现。
ASP.NET Core 实现传统 <form /> 提交(以“Plain File Input”为例):
<form class="g-brd-around g-brd-gray-light-v4 g-pa-30 g-mb-30" id="form_avatar_upload" asp-action="AvatarUpload" enctype="multipart/form-data" method="post">
<!-- Plain File Input -->
<div class="form-group mb-0">
<p>Plain File Input</p>
<label class="u-file-attach-v2 g-color-gray-dark-v5 mb-0">
<input id="fileAttachment" name="Avatar" type="file" onchange="form_avatar_upload.submit()">
<i class="icon-cloud-upload g-font-size-16 g-pos-rel g-top-2 g-mr-5"></i>
<span class="js-value">Attach file</span>
</label>
</div>
<!-- End Plain File Input -->
</form>public class AvatarUploadModel
{
public IFormFile Avatar { get; set; }
}
[HttpPost]
public IActionResult AvatarUpload(AvatarUploadModel Item)
{
return RedirectToAction("");
}ASP.NET Core 调用 HSFileAttachment 组件实现:
<div class="form-group mb-20">
<label class="g-mb-10">上传作品文件(包)</label>
<input class="js-file-attachment_project" type="file" name="" id="file_project">
<div style="display: none;" id="file_project_tip">
<div class="u-file-attach-v3 g-mb-15" id="file_project_tip_content">
<h3 class="g-font-size-16 g-color-gray-dark-v2 mb-0">拖曳文件到此处或者 <span class="g-color-primary">浏览你的电脑</span></h3>
<p class="g-font-size-14 g-color-gray-light-v2 mb-0">
如果您的作品包含多个文件,请添加到压缩包再上传
</p>
<p class="g-font-size-14 g-color-gray-light-v2 mb-0">
支持的文件格式:
<code>zip</code>
<code>rar</code>
<code>7z</code>
<code>psd</code>
<code>ai</code>
<code>cdr</code>
<code>jpg</code>
<code>png</code>
<code>gif</code>
<code>tif</code>
<code>webp</code>
</p>
<p class="g-font-size-14 g-color-gray-light-v2 mb-0">文件(包)的大小不能超过 <span>@options.Value.MaxSizeOfSigleFile_MB</span> MB</p>
</div>
</div>
<small class="form-control-feedback">请上传作品文件</small>
</div>
<!-- JS Implementing Plugins -->
<script src="//cdn.***.com/unify/unify-v2.6.2/html/assets/vendor/jquery.filer/js/jquery.filer.min.js"></script>
<!-- JS Unify -->
<script src="//cdn.***.com/unify/unify-v2.6.2/html/assets/js/helpers/hs.focus-state.js"></script>
<script src="//cdn.***.com/unify/unify-v2.6.2/html/assets/js/components/hs.file-attachement.js"></script>
<!-- JS Plugins Init. -->
<script>
$(document).on('ready', function () {
// initialization of forms
$.HSCore.components.HSFileAttachment.init('.js-file-attachment_project', {
changeInput: $('#file_project_tip').html(),
uploadFile: {
url: url_UploadFile,
data: { type: 'project' },
success: function (data, element) {
console.log(data);
if (!data.success) {
var parent = element.find(".u-progress-bar-v1").parent();
element.find(".u-progress-bar-v1").fadeOut("slow", function () {
$("<div class=\"text-error g-px-10\"><i class=\"fa fa-minus-circle\"></i> Error</div>").hide().appendTo(parent).fadeIn("slow");
});
alert(data.err_msg);
} else {
var parent = element.find(".u-progress-bar-v1").parent();
element.find(".u-progress-bar-v1").fadeOut("slow", function () {
$("<div class=\"text-success g-px-10\"><i class=\"fa fa-check-circle\"></i> Success</div>").hide().appendTo(parent).fadeIn("slow");
});
$('#file_project_tip_content').slideUp();
upload_project_id = data.id;
}
}
}
});
$.HSCore.helpers.HSFocusState.init();
});
</script>public IActionResult UploadFile([FromForm(Name = "[]")]IFormFile file, string type)
{
return Ok(new { id = 1 });
}使用 JS 根据屏幕宽度来计算页面尺寸和字体大小并不是一个好办法。
rem 单位是跟随网页根元素的 font-size 改变而自动改变的,是一个相对的长度单位,非常适合在不同手机界面上自适应屏幕大小。
一般的手机浏览器的默认字体大小为 16px。即:
:root { font-size: 16px; } /* 在 HTML 网页中,:root 选择器相当于 html */也就是说,如果我定义一个宽度为 1rem,那么它的实际宽度为 16px。反过来说,如果设计稿宽度为 1000px,那么相当于 1000÷16=62.5rem。因此,我们设置一个 62.5rem 的宽度,即可正常显示 1000px 的设计效果。
为了适应不同屏幕尺寸的手机,只需按比例换算更改 :root 的 font-size 即可。这个比例是由“窗口宽度 ÷ 设计稿宽度”得到的,姑且称它为“窗设比”。
以 iPhone X 逻辑宽度 375px 为例,设计稿宽度 1000px 时,窗设比为:0.375。:root 的 font-size 将会被重置为 16 * 0.375 = 6px。上面的 62.5rem 宽度将对应 iPhone X 宽度为 62.5 * 6 = 375px,正好是屏幕逻辑宽度。
根据这个思路,理论上,我们只需要使用 CSS 将 :root 的 font-size 设置为 1px,然后使用 JS 重置为与“窗设比”相乘的积(本例中将被重置为 0.375px)。这样,我们可以不通过换算,只需要更改单位,将设计稿中的 500px 直接写入到 CSS 为 500rem,即可实现在 iPhone X 上显示逻辑宽度为(500 * 0.375 =)187.5px,即设计稿中的 1/2 宽度对应到屏幕上 1/2 的宽度。
实际上,部分安卓手机(如华为某旗舰机)不支持 :root 的 font-size 小于 12px,其它一些浏览器也会出现小于 12px 的文字以 12px 显示。
为了避免在任何环节出现小于 12px 的 font-size 值,且不增加设计稿尺寸到 CSS 数值的换算难度,我们设计了以下思路:
约定“设样比(设计稿的 px 值与 CSS 的 rem 值的比)”为 100;
使用 JS 将 :root 的 font-size 重置为“设样比”与“窗设比(窗口宽度 ÷ 设计稿宽度)”的乘积;
计算 CSS 时的 rem 值只需从设计稿中获取 px 值再除以“设样比”100 即可。
这样做的另一个好处是,不用要求设计师必须按多少尺寸来设计,当然按主流尺寸来建议是可行的。
完整代码(jQuery 版):
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Index</title>
<style>
body { margin: 0; text-align: center; }
#app { margin: 0 auto; background-color: #EEE; display: none; }
/* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
.whole { background-color: #D84C40; width: 10rem; }
.half { background-color: #3296FA; width: 5rem; }
.half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
</style>
</head>
<body>
<div id="app">
<div class="whole">100%</div>
<div class="half">50%</div>
<div class="half2">50%</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
// [函数] 重置尺寸
function fn_resize() {
// 设计稿宽度(px)
var designsWidth = 1000;
// 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
var px2rem = 100;
// 限制 #app 的最大宽度为设计稿宽度
$('#app').css('max-width', designsWidth).css('min-height', $(window).height());
// 重置 :root 的 font-size
var appWidth = Math.min($(window).width(), designsWidth);
$(':root').css('font-size', px2rem * appWidth / designsWidth);
$('#app').show();
}
// 页面加载完成后重置尺寸
$(fn_resize);
// 改变窗口大小时重置尺寸
$(window).resize(fn_resize);
</script>
</body>
</html>完整代码(Vue2 版):
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Index</title>
<style>
body { margin: 0; text-align: center; }
#app { margin: 0 auto; background-color: #EEE; }
/* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
.whole { background-color: #D84C40; width: 10rem; }
.half { background-color: #3296FA; width: 5rem; }
.half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
</style>
</head>
<body>
<div id="app" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px' }" style="display: none;" v-show="appShow">
<div class="whole">100%</div>
<div class="half">50%</div>
<div class="half2">50%</div>
<div>appWidth: {{ appWidth }}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
var app = new Vue({
el: '#app',
data: {
// 设计稿宽度(px)
designsWidth: 1000,
// 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
px2rem: 100,
// #app 的 width
appWidth: 0, // 勿改
appMinHeight: 0, // 勿改
appShow: false, // 勿改
},
mounted: function () {
// 页面加载完成后重置尺寸
this.fn_resize();
const that = this;
// 改变窗口大小时重置尺寸
window.onresize = () => {
return (() => {
console.log('RUN window.onresize()');
that.fn_resize();
})();
};
},
watch: {
// 侦听 appWidth 更改 root 的 font-size
appWidth: function () {
console.log('RUN watch: appWidth');
var root = document.getElementsByTagName("html")[0];
root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
this.appShow = true;
}
},
methods: {
fn_resize: function () {
console.log('RUN methods: fn_resize()');
this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
this.appMinHeight = document.documentElement.clientHeight;
}
}
});
</script>
</body>
</html>完整代码(Vue3 版):
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Index</title>
<style>
body { margin: 0; text-align: center; }
#app > div { display: none; margin: 0 auto; background-color: #EEE; }
/* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
.whole { background-color: #D84C40; width: 10rem; }
.half { background-color: #3296FA; width: 5rem; }
.half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
</style>
</head>
<body>
<div id="app">
<div style="display: none;" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px', display: appShow ? 'block' : 'none' }">
<div class="whole">100%</div>
<div class="half">50%</div>
<div class="half2">50%</div>
<div>appWidth: {{ appWidth }}</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
// 设计稿宽度(px)
designsWidth: 1000,
// 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
px2rem: 100,
// #app 的 width
appWidth: 0, // 勿改
appMinHeight: 0, // 勿改
appShow: false, // 勿改
}
},
mounted: function () {
// 页面加载完成后重置尺寸
this.fn_resize();
const that = this;
// 改变窗口大小时重置尺寸
window.onresize = () => {
return (() => {
console.log('RUN window.onresize()');
that.fn_resize();
})();
};
},
watch: {
// 侦听 appWidth 更改 root 的 font-size
appWidth: function () {
console.log('RUN watch: appWidth');
var root = document.getElementsByTagName("html")[0];
root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
this.appShow = true;
}
},
methods: {
fn_resize: function () {
console.log('RUN methods: fn_resize()');
this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
this.appMinHeight = document.documentElement.clientHeight;
}
}
});
const vm = app.mount('#app');
</script>
</body>
</html>示例中 CSS 初始 :root 的 font-size 为 16px(一个较小值,防止页面加载时瞬间出现大号文字影响用户体验),经过 fn_resize 后,:root 的 font-size 被设置为(100 * 375 / 1000 =)37.5px(iPhone X 中),那么宽度为 1000px 的设计稿中的 500px 换算到 CSS 中为 5rem,也即 37.5 * 5 = 187.5px,就是 iPhone X 的屏幕宽度的一半。
示例中 id 为 app 的 div 是用来在 PC 浏览器中限制页面内容最大宽度的(类似微信公众号发布的文章),如果网页不需要在 PC 端显示,jQuery 版代码中的 $('#app').width() 可以用 $(window).width() 来代替。
这个 div#app 一般设计有背景色,用于在宽度超过设计稿的设备上显示时区别于 body 的背景。但是当网页内容不超过一屏时,div#app 高度小于窗口,示例中与 appMinHeight 相关的代码就是为了解决这个问题。
示例中 div#app 隐藏/显示相关代码用于解决在页面加载初期由于 font-size 值变化引起的一闪而过的排版错乱。

最后补充一点,如果改变窗口大小时涉及执行耗时的操作,为避免页面卡顿,可以参考这篇文章添加函数防抖:https://xoyozo.net/Blog/Details/js-function-anti-shake
对于不同场景下 WebP 的使用,我们总结了一些解决方案,如下:
1、若使用场景是浏览器,可以:
JavaScript 能力检测,对支持 WebP 的用户输出 WebP 图片(https://developers.google.com/speed/webp/faq#how_can_i_detect_browser_support_for_webp)
使用 WebP 支持插件:WebPJS:http://webpjs.appspot.com,下载 webpjs-0.0.2.min.js
2、若使用场景是 App,可以:
Android 4.0 以下 WebP 解析库(https://github.com/alexey-pelykh/webp-android-backport)
iOS WebP 解析库(https://github.com/carsonmcdonald/WebP-iOS-example)
3、转换工具:
iSparta(http://isparta.github.io/)
阿里云 OSS 格式转换:https://help.aliyun.com/document_detail/44703.html
腾讯云数据万象格式转换:https://cloud.tencent.com/document/product/460/36543
七牛:https://developer.qiniu.com/dora/api/1279/basic-processing-images-imageview2
在数据库连接字符串可设置是否将 tinyint(1) 映射为 bool,否则为 sbyte:
TreatTinyAsBoolean=false/truetinyint(1) 一般用于表示 bool 型字段,存储内容为 0 或 1,但有时候也用来存储其它数字。
以 Discuz! 的表 pre_forum_post 为例,字段 first 和 invisible 都是 tinyint(1),但 first 只储存 0 和 1,invisible 却有 -1、-5 之类的值。
因此我们一般设置 TreatTinyAsBoolean=false,程序中 first 与 invisible 均以 sbyte 处理。
设置 TreatTinyAsBoolean=true 后,EF 或 EF Core 自动将该类型映射为 bool,方便在程序中作进一步处理。
一旦设置 TreatTinyAsBoolean=true,那么所有查询结果中 tinyint(1) 字段的返回值永远只有 1 和 0(即 True/False),即使真实值为 -1,也返回 1。
因为我们必须在确保所有的 tinyint(1) 类型字段都仅表示布尔值是才设置 TreatTinyAsBoolean=true。
一旦部分 tinyint(1) 类型字段用于存放 0 和 1 以外的数,那么就应该设置 TreatTinyAsBoolean=false。
在数据库优先的项目中,以 TreatTinyAsBoolean=false 生成数据模型后,可将明确为布尔类型的字段改为 bool。列出 MySQL 数据库中所有表所有字段中类型为 tinyint(1) 的字段值
以 .edmx 为例:
在项目中搜索该字段名,在搜索结果中找到 .edmx 文件中的两处。
.edmx 文件中的注释已经表明其包含 SSDL、CSDL、C-S mapping 三块内容,
在 SSDL content 下方找到该字段:
<Property Name="字段名" Type="tinyint" Nullable="***" />改为
<Property Name="字段名" Type="bool" Nullable="***" />在 CSDL content 下方找到该属性:
<Property Name="属性名" Type="SByte" Nullable="***" />改为
<Property Name="属性名" Type="Boolean" Nullable="***" />在解决方案管理器中展开 .edmx ->库名.tt -> 表名.cs 文件,
将模型类中的属性
public sbyte invisible { get; set; }改为
public bool invisible { get; set; }或 sbyte? 改为 bool?。
在 EF Core 中,直接打开对应数据表的 .cs 文件,更改属性类型即可。
相关报错:
错误: 指定的成员映射无效。类型中的成员的类型“Edm.SByte[Nullable=False,DefaultValue=]”与类型中的成员的“MySql.bool[Nullable=False,DefaultValue=]”不兼容。
InvalidOperationException: The property '***' is of type 'sbyte' which is not supported by the current database provider. Either change the property CLR type, or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
尝试先连接一次能解决此问题(概率),非常的莫名其妙:
using Data.Discuz.db_bbs2021Context dbd = new();
var conn = dbd.Database.GetDbConnection();
conn.Open();
conn.Close();参考:
https://mysqlconnector.net/connection-options/
https://stackoverflow.com/questions/6656511/treat-tiny-as-boolean-and-entity-framework-4
2023年1月注:本文适用于 Pomelo.EntityFrameworkCore.MySql 6.0,升级到 7.0 后会出现:
System.InvalidOperationException:“The 'sbyte' property could not be mapped to the database type 'tinyint(1)' because the database provider does not support mapping 'sbyte' properties to 'tinyint(1)' columns. Consider mapping to a different database type or converting the property value to a type supported by the database using a value converter. See https://aka.ms/efcore-docs-value-converters for more information. Alternately, exclude the property from the model using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.”
An unhandled exception occurred while processing the request.
InvalidOperationException: RenderBody has not been called for the page at '/Areas/Admin/Views/Shared/_Layout.cshtml'. To ignore call IgnoreBody().
Microsoft.AspNetCore.Mvc.Razor.RazorPage.EnsureRenderedBodyOrSections()
解决方法一:
在 return; 前执行 IgnoreBody();
解决方法二:
不要在 @RenderBody() 之前执行 return;