Session 有多种存储模式,默认是 InProc
,顾名思义是“在进程内”,即在 IIS 中,随网站重启而重启,网站发布后(或改动 .dll 文件)会导致应用重新运行,Session 清空。
Session 可以使用 StateServer
来保存,需要服务器开启 ASP.NET State Service 服务,此种方式要求保存在 Session 的信息必须序列化,然后从 Session 中获取的时候也要反序列化,这就导致性能有略微的损失。
Session 还可以配置为 SQLServer
,将 Session 存储在数据库中,同样需要序列化。
当配置为 Custom
时,可以在使用 Memcached、Redis 等第三方缓存技术来实现 Session 的管理,本文不作讨论。
Off
模式即关闭 Session,这种方式如果要实现用户登录功能,就必须依赖 Cookie 等来实现。
下面针对常见的三种模式进行比较:
InProc | StateServer | SQLServer | |
存储位置 | 应用进程内 | ASP.NET State Service 服务 | SQL Server 数据库 |
远程存储 | 不支持 | 支持 | 支持 |
存储格式 | 任意 | 序列化的 | 序列化的 |
可能导致重启(丢失)的情况 | 配置文件中 processModel 标签的 memoryLimit 属性; Global.asax 或者 Web.config 文件被更改; Bin 文件夹中的 Web 程序(DLL)被修改; 杀毒软件扫描了一些 .config 文件; 系统资源紧张进行资源回收导致 IIS 进程崩溃或重启等。 | ASP.NET State Service 服务停止; | - |
Session_End() 事件 | 有 | 无 | 无 |
部署难度 | 无 | 简单 | 稍复杂 |
Web.config 配置示例 (<system.web> 节点内) | <sessionState mode="InProc" /> | <sessionState mode="StateServer" /> | <sessionState mode="SQLServer" sqlConnectionString ="data source=x.x.x.x; user id=[user]; password=[pwd]" /> |
因此,如果你仅仅为了标题所述的“每次发布后丢失 Session”而烦恼,那么推荐使用 StateServer
模式来存储 Session。
注:
如果 Session 的 Key 是在应用启动时随机生成的话,使用 StateServer 和 SQLServer 还是会“丢失” Session。
这是对 Session 的配置,对 Cache 无效。

