一文学会使用Entity Framework Core
Posted JimCarter
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文学会使用Entity Framework Core相关的知识,希望对你有一定的参考价值。
本文基于发稿时EF Core的最新版本5.0.
文章目录
1. 操作篇
快速开始
如何根据实体类生成模型
实体关系的配置
值转换器(Value Conversion)
数据库架构调整与数据迁移
2. 原理篇
3. 优化篇
4. 常见问题
4.1 怎么查看生成的sql
使用LogTo
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Server=(localdb)\\mssqllocaldb;Database=Blogging;Integrated Security=True")
.LogTo(Console.WriteLine, LogLevel.Information);
}
4.2 怎么直接执行sql
context.Database.ExecuteSqlRaw("UPDATE [Employees] SET [Salary] = [Salary] + 1000");
//防止sql注入方式四,直接构造DbParameter
var user = new SqlParameter("user", "johndoe");
var blogs = context.Blogs
.FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser @user", user)
.ToList();
详见【原理篇-数据查询原理】
4.3 怎么执行事务
using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();
try
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
context.SaveChanges();
var blogs = context.Blogs
.OrderBy(b => b.Url)
.ToList();
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
事务失败会自动回滚,不需要手动操作。事务的更多用法,详见【原理篇-数据保存原理】
4.4 怎么使用数据库锁
4.4.1 乐观锁
实体上新增一个属性,标记为Timestamp:
[Timestamp]
public byte[] version{get;set;}
然后把SaveChanges
用try-catch包裹起来 ,当有并发冲突时会抛出DbUpdateConcurrencyException
异常,然后进行处理 即可。
详见【原理篇-数据保存原理3.2节】
4.4.2 悲观锁
查询之后就锁住,禁止其他查询和修改,容易死锁,性能低。efcore不支持,可以借助ADO.NET的事务实现:
static void Main(string[] args)
{
using (SqlConnection conn = new SqlConnection(connstr))
{
conn.Open();
using (var tx = conn.BeginTransaction())
{
try
{
using (var selectCmd = conn.CreateCommand())
{
selectCmd.Transaction = tx;
//xlock:排它锁,ROWLOCK行锁
selectCmd.CommandText = "select * from T_Girls with(xlock,ROWLOCK) where id=1";
using (var reader = selectCmd.ExecuteReader())
{
if (!reader.Read())
{
Console.WriteLine("没有id为1的女孩");
return;
}
string bf = null;
if (!reader.IsDBNull(reader.GetOrdinal("BF")))
{
bf = reader.GetString(reader.GetOrdinal("BF"));
}
if (!string.IsNullOrEmpty(bf))//已经有男朋友
{
if (bf == myname)
{
Console.WriteLine("早已经是我的人了");
}
else
{
Console.WriteLine("早已经被" + bf + "抢走了");
}
Console.ReadKey();
return;
}
//如果bf==null,则继续向下抢
}
Console.WriteLine("查询完成,开始update");
using (var updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tx;
updateCmd.CommandText = "Update T_Girls set BF=@bf where id=1";
updateCmd.Parameters.Add(new SqlParameter("@bf", myname));
updateCmd.ExecuteNonQuery();
}
Console.WriteLine("结束Update");
Console.WriteLine("按任意键结束事务");
Console.ReadKey();
}
//事务提交之前你一直占有这行数据
tx.Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex);
tx.Rollback();
}
}
}
Console.ReadKey();
}
4.5 导航属性的类型
如果是集合则可以为ICollection<T>
或List<T>
或HashSet<T>
4.6 主外键的默认命名规则
- 主键命名规则:符合
Id
、ID
、ClassnameId
、ClassnameID
这些规则的属性自动认为是主键 - 外键规则:
<导航属性名称><导航属性对应实体的主键属性名>
或<导航属性对应实体的主键属性名>
4.7 EF Core怎么知道SaveChanges
时哪些内容要提交到数据库?
通过Change tracking
实现的。当数据从数据库读取出来之后,efcore会创建数据的快照,调用保存时会与快照进行对比。详见【原理篇-更改跟踪原理】。
4.8 禁止自动给主键赋值
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int BlogId { get; set; }
5. 一些“坑”
“坑”不一定是真坑,所以加了引号。
5.1 跟踪查询引起的问题
ef默认使用的是跟踪查询,会把查询出来的实体实例信息记录在跟踪器中。当下次查询返回了一个跟踪器中已记录(主键相同)的实体时,则会把记录的这个实体作为查询结果直接返回(虽然还是会执行一遍sql),而且不会用数据库中的值覆盖现有实体的值。所以以下代码可能会出乎你的意料:
var blog = context.Blogs.Single(b => b.Name == "fish");
blog.Url = "aa";
var b = context.Blogs.Single(b => b.Name == "fish");
Console.WriteLine(b.Url);
此时blog的url还是aa,虽然生成的sql可以看到明显有两次查询:
info: 2021/6/2 15:40:01.856 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [b].[Id], [b].[Name], [b].[Url]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'fish'
info: 2021/6/2 15:40:01.862 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [b].[Id], [b].[Name], [b].[Url]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'fish'
针对where的一个操作:
var orders = context.Orders.Where(o => o.Id > 1000).ToList();
var filtered = context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToList();
此时的查询结果:order的id大于1000的数据,而不是大于5000的数据。因为在跟踪查询的情况下,filter里的导航属性会被认为已经加载完成。
因为这个问题的存在,所以有时候你会发现明明自己没有调用Include
,却也把子实体的数据给加载过来了。
以上问题产生的原因是:efcore只会维护同一实体的一个状态,如果同一个实体既是modified又是deleted,那么保存时就会出错(上面的原理篇-更改跟踪原理做了详细解释)。这个问题可以通过使用非跟踪查询AsNoTracking
解决。
var blog = context.Blogs.Single(b => b.Name == "fish");
blog.Url = "aa";
var b = context.Blogs.AsNoTracking().Single(b => b.Name == "fish");
Console.WriteLine(b.Url);
以上是关于一文学会使用Entity Framework Core的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法以编程方式检查 Entity Framework Core 中的待定模型更改?
如何在 Entity Framework 6 中以编程方式创建与 MS SQL 的连接字符串?
Log4j 2再现新漏洞;缺乏资助不是开源软件安全的唯一问题;微软公布 Entity Framework 7.0 计划 | 开源日报