一、高级部分
1、实体对象状态
1.1、Added
-
描述: 实体对象被添加到上下文中,但还没有保存到数据库。
-
常见场景: 使用
Add
、AddRange
方法将实体添加到上下文中。 -
示例:
var student = new Student { Name = "John Doe" }; context.Students.Add(student); // 此时,student 的状态是 Added
2.2、Unchanged
-
描述: 实体对象从数据库加载到上下文中,且没有对其进行任何修改。
-
常见场景: 实体对象从数据库查询后,默认状态为 Unchanged。
- 示例:
var student = context.Students.Find(1);
// 此时,student 的状态是 Unchanged
2.3、Modified
-
描述: 实体对象被修改,且这些修改需要在保存到数据库时应用。
-
常见场景: 修改实体的属性,并调用
SaveChanges
方法后。 -
示例:
var student = context.Students.Find(1); student.Name = "Jane Doe"; // 此时,student 的状态是 Modified
2.4、Deleted
-
描述: 实体对象被标记为从数据库中删除。
-
常见场景: 使用
Remove
方法将实体标记为删除。 -
示例:
var student = context.Students.Find(1); context.Students.Remove(student); // 此时,student 的状态是 Deleted
2.5、Detached
-
描述: 实体对象不被上下文跟踪,通常是新创建的实体或从上下文中分离的实体。
-
常见场景: 实体对象从上下文分离或未被上下文跟踪。
-
示例:
var student = new Student { Name = "Detached Student" }; // 此时,student 的状态是 Detached
检查实体状态
var student = context.Students.Find(1);
var entry = context.Entry(student);
Console.WriteLine(entry.State); // 输出 Unchanged, Modified, Added, Deleted, 或 Detached
实例
[HttpGet]
public ActionResult G_Test()
{
Blog blog = new Blog { Title = "西游记", Content = "这是师徒四人取经的故事" };
Console.WriteLine(_context.Entry(blog).State);//Detached
_context.Add(blog);
Console.WriteLine(_context.Entry(blog).State);//Added
_context.SaveChanges();
Blog blog1 = _context.Blog.First();
Console.WriteLine(_context.Entry(blog1).State);//UnChanged
blog.Content = "这是师徒四人西天取经的故事";
Console.WriteLine(_context.Entry(blog1).State);//Modified
_context.SaveChanges();
_context.Blog.Remove(blog);
Console.WriteLine(_context.Entry(blog1).State);//Deleted
return Ok("ok");
}
2、事务管理
2.1、默认事务行为
SaveChanges
方法的调用中所做的所有更改都将在一个事务中提交
2.2、手动创建事务
using var context = new YourDbContext();
using var transaction = context.Database.BeginTransaction();
try
{
// 执行第一个SaveChanges调用
context.YourEntities.Add(new YourEntity { /* ... */ });
context.SaveChanges();
// 执行其他数据库操作...
// 提交事务
transaction.Commit();
}
catch (Exception ex)
{
// 如果发生异常,则回滚事务
transaction.Rollback();
// 处理异常...
}
3.3、
using (ApplicationDbContext db = new ApplicationDbContext(options))
{
using (var scope = new TransactionScope())
{
try
{
db.Customers.Add(new Customer() { Name = "梁朝伟", Age = 11, Email = "lcw@qq.com" });
db.SaveChanges();
db.Customers.Add(new Customer() { Name = "张曼玉", Age = 55, Email = "zmy@qq.com" });
db.SaveChanges();
//提交事务
scope.Complete();
}
catch (Exception)
{
scope.Current.Rollback();回滚事务
}
}
}
3、批量
使用第三方包 EFCore.BulkExtensions
3.1、批量新增
using EFCore.BulkExtensions;
using (var context = new MyDbContext())
{
var products = new List<Product>
{
new Product { Name = "Product1", Price = 10 },
new Product { Name = "Product2", Price = 20 },
// 其他产品
};
context.BulkInsert(products);
}
3.2、批量修改
- EF Core自带
context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdate(setters => setters.SetProperty(b => b.IsVisible, false));
//更新多个属性
context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdate(setters => setters
.SetProperty(b => b.IsVisible, false)
.SetProperty(b => b.Rating, 0));
- 第三方包
using EFCore.BulkExtensions;
using (var context = new MyDbContext())
{
var products = context.Products.Where(p => p.Price < 50).ToList();
foreach (var product in products)
{
product.Price += 10;
}
context.BulkUpdate(products);
}
- 直接执行SQL语句,更高效
using (var context = new MyDbContext())
{
context.Database.ExecuteSqlRaw("UPDATE Products SET Price = Price + 10 WHERE Price < 50");
}
3.3、批量删除
using EFCore.BulkExtensions;
using (var context = new MyDbContext())
{
var products = context.Products.Where(p => p.Price < 10).ToList();
context.BulkDelete(products);
}
也可以直接执行SQL语句
4、调用视图
和实体使用差不多。只是视图通常是只读的,不能增删改
- 数据库定义视图
CREATE VIEW ProductView AS
SELECT ProductId, Name, Price
FROM Products
WHERE Price > 50;
- 定义实体
public class ProductView
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
- 配置上下文
public class MyDbContext : DbContext
{
public DbSet<ProductView> ProductViews { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("YourConnectionString");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductView>()
.HasNoKey() // 视图没有主键,要特别指明
.ToView("ProductView") // 指定视图名称
.Property(v => v.ProductId).HasColumnName("ProductId");
modelBuilder.Entity<ProductView>()
.Property(v => v.Name).HasColumnName("Name");
modelBuilder.Entity<ProductView>()
.Property(v => v.Price).HasColumnName("Price");
}
}
- 使用视图
using (var context = new MyDbContext())
{
var productViews = context.ProductViews.ToList();
foreach (var product in productViews)
{
Console.WriteLine($"ProductId: {product.ProductId}, Name: {product.Name}, Price: {product.Price}");
}
}
5、调用存储过程
5.1、只有输入参数
- 数据库定义存储过程
CREATE PROCEDURE GetProductsByPrice
@MinPrice DECIMAL(18, 2),
@MaxPrice DECIMAL(18, 2)
AS
BEGIN
SELECT ProductId, Name, Price
FROM Products
WHERE Price BETWEEN @MinPrice AND @MaxPrice
END
- 创建实体类
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
- 配置上下文
public class MyDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("YourConnectionString");
}
}
- 调用存储过程
using (var context = new MyDbContext())
{
var minPrice = 10.0m;
var maxPrice = 50.0m;
var products = context.Products
.FromSqlRaw("EXEC GetProductsByPrice @MinPrice = {0}, @MaxPrice = {1}", minPrice, maxPrice)
.ToList();
foreach (var product in products)
{
Console.WriteLine($"ProductId: {product.ProductId}, Name: {product.Name}, Price: {product.Price}");
}
}
5.2、有输出参数
- 数据库定义存储过程
CREATE PROCEDURE GetProductsAndCount
@MinPrice DECIMAL(18, 2),
@MaxPrice DECIMAL(18, 2),
@ProductCount INT OUTPUT
AS
BEGIN
SELECT ProductId, Name, Price
FROM Products
WHERE Price BETWEEN @MinPrice AND @MaxPrice
SELECT @ProductCount = COUNT(*)
FROM Products
WHERE Price BETWEEN @MinPrice AND @MaxPrice
END
- 创建实体类
- 配置上下文
- 调用存储过程
using System.Data;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using (var context = new MyDbContext())
{
var minPrice = 10.0m;
var maxPrice = 50.0m;
var productCountParameter = new SqlParameter
{
ParameterName = "@ProductCount",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Output
};
var products = context.Products
.FromSqlRaw("EXEC GetProductsAndCount @MinPrice = {0}, @MaxPrice = {1}, @ProductCount = @ProductCount OUTPUT", minPrice, maxPrice, productCountParameter)
.ToList();
var productCount = (int)productCountParameter.Value;
Console.WriteLine($"Total Products: {productCount}");
foreach (var product in products)
{
Console.WriteLine($"ProductId: {product.ProductId}, Name: {product.Name}, Price: {product.Price}");
}
}
6、并发控制
6.1、悲观并发控制
悲观并发控制假设并发冲突会频繁发生,因此在读取数据时锁定数据,以防止其他事务修改这些数据
ROWLOCK:行锁。
TABLOCK:表锁。
XLOCK:排他锁。
UPDLOCK:更新锁,防止其他事务获取排他锁。
HOLDLOCK:保持锁,直到事务完成,等同于 SERIALIZABLE
隔离级别。
示例:假设我们有一个 Product
实体类和对应的 Products
数据表。我们希望在更新产品价格时,锁定该产品记录,防止其他事务修改。
- 定义实体类和数据库上下文(略,正常配置)
- 使用事务和锁定提示更新产品价格
public void UpdateProductPrice(int productId, decimal newPrice)
{
using (var context = new MyDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
// 使用 UPDLOCK 锁定产品记录
var product = context.Products
.FromSqlRaw("SELECT * FROM Products WITH (UPDLOCK) WHERE ProductId = {0}", productId)
.SingleOrDefault();
if (product != null)
{
// 更新产品价格
product.Price = newPrice;
context.SaveChanges();
// 提交事务
transaction.Commit();
}
else
{
throw new Exception("Product not found.");
}
}
catch (Exception)
{
// 回滚事务
transaction.Rollback();
throw;
}
}
}
}
6.2、乐观并发控制
乐观并发控制则允许多个用户同时操作同一个资源,通过并发冲突检测避免并发操作。和悲观并发控制相比,乐观并发控制不需要显示使用事务,而且也不需要使用锁。
EF Core 使用并发令牌列实现乐观并发控制,并发令牌列就是需要防止并发冲突的列。
//1、通用
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
}
modelBuilder.Entity<Blog>(option =>
{
option.ToTable("M_Blog").Property(o=>o.Content).IsConcurrencyToken();//指定并发令牌列
});
//2、sqlserver自带的
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public byte[] Rowversion{ get; set; }
}
modelBuilder.Entity<Blog>(option =>
{
option.ToTable("M_Blog").Property(o=>o.Rowversion).IsRowVersion();//指定并发令牌列
});
测试
[HttpPost]
public IActionResult G_test1(string content)
{
Blog? blog = _context.Blog.Find(2);
blog.Content = content;
try
{
_context.SaveChanges();
return Ok($"content内容已经是{content}");
}
catch (DbUpdateConcurrencyException ex)//捕捉到发生并发冲突
{
var entry = ex.Entries.First();
var dbValues = entry.GetDatabaseValues();
string dbContent = dbValues.GetValue<string>(nameof(blog.Content));
return Ok($"并发冲突,数据已经被更改为{content}");
}
}