经常会看到这样似错非错的提示:
当前上下文中不存在名称"__o"
The name '__o' does not exist in the current context
实际上,我没有定义任何名为 __o 的变量。
发生这种情况的原因可能是使用了类似如下的代码:
<% if(true) { %>
<%= 1 %>
<% } %>
<%= 2 %>
为了在设计界面的 <%= %> 代码块中提供智能感知,ASP.NET(VB 或 C#)会自动生成一个名为“__o”的临时变量,这在页面编译器看到第一个 <%= %> 块时就完成了。但是在这里,<%= %> 块在 if 中出现,所以当关闭 if 后再使用 <%= %> 时,变量超出了定义的范围。
if (true)
{
object @__o;
@__o = 1;
}
@__o = 2;
解决方法:在页面的早期添加一个虚表达式。例如:<%= "" %>。这将不会呈现任何内容,并且它将确保在任何潜在的 if(或其他范围界定)语句之前,在 Render 方法中将 __o 声明为顶级。
当然还有一种治标不治本的方法就是隐藏这些错误提示(这并不影响程序正常运行):
点击错误列表面板左上角的过滤器按钮,CS0103,其中包含错误代码:当前上下文中不存在名称"__o",这些错误将不再显示,您仍然可以有其他 IntelliSense 错误和警告。
可能 VS2017 目前还没有完善这个功能,只能选择发布到文件夹再通过 FTP 工具上传到服务器,期待 VS2017 更新。
参考资料:
https://stackoverflow.com/questions/43162185/trouble-publishing-net-core-project-in-vs-2017?rq=1
2017-8-15 最新版的 Visual Studio 2017 (version 15.3) 已经解决了这个问题,前往下载更新

在 NuGet 中安装
Microsoft.AspNetCore.Session
和Newtonsoft.Json
打开 Startup.cs,在 ConfigureServices 方法中加入(视情况配置相关属性)
services.AddSession(options => { // 设置了一个较短的时间,方便测试 options.IdleTimeout = TimeSpan.FromSeconds(10); options.CookieHttpOnly = true; });
在 Configure 方法中加入
app.UseSession();
添加 Extensions 文件夹并添加类 SessionExtensions.cs
using Microsoft.AspNetCore.Http; using Newtonsoft.Json; public static class SessionExtensions { public static void Set<T>(this ISession session, string key, T value) { session.SetString(key, JsonConvert.SerializeObject(value)); } public static T Get<T>(this ISession session, string key) { var value = session.GetString(key); return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value); } }
使用方法
HttpContext.Session.Set("abc", 123); var v1 = HttpContext.Session.Get<int>("abc"); HttpContext.Session.Remove("abc"); var v2 = HttpContext.Session.Get<int>("abc"); // v1 = 123, v2 = 0

问题:
Invalid use of NULL value
原因:
将字段从可空改为不可空时,数据库中存在该字段为空的行,因此更新列属性不成功。
解决:
将数据库中已存在行中填充该字段值,再执行迁移。
问题:
Duplicate column name 'xxx'
原因:
数据库中已存在该列,但迁移希望执行 AddColumn,可能是手动修改了数据库结构导致的。
解决:
删除数据库中该列(如果数据不重要),或通过删除迁移的方法回到数据库结构与 EF 快照一致的情况。
问题:
Cannot add or update a child row: a foreign key constraint fails (`xxx`.`xxx`, CONSTRAINT `xxx` FOREIGN KEY (`xxx`) REFERENCES `xxx` (`xxx`) ON DELETE CASCADE)
原因:
设置外键时,发现外键属性值不在导航属性所在表中。
解决:
添加外键属性和添加导航属性分两次迁移。
第一次迁移:添加外键属性(字段),MySQL 可设外键属性非空,迁移成功后更改默认值 0 为真实值(即外键对应的主表中存在的值)。
第二次迁移:添加导航属性(外键)。

官方教程:Getting started with ASP.NET Core MVC and Entity Framework Core using Visual Studio
第一课:入门
Entity Framework Core NuGet 软件包
安装数据库提供程序(以 SQL Server 为例):其它数据库提供程序
Install-Package Microsoft.EntityFrameworkCore.SqlServer
该软件包依赖 Microsoft.EntityFrameworkCore 和 Microsoft.EntityFrameworkCore.Relational
新建数据模型
文件夹 Models 存放实体类
默认以 ID 或 类名ID 为主键列,并且是标识(即自动递增),以下代码可以取消标识,允许自定义 ID 值
[DatabaseGenerated(DatabaseGeneratedOption.None)]
导航属性:对于数据库来说就是外键,对一定义为实体类,对多定义为 ICollection<T>
字段类型可以是枚举 enum
创建数据库上下文
在 Data 文件夹中创建一个名为 SchoolContext.cs 的类,继承于 DbContext
使用依赖注入注册上下文
修改 Startup.cs 的 ConfigureServices 方法
在 appsettings.json 中配置数据库连接字符串
添加代码以使用测试数据初始化数据库
在 Data 文件夹中创建一个名为 DbInitializer.cs 的类
修改 Startup.cs 的 Configure 方法
新建一个控制器和视图
在 Controllers 文件夹上右键,添加
MVC 依赖项选择 Minimal Dependencies 即可
会自动安装 NuGet 包:Microsoft.EntityFrameworkCore.Design 和 Microsoft.EntityFrameworkCore.SqlServer.Design
公约
表名优先来自 DbSet 属性名,其次是类名
实体属性名将映射为列名
实体属性名 ID 或 类名ID 将默认被设为主键
如果属性被命名,它将当作外键(例如,导航属性 Student 对应的实体 Student 的主键是 ID,所以 StudentID 是外键);
公约可以被覆盖,例如,可以指定表名,设置列名,并将任何属性设置为主键或外键。
异步代码
第二课:新建、读取、更新和删除操作(CRUD)
自定义详细信息页面
Include() // 加载导航属性
ThenInclude() // 加载导航属性的导航属性
AsNoTracking() // 使该实体在当前上下文生命周期中不更新
SingleOrDefaultAsync() // 异步检索单个实体
路由数据
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>
<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>
生成为
<a href="/Students/Edit/6">Edit</a>
<a href="/Students/Edit?studentID=6">Edit</a>
改进“新建页面”
在 HttpPost 的 Create 方法中,因为 ID 是自动递增的,所以可以从 Bind 中删除
[ValidateAntiForgeryToken] 搭配 if (ModelState.IsValid),用于实现验证,防止 CSRF 攻击
改进“删除页面”
在 HttpGet 的 Delete 方法中增加参数用于传递报错内容
在 HttpPost 的 Delete 方法中删除失败则 RedirectToAction 到 Delete 页面并带上报错参数
用”初始化和附加“的方法实现删除
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
实例化要删除的实体(仅指定主键值),并将状态设置为 Deleted,即可在 SaveChanges 时实现删除,从而避免了删除必须先读取的情况。
处理交易
当 SaveChanges 时,EF 自动确保所有更改都成功或都失败,如果首先完成一些更改,然后发生错误,那么这些更改将自动回滚。
无追踪查询
什么情况下适用 AsNoTracking 方法:
在上下文中不需要更新任何实体,并且不需要加载导航属性(若需要导航属性,可以预先 Include 或稍后单独查询)
检索大量数据时,仅一小部分将被更新(稍后单独查询)
当前检索的实体需要在稍后以不同的目的附加(_context.Entry(*).State)
第三课:排序,过滤,分布和分组
添加列排序链接到学生索引页
将“搜索框”添加到“学生索引”页面
将分页功能添加到“学生索引”页面
PaginatedList 类的使用
创建一个显示学生统计信息的关于页面
创建视图模型:在 Models 文件夹中创建一个 SchoolViewModels 文件夹,添加一个类文件 EnrollmentDateGroup.cs
修改 HomeController 添加上下文
使用 group by 进行分组查询
第四课:迁移
用于迁移的 NuGet 软件包
要使用迁移,可以使用包管理器控制台(PMC)或命令行界面(CLI)。教程
CLI 的另一种打开方式:在项目上右键 - 在文件资源管理器中打开文件夹,在地址栏上键入 cmd 回车
删除数据库的 CLI 命令
dotnet ef database drop
创建初始迁移
dotnet ef migrations add InitialCreate
创建数据库的代码位于 Migrations 文件夹中,Up 方法用于创建,Down 方法用于回滚
这时只生成了创建数据库的代码,并没有真正的创建数据库
多次创建的迁移会按时间戳的次序展示在 Migrations 文件夹中,数据库中记录了最后一次迁移的版本
检查数据模型快照
迁移会在 Migrations/SchoolContextModelSnapshot.cs 中创建当前数据库模式的快照,因此 EF Core 不必与数据库进行交互以创建迁移。
要删除最后一次迁移,执行:
dotnet ef migrations remove
将迁移应用于数据库
dotnet ef database update
执行完成会创建数据库结构,但表中的初始数据将在第一次运行时被添加。
上次应用迁移之后创建的一个或多个新迁移,将在当前应用迁移时按顺序全部执行。
第五课:创建复杂的数据模型
通过使用属性自定义数据模型
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType 用于指定比数据库固有类型更具体的数据类型,上例指定为日期,而不是日期和时间
DateType 支持:Date, Time, PhoneNumber, Currency, EmailAddress 等等
DateType 支持传达数据的语义到 HTML5
DisplayFormat 用于指定格式
ApplyFormatInEditMode 指定是否当值显示在文本框中进行编辑时也应用此格式
[StringLength(50)]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
StringLength 设置数据库中最大长度,并为客户端和服务端提供验证
StringLength 可以指定最小长度,但该值对数据库没有影响
StringLength 不会阻止用户输入空格,可以使用 RegularExpression 来限制
缩短最大长度会导致迁移时发出警告,为了保证顺利迁移,请先更改数据或重新选择合适的最大长度
[Column("FirstName")]
public string FirstMidName { get; set; }
上例将属性 FirstMidName 映射到数据库列名 FirstName
[Required]
必需属性,不能为空的类型将自动处理必填字段,无需重复指定。
[Display(Name = "Last Name")]
显示属性,指定用于显示的名称
计算属性,只有一个 get 访问器,不会在数据库中生成列,例如:
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
可以将多个属性放在一行中:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
在一对一的关系中,副表的主键名称不以副表实体 classnameID 命名,而以主表实体 classnameID 命名时,用 Key 属性来指定主键:
[Key]
或者需要以除 classnameID 或 ID 外的其它属性为主键,那么可以用 Key
[Range(0, 5)]
指定数值范围
当有相关实体的导航属性时,EF 不要求在数据模型中添加外键属性。详情见教程
[DatabaseGenerated(DatabaseGeneratedOption.None)]
主键值由用户提供,而不是由数据库生成。
[Column(TypeName = "money")]
public decimal Budget { get; set; }
通常不需要列映射,此例将 decimal 类型的属性 Budget 映射为 money 类型的字段 Budget。
部门可能有负责人也可能没有,负责人必定是教师。因此 InstructorID 属性为教师实体的外键,并且可空,导航属性是被命名为 Administrator 的 Instructor 实体。
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
通常,EF 允许对不可空的外键进行级联删除以久多对多关系。
例如,如果将 Department.InstructorID 属性定义为不可空,则 EF 将配置级联删除规则,以在删除部门时删除教师。如果我们想保留教师,但业务逻辑要求该 InstructorID 属性确实不可为空,则必须使用以下语句来禁用关系中的级联删除:
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
补充,现在设计业务逻辑时会尽量避免删除操作,而使用禁用来代替,以在数据库中永久保留记录。
[DisplayFormat(NullDisplayText = "No grade")]
NullDisplayText 指定当值为 null 时的显示内容
联合主键
在多对多关系(教师到课程)的连接表(CourseAssignment)中,两个外键都不可为空,并且一同唯一标识表的每一行,因此不需要单独的主键,将 InstructorID 和 CourseID 作为一个联合主键。
定义联合主键不能通过使用属性来完成,唯一方法是使用 Fluent API,修改 Data/SchoolContext.cs 的 OnModelCreating 方法:
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
Fluent API 替代属性
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
在本教程中,您只使用 Fluent API 进行数据库映射,而不能使用属性。您还可以使用 Fluent API 来指定可以使用属性执行的大多数格式化、验证和映射规则。一些属性(如 MinimumLength)不能应用 Fluent API,因为它不会更改数据库模式,它只适用于客户端和服务端验证。
一些开发人员喜欢专门使用 Fluent API,以保持实体类“干净”。当然可以混合使用属性和 Fluent API,但推荐只选择一种方式。如果混合使用,那么 Fluent API 会覆盖属性。
添加迁移
dotnet ef migrations add ComplexDataModel
如果在此时尝试运行命令 database update(不要这样做),您将收到以下错误:
ALTER TABLE语句与FOREIGN KEY约束“FK_dbo.Course_dbo.Department_DepartmentID”冲突。冲突发生在数据库“ContosoUniversity”,表“dbo.Department”,“DepartmentID”列。
这是因为在课程 Course 表增加了一个 DepartmentID 外键,而且是不可空的,又由于课程表中已经存在数据,所以无法 AddColumn。
我们可以创建一个临时部门,然后将它的 DepartmentID 值赋给 Course.DepartmentID 作为默认值。
打开 _ComplexDataModel.cs 文件,注释将 DepartmentID 添加到课程表的代码行,注释之,并在创建 Department 表的代码之后添加创建默认部门和添加 DepartmentID 的代码,详见教程。
第六课:读取相关数据
预先加载、显式加载和延时加载
预先加载:当实体被读取时,相关数据随之被检索。即在单个连接查询中读取所有所需数据。通过 Include 和 ThenInclude 方法实现。
显式加载:当实体首次读取时,不检索相关数据。如果需要再检索相关数据。显式加载会导致多次数据库查询。
延时加载:当实体首次读取时,不检索相关数据。在首次尝试访问导航属性时,将自动检索该导航属性所需的数据。
* 从定义上看“显示加载”和“延时加载”比较类似,详细区别会在学完本课后有具体的了解。
创建一个显示部门名称的课程页面
在课程列表中,每个课程都显示部门名称,因此使用预先加载。
在控制器中使用 Include 和 AsNoTracking
在视图中使用 @Html.DisplayFor(modelItem => item.Department.Name)
创建一个显示课程和报名的教师页面
为教师页面创建一个视图模型 InstructorIndexData.cs
在控制器中多次使用 Include 和 ThenInclude 来预先加载相关数据
第一个 Include 加载办公室(一对零或一)
第二个 Include 加载课程(多对多)和报名课程的学生(多对多)
第三个 Include 加载课程(多对多)和课程所在部门(多对一)
本例中“课程的学生”和“课程的部门”是各自级联 ThenInclude 加载的,不能合并到同一个 Include 的课程中,因为 ThenInclude 不能读取多个分支的导航属性,每个分支都必须使用 Include 从教师实体调用。
在视图中给选择的教师添加 class 样式类
显式加载
去掉 Include 和 AsNoTracking,然后在需要的时候附加相关的实体(上下文.Entry())来实现显式加载,以减少首次读取实体时的数据量。
await 上下文.Entry(单个实体).Collection(x=>x.导航属性).LoadAsync(); // 一对多
await 上下文.Entry(单个实体).Reference(x=>x.导航属性).LoadAsync(); // 一对一
第七课:更新相关数据
课程必须与部门关联,在新建和编辑课程时提供部门的下拉列表。
ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(), "DepartmentID", "Name", selectedDepartment);
<select asp-for="DepartmentID" class="form-control" asp-items="ViewBag.DepartmentID">
<option value="">-- Select Department --</option>
</select>
SelectList 为下拉列表创建一个集合,允许指定默认选中项,并传递到 ViewBag。
将 .AsNoTracking() 添加到详细信息和删除方法
“一对零或一”关系:(本例是教师和办公室分配,抑或是文章和正文、学生和档案)
删除教师的办公室分配时,如果原先具有值,则删除办公室分配;
输入教师的办公室分配时,如果原先为空,则创建一个办公室分配;
更改教师的办公室分配时,更改现在有办公室分配值。
如何处理:
在 HttpGet 的 Edit 方法中,Include 办公室实体,并 AsNoTracking
在 HttpPost 的 Edit 方法中,移除除 id 以外的参数(为了不与 HttpGet 的 Edit 冲突,改名为 EditPost,并 ActionName("Edit"))
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
使用模型绑定器的值更新检索到的教师实体。TryUpdateModel 支持更新实体的导航属性。
我们可以直接设置实体的导航属性为 null,以便相关的行将被删除。
第八课:处理并发冲突
悲观并发,即锁定,性能差。
乐观并发:
[Timestamp]
public byte[] RowVersion { get; set; }
添加一个 timestamp 类型的 RowVersion 字段
详见教程
第九课:继承
第十课:进阶专题
调用“返回实体”的查询
为了防止 SQL 注入,使用参数化查询:
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.SingleOrDefaultAsync();
if (department == null)
{
return NotFound();
}
return View(department);
}
调用“返回其他类型”的查询
编写 SQL,而非 LINQ:
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
调用“更新”查询(批量更新)
使用参数化查询批量更新数据,返回受影响行数:
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
检查发送到数据库的 SQL
有时,可以看到发送到数据库的实际 SQL 查询很有帮助。
第一步:设置一个断点
第二步:调试模式运行程序
第三步:在“输出”窗口可以看到 SQL 查询语句
自动变化检测
EF 通过将实体的当前值与原始值进行比较来确定实体如何更改。
一些方法会导致自动更改检测:
DbContext.SaveChages
DbContext.Entry
ChangeTracker.Entries
如果您正在跟踪大量实体,并且您在循环中多次调用上述方法之一,则可以通过临时关闭 AutoDetectChangesEnabled 属性,能显著提升性能。
_context.ChangeTracker.AutoDetectChangesEnabled = false;
从现有数据库反向工程
常见错误
问:ContosoUniversity.dll 由另一个进程使用
答:在 IIS Express 中停止站点。
问:迁移在 Up 和 Down 方法中没有代码
原因:在运行migrations add
命令时有未保存的更改
答:运行migrations remove
命令,保存代码更改并重新运行migrations add
命令。

本文不定时更新!
A: MySQL 执行 SHOW FULL PROCESSLIST
Q: 查看连接数和慢查询,适用于 MySQL 数据库无法连接 1040
A: iftop -i eth0
Q: 查看占用带宽的IP(命令:iftop -i eth0 -F ip/24
),添加到安全组、防火墙、宝塔的黑名单中。
命令 grep -l "x.x.x.x" /www/wwwlogs/*.log
可以在 wwwlogs 目录下的所有 .log 文件中查找指定的恶意 IP。
A: goaccess -f xxx.log
Q: 实时分析网站日志,查看请求最多的IP
A: net.xoyozo.weblog 日志分析工具
Q: 自制的 Web 日志分析工具,可按多种方式排序,纠出可疑访问
A: 重启 web 服务器
Q: 有时候能解决 CPU 和内存消耗的问题,如果一会儿又升高,则需要找另外的原因
Q: 500 服务器内部错误
502 Bad Gateway
504 Gateway Time-out
A: 查看 php 日志,可能的路径:
/usr/local/php/var/log/php-fpm.log
/www/server/php/[版本]/var/log/php-fpm.log
Q: RDS MySQL IOPS 使用率高的原因和处理
A: 根据时间点查看慢查询
Q: Discuz! 论坛界面错乱、表情不显示、模块缺失、登录失败、发帖失败等等
A: 进入管理中心 - 工具 - 更新缓存,能解决大部分问题
Q: Discuz! 浏览帖子提示“没有找到帖子”
A: 进入数据库,修复表 pre_forum_post 或分表
Q: CPU 100% 或内存 100%,负载100+
A: 原因有很多,以下是一些建议:
Windows 在任务管理器中查看进程
当前是否有正常的大流量访问(譬如民生类论坛的某个帖子突然火了)特别是重启无效的情况
对比网站日志大小可大致确定哪个网站被大量恶意请求。
观察:命令 top
排查:通过关闭网站来确定是某网站的问题,通过关闭功能确定是某功能的问题,如果 nginx 崩溃请参下条
案例:通过修改 mobcent 文件夹名确定是安米的文件被疯狂请求导致的,更新插件和 mobcent 包解决问题。
如果都是正常访问,top 看到很多 php-fpm,而且个个占用 CPU 还不小,那么根据服务器硬件配置来修改 php 的并发量,如宝塔面板在 php 设置 - 性能调整 页,300 并发方案的推荐配置是:
max_children:300
start_servers:30
min_spare_servers:30
max_spare_servers:180
另外,memcached 或 redis 的配置也可以进行相应的修改。
另一个案例是 kswapd0 进程占满 CPU,原因是内存不足导致 swap 分区与内存频繁交换数据。同样调整 php 的设置即可。
也可以通过 iftop 来查询占用带宽较多的 IP 并封禁(出方向),如果 CPU 能降下来,那这个 IP 就是罪魁祸首。
Q: 阿里云 ECS 的 CPU 突然达到 100%,并持续到次日 0:00 左右
A: 可能 ECS 是 t5 规格,受 CPU 积分制度限制,积分耗尽时 CPU 不工作。解决方法是更换其它规格产品或升配。
Q: ASP.NET 所在服务器 CPU 突然达到 50% 或 100%,并持续
A: 首先确定哪个网站,再依次排查网站各功能。可能是 HttpWebRequest 请求远程数据时长时间未返回结果导致的程序阻塞。
Q: nginx 服务停止
A: 查看 nginx 日志
WDCP 路径:/www/wdlinux/nginx-1.0.15/logs/error.log
Q: 公网出带宽 100%,其它指标正常
A: Windows 在任务管理器-性能-资源监视器-网络 查看占用带宽的进程PID,然后在任务管理器-详细信息中的找到对应的用户(如果为每个网站分别创建了用户,就能知道是哪个网站占用了带宽);如果是被 PID 为 4 的 System 占用大部分带宽,也可以尝试重启 IIS 来解决。
CentOS 使用 nethogs 查看占用带宽的进程PID和USER,如果为每个网站分别创建了用户,就能知道是哪个网站占用了带宽,否则只能一个个关闭网站来判断,不知道大家有没有好的方法?当然还可以直接用 iftop 命令查看占用带宽的 IP。另外,查看每个网站在那个时间段的日志文件的大小也能大概看出是哪个网站被采集了。
A: Linux 显示每个用户会话的登入和登出信息
utmpdump /var/log/wtmp
参考:http://www.tulaoshi.com/n/20160331/2050641.html
Q: RDS 的 CPU 100%
A: 如果是突然持续占满(同时伴随 ECS 资源使用率下降,页面出现 502),很大可能是受攻击(或社交网站推送突发事件等),查看“慢查询”,添加相关索引;如果是 Discuz! 论坛,可尝试修复优化表 pre_common_session。
如果是数日缓步上升,或新项目上线,考虑 SQL 慢查询,思路:MySQL / SQL Server。
MySQL:SHOW FULL PROCESSLIST
SQL Server:sp_who
Q: php 网站的服务器,内存在数天内缓慢上升
A: 大概是 php-fpm 占用过多,或进程数太多
更改 php 的配置(如 max_spare_servers),执行:service php-fpm reload
Q: 进程 cloudfs 占用内存过多
A: 参:https://xoyozo.net/Blog/Details/cloudfs-cache
Q: RDS 磁盘占用过大
A: 参:https://xoyozo.net/Blog/Details/how-to-use-rds
Q: ECS 受到 DDoS 攻击怎么办?
A: 参:https://xoyozo.net/Blog/Details/aliyun-ddos-without-bgp
Q: 如果 ECS 和 RDS 各项指标都没有异常,但网页打开慢或打不开502,TTFB 时间很长,是什么原因?(ECS 的 CPU 100%,RDS 的连接数上升,也可参考此条)
A: 数据库有坏表,尝试优化/修复表(慢 SQL 日志中锁等待时间较长的表?),或主备切换。show full processlist 时看到许多
DELETE FROM pre_common_session WHERE sid='******' OR lastactivity<****** OR (uid='0' AND ip1='*' AND ip2='*' AND ip3='*' AND ip4='*' AND lastactivity>******)
Q: Discuz! 创始人(站长)密码被改
A: 数据库找到 pre_ucenter_members 表,复制其它的已知登录密码的账号,复制其 password 和 salt 两个字段的值到创始人账号中,创始人账号即可用该密码登录了。
Q: 通过 iftop 观察到,Discuz! 网站从 RDS 数据库到 ECS 网站服务器私网流量非常大,远大于公网流量
A: 可能是缓存出问题了,尝试卸载重装 Redis 来解决。
Q: 宝塔面板中安装的 Redis 经常自动停止
A: 尝试卸载重装 Redis 来解决。
Q: 马甲客户端出现“您的网络有些问题”
A: 原因有许多,其中一个就是新建了一个数据表,然后 /source/class/table/ 下面丢失了对应的文件,具体可以找官方排查原因。
Q: 排查服务器安全需要检查哪些日志?
A: Web日志、登录日志(/var/log/secure)等。
Q: 带宽波形以几分钟为周期呈锯齿状波动是什么原因?
A: 该现象主要由防火墙流量管控机制与检测周期设置共同作用所致。防火墙基于预设的带宽阈值执行安全防护策略,当检测到流量峰值超过设定阈值时,将自动触发限流策略拒绝后续请求。待流量回落至安全阈值后,系统自动恢复服务访问权限。若防火墙的带宽采样检测周期设置过长(如以分钟为单位的检测间隔),将导致系统对实时流量变化的响应出现迟滞。这种周期性的检测机制会使带宽监控数据在阈值临界点附近呈现规律性的锯齿状波动特征。
优化建议:可通过调整防火墙的流量检测周期至更小时间粒度(如10-15秒),或采用动态流量整形策略,以实现更平滑的带宽控制效果。
补充说明:对于阿里云监控数据的调用,建议注意接口调用频率管控。高频调用云监控API接口将触发阿里云的API计费策略,可能产生额外的资源消耗成本。
其它案例
某台 ECS 上的多个网站出现 502,查询到 CPU / 内存 / 带宽 都比平时高,但都未满。用 iftop 看到连接的一个远程 Redis 占用内网带宽非常高。尝试重启远程 Redis 未果。尝试重启 ECS 上的 nginx 未果。尝试重启 ECS 未果。进入 ECS 的宝塔面板,重启 PHP 有效。

新建项目
使用 VS2017 / VS2015 新建项目 - ASP.NET Web 应用程序 - MVC - 个人身份验证
添加模型
右击 Models 文件夹,添加类 Movie
using System; using System.Data.Entity; namespace MvcMovie.Models { public class Movie { public int ID { get; set; } public string Title { get; set; } public DateTime ReleaseDate { get; set; } public string Genre { get; set; } public decimal Price { get; set; } } public class MovieDBContext : DbContext { public DbSet<Movie> Movies { get; set; } } }
Movie 类表示一部电影,一个对象实例对应数据库中一行,每个属性对应表中一列。
MovieDBContext 代表 EF 数据库上下文,处理抓取、存储、更新。
创建连接字符串和使用 SQL Server
打开 Web.config
在 <configuration /> 中的 <connectionStrings /> 中添加
<add name="MovieDBContext" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Movies.mdf;Initial Catalog=Movies;Integrated Security=True" providerName="System.Data.SqlClient" />
name 必须与 DbContext 类的名称匹配(MovieDBContext)
这步将会把数据库文件 Movies.mdf 创建到 App_Data 文件夹中,你也可以使用其它 SQL Server 数据库的连接字符串,简单的方法是:
在“服务器资源管理器”中添加连接
从右击属性中获取连接字符串
从控制器访问模型的数据
右击 Controllers 文件夹添加控制器,选择 包含视图的 MVC 5 控制器(使用 Entity Framework)
模型类:Movie (MvcMovie.Models)
数据上下文类:MovieDBContext (MvcMovie.Models)
F5 运行,访问 /Movies 可添加、查看、编辑、删除影片
添加新字段
设置模型更改的 Code First 迁移
在程序包管理器控制台窗口中,在 PM>
提示符下输入
Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext
在 Migrations 文件夹中新建了 Configurations.cs,打开并在 Seed 方法中加入
context.Movies.AddOrUpdate(i => i.Title, new Movie { Title = "When Harry Met Sally", ReleaseDate = DateTime.Parse("1989-1-11"), Genre = "Romantic Comedy", Price = 7.99M }, new Movie { Title = "Ghostbusters ", ReleaseDate = DateTime.Parse("1984-3-13"), Genre = "Comedy", Price = 8.99M } );
Seed() 将在每次迁移(PM>update-database)后被调用执行,AddOrUpdate 将执行 upsert 操作(有则 update,无则 insert)
AddOrUpdate 的第一个参数指定用于检查行是否已存在的属性
如果该属性值不唯,则出现异常
序列包含多个元素
创建迁移命令:
PM>add-migration Initial
名称“Initial”是任意的
执行迁移:
PM>update-database
F5 运行将显示种子数据
向 Movie 模型添加 Rating 属性
向 Movie 类添加属性,生成
public string Rating { get; set; }
向 Create 和 Edit Action 方法的 Bind 属性添加 Rating
更改各视图支持新的 Rating 属性
此时 F5 运行将提示
System.InvalidOperationException:“支持“MovieDBContext”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。”
要解决错误除了手动往数据库中添加 Rating 字段外可以利用 Code First 迁移:
更新 Migrations\Configuration.cs 给每个 Movie 对象添加一个 Rating 字段
PM>add-migration Rating
名称 Rating 是任意的
创建了 DbMigration 的派生类 Rating,可以看到添加新列的代码
PM>update-database
数据库自动完成了对新字段的添加,当然 update-database
也会把种子数据还原。
参考
Getting Started with ASP.NET MVC 5

本文适用于 EF Core,并非 EF 6.x 及以下。
在本演练中,您将构建一个使用 Entity Framework 执行基本数据访问的 ASP.NET Core MVC 应用程序。您将使用逆向工程基于现有数据库创建实体框架模型。
创建一个新项目
打开 Visual Studio 2017
文件 -> 新建 -> 项目...
从左侧菜单中选择 已安装 -> 模板 -> Visual C# -> Web
选择 ASP.NET Core Web应用程序(.NET Core)项目模板
输入项目名称,然后单击确定
在 ASP.NET Core 1.1 选择 Web 应用程序
确保“身份验证”设置为“不进行身份验证”
点击确定
安装 Entity Framework
要使用 EF Core,请安装目标数据库提供程序的软件包。本演练使用 SQL Server。有关可用提供程序的列表,请参阅 Database Providers(从打开页面的左侧菜单选择相应的数据库)。
工具 -> NuGet 包管理器 -> 管理解决方案的 NuGet 程序包
在“浏览”选项卡中搜索并安装 Microsoft.EntityFrameworkCore.SqlServer
我们将使用一些 Entity Framework 命令从数据库创建模型。所以我们也将安装工具包。
安装 Microsoft.EntityFrameworkCore.SqlServer.Design
安装 Microsoft.EntityFrameworkCore.Tools
模型的反向工程
现在是根据现有数据库创建 EF 模型的时候了。
工具 -> NuGet 包管理器 -> 程序包管理器控制台
运行
Scaffold-DbContext "数据库连接字符串" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
数据库连接字符串可以从 服务器资源管理器 -> 数据连接,在要连接的数据库上右击属性来获得。
如果您收到以下错误说明,请关闭并重新打开 Visual Studio 后重试。
The term 'Scaffold-DbContext' is not recognized as the name of a cmdlet
逆向工程过程基于现有数据库的模式创建实体类(表名.cs)和派生上下文(库名Context.cs)。
实体类是简单的 C# 对象,表示要查询和保存的数据。
上下文表示与数据库的会话,并允许您查询和保存实体类的实例。
使用依赖注入注册上下文
依赖注入的概念是ASP.NET Core的核心。服务(例如数据库 Context)在应用程序启动期间使用依赖注入进行注册。然后,需要这些服务的组件(例如您的 MVC 控制器)通过构造函数参数或属性提供这些服务。
删除内联上下文配置
打开 Models\库名Context.cs
删除方法 OnConfiguring(...)
添加以下构造函数,这将允许通过依赖注入将配置传递到上下文中
public 库名Context(DbContextOptions<库名Context> options) : base(options) { }
在 Startup.cs 中注册和配置上下文
为了使我们的 MVC 控制器使用数据库Context,我们将它注册为一个服务。
打开 Startup.cs
使用 using 语句在文件的开头添加以下内容
using 项目命名空间.Models; using Microsoft.EntityFrameworkCore;
现在我们可以使用 AddDbContext(...)方法将其注册为服务。
找到 ConfigureServices(...)方法
添加以下代码以将上下文注册为服务
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); var connection = @"连接字符串"; services.AddDbContext<库名Context>(options => options.UseSqlServer(connection)); }
在实际应用程序中,通常将连接字符串放在配置文件中。为了简单起见,我们在代码中定义它。查看如何将连接字符串配置到 appsettings.json 文件中
创建一个控制器
接下来,我们将在我们的项目中启用基架。
右键单击解决方案资源管理器中的 Controllers 文件夹,然后选择添加 -> 控制器...
选择 Full Dependencies(完全依赖),然后单击添加
您可以忽略打开的
ScaffoldingReadMe.txt
文件中的指令
现在基架已启用,我们可以为数据表实体构建控制器。
右键单击解决方案资源管理器中的 Controllers 文件夹,然后选择添加 -> 控制器...
选择“视图使用 Entity Framework 的 MVC 控制器”并单击“添加”
设置模型类和数据上下文类
单击添加
运行应用程序
您现在可以运行应用程序以查看它的操作。
调试 -> 开始执行(不调试)
应用程序将在 Web 浏览器中构建和打开
导航到相应的路径
/控制器名
单击 Create New
填写表单并单击 Create
EF Core for MySQL
截至 Visual Studio 2017 正式版发布(2017年3月7日),MySQL for Visual Studio(支持)尚不支持 Visual Studio 2017,因此 Visual Studio 2017 的服务器资源管理器不能连接数据源为 MySQL 的数据库,也无法创建和更新可视化模型(.edmx)
NuGet 包 MySql.Data.EntityFrameworkCore 还没有找到 Database First 的 PM 命令,因此不能由 MySQL 数据库生成实体类和上下文
综上,要连接 MySQL 只能用 VS2015,要生成 MySQL 实体类和上下文就不能用 EF Core,暂时使用 EF 6.x 代替(创建 ASP.NET MVC 项目)
EF Core 1.1 未实现
EF Core 1.1 未实现:可视化模型,以查看基于代码的模型的图形表示。
EF Core 1.1 未实现:用于逆向工程的 Visual Studio 向导,允许您在从现有数据库创建模型时可视化地配置连接,选择表等。
EF Core 1.1 未实现:从数据库更新模型,允许从数据库以前反向工程的模型使用对模式所做的更改进行刷新。
补充
如果我们在创建新项目时选择 ASP.NET Core Web应用程序(.NET Framework)项目模板,那么完全按照 Core 的方式来开始(比如选择 NuGet 包的版本),而发布必须是 .NET Framework 环境,而非跨平台。
参考

语法示例
ASPX:
<ul> <% foreach(var p in products) { %> <li><%=p.Name%> ($<%=p.Price%>)</li> <% } %> </ul>
Razor:
<ul> @foreach(var p in products) { <li>@p.Name ($@p.Price)</li> } </ul>
代码块
ASPX: <% int a = 1; %> Razor: @{ int a = 1; }
渲染输出
经过 HTML 编码
ASPX: <span><%: model.Message %></span> Razor: <span>@model.Message</span>
未经 HTML 编码
ASPX: <span><%= model.Message %></span> Razor: <span>@Html.Raw(model.Message)</span>
代码与纯文本混合
ASPX:
<% if(foo) { %> Plain Text <% } %>
Razor 写法一:多用于多行文本
@if(foo) { <text>Plain Text</text> }
Razor 写法二:多用于单行文本
@if(foo) { @:Plain Text }
@ 符号
<span>我在微博上@@了她</span>
自动识别 Email,不需要 @@
940534113@qq.com
显示渲染输出
如果变量被误识别为 Email,可以使用括号包围变量
<span>ISBN@(isbnNumber)</span>
服务端注释
@* 注释内容 *@
渲染输出动态方法
@(MyClass.MyMethod<AType>())
Razor 委托
复用视图逻辑
@{ Func<dynamic, object> b = @<strong>@item</strong>; } @b("Bold this")
布局文件 _Layout.cshtml
视图主体区域 @RenderBody() 创建预设区域 @RenderSection("名称", required: false) 使用预设区域 @section 名称 {...}
视图入口文件 _ViewStart.cshtml
放置通用视图代码,譬如 @{ Layout = "~/Views/Shared/_Layout.cshtml"; } 视图中可重写覆盖 _ViewStart.cshtml 中的设置,譬如 @{ Layout = null; }
@ViewBag.Title 相当于 @ViewData["Title"]
一款将 ASPX 视图转换为 Razor 视图的软件:razor-converter
ActionResult
ContentResult
EmptyResult
FileResult
HttpStatusCodeResult
HttpNotFoundResult
HttpUnauthorizedResult
JavaScriptResult
JsonResult
RedirectResult
RedirectToRouteResult
ViewResultBase
PartialViewResult
ViewResult
NotFound()
OK(Model)
过滤器
规定请求方式
[HttpPost] [HttpGet]
Action 名称替身(不推荐)
[ActionName("另起一名")]
授权验证
[Authorize] [Authorize(Roles="Admin")]
客户端验证
jquery.validate
远程验证
[Remote("验证的 Action 名", "控制器名", ErrorMessage="远程验证未通过")]
用于远程验证的 Action 必须是 HttpGet 的,返回 JsonResult。
自验证
结合 ValidationContext 和 ValidationResult
保持视图中代码量最小化:
- 视图中不要含有数据处理的逻辑代码
- 视图中要避免包含大的代码块
- 构建多个视图/局部视图
- 适当使用 @helper 和 @function 语法
尽量使用特定的 Model 代替 ViewData / ViewBag!
@ViewData["Message"] => @Model.Message
Ajax Helper
jquery.unobtrusive-ajax.js
@Ajax.ActionLink("Home", "Index", new AjaxOptions { UpdateTargetId = "main" }) <a data-ajax="true" data-ajax-mode="relace" data-ajax=update="#main" href="/">Home</a>
推荐插件
Elmah:记录所有异常日志,便于发现 Bug,提高用户体验
RouteDebugger:查看路由详情
Glimpse:类似浏览器 F12,提供服务端信息
MiniProfiler:监控各环节耗时,找出性能瓶颈
教程
ASP.NET MVC 微软官方文档(英文)(一键翻译即可)
