EF Core ORM 框架
一、定义EF Core模型
EF Core使用约定,特性,Fluent API 在运行时构建实体模型
按照这三者的优先级高低排序分别是:Fluent API、特性、约定。
1. 约定(即按照EF Core默认的生成关系)
(1). 在实体中如果有名为ID的字段,或者实体名+ID的字段如:PersonID,那么EFCore生成的表会自动标识为主键。并且如果它的类型是int或Guid则会默认自增长
(2). EF Core 约定创建数据库表,表名称和DbContext中定义的DbSet属性名称是相同的
(3). 数据类型的约定

varchar:按字节存储,适合存储非Unicode字符数据。每个字符占用1个字节,适合存储英文字符或其他单字节字符集的字符。
nvarchar:按字符存储,适合存储Unicode字符数据。每个字符占用2个字节,适合存储多语言字符,包括中文、日文、韩文等。
2.特性
// 指定表名
[Table("Customers")]
public class Customer
{
// 主键,自增
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
// 必填字段,最大长度 100
[Required]
[MaxLength(100)]
public string Name { get; set; }
// 唯一索引
[Index(IsUnique = true)]
[StringLength(50, MinimumLength = 5)]
public string Email { get; set; }
// 不映射到数据库
[NotMapped]
public string DisplayName => $"{Name} ({Email})";
// 复杂类型
public Address Address { get; set; }
// 导航属性,一对多关系
[InverseProperty("Customer")]
public ICollection<Order> Orders { get; set; } = new List<Order>();
// 并发检查字段
[ConcurrencyCheck]
public string LastUpdatedBy { get; set; }
// 行版本(用于并发控制)
[Timestamp]
public byte[] RowVersion { get; set; }
}
// 复杂类型
[ComplexType]
public class Address
{
[MaxLength(100)]
public string Street { get; set; }
[MaxLength(50)]
public string City { get; set; }
[MaxLength(10)]
public string PostalCode { get; set; }
}
// 订单实体
[Table("Orders")]
public class Order
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public DateTime OrderDate { get; set; }
[MaxLength(200)]
public string Description { get; set; }
// 外键
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
public int CustomerId { get; set; }
}
导航属性是实体类中的一个属性,用于表示该实体与其他实体之间的关系。它可以是:
引用导航属性:表示一对一(One-to-One)或一对多(One-to-Many)关系中的“一”方。
集合导航属性:表示一对多(One-to-Many)或多对多(Many-to-Many)关系中的“多”方。
3. Fluent API
Fluent API 相较于特性更加灵活。它的配置需要写在自定义的数据库上下文类中的OnModelCreating方法中。
public class AppDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Customer 配置
modelBuilder.Entity<Customer>(entity =>
{
// 设置表名
entity.ToTable("Customers");
// 配置主键
entity.HasKey(c => c.Id);
// 配置唯一索引
entity.HasIndex(c => c.Email).IsUnique();
// 配置复杂类型
entity.OwnsOne(c => c.Address, a =>
{
a.Property(p => p.Street).HasMaxLength(100);
a.Property(p => p.City).HasMaxLength(50);
a.Property(p => p.PostalCode).HasMaxLength(10);
});
// 配置行版本
entity.Property(c => c.RowVersion).IsRowVersion();
// 配置并发检查字段
entity.Property(c => c.LastUpdatedBy).IsConcurrencyToken();
// 配置导航属性
entity.HasMany(c => c.Orders)
.WithOne(o => o.Customer)
.HasForeignKey(o => o.CustomerId)
.OnDelete(DeleteBehavior.Cascade); // 级联删除
});
// Order 配置
modelBuilder.Entity<Order>(entity =>
{
// 设置表名
entity.ToTable("Orders");
// 配置主键
entity.HasKey(o => o.Id);
// 配置外键
entity.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(o => o.CustomerId);
// 配置字段约束
entity.Property(o => o.Description).HasMaxLength(200);
entity.Property(o => o.OrderDate).IsRequired();
});
}
}
二、EF Core的增删查改
配置数据库连接
public class AppDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("ConnectionString");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>(entity =>
{
// 设置表名
entity.ToTable("Customers");
// 配置主键
entity.HasKey(c => c.Id);
// 配置唯一索引
entity.HasIndex(c => c.Email).IsUnique();
// 配置复杂类型
entity.OwnsOne(c => c.Address, a =>
{
a.Property(p => p.Street).HasMaxLength(100);
a.Property(p => p.City).HasMaxLength(50);
a.Property(p => p.PostalCode).HasMaxLength(10);
});
// 配置行版本
entity.Property(c => c.RowVersion).IsRowVersion();
// 配置并发检查字段
entity.Property(c => c.LastUpdatedBy).IsConcurrencyToken();
// 配置导航属性
entity.HasMany(c => c.Orders)
.WithOne(o => o.Customer)
.HasForeignKey(o => o.CustomerId)
.OnDelete(DeleteBehavior.Cascade); // 级联删除
});
}
}
-
在 EF Core 中,所有的更改(如添加、更新、删除)都是在内存中进行的,只有在调用 SaveChanges() 或 SaveChangesAsync() 时,这些更改才会被提交到数据库。
-
XxxRange()函数实际上是单个操作的循环,并不是真正意义上的批量操作,但 EF Core 仍然会为每个实体生成单独的 SQL 语句,而不是真正的批量操作。
-
ExecuteSqlRaw() 和 ExecuteSqlRawAsync() 是直接执行原生 SQL 语句的方法,可以生成一条 SQL 语句来操作多条数据,是真正的批量操作。此外,区别于XxxRange(),无需将数据加载至内存(无查询操作,仅生成一条SQL)
添加数据
using (var context = new AppDbContext())
{
var customer = new Customer
{
Name = "John Doe",
Email = "john.doe@example.com",
Address = new Address
{
Street = "123 Main St",
City = "New York",
PostalCode = "10001"
},
LastUpdatedBy = "Admin"
};
context.Customers.Add(customer);
context.SaveChanges();
}
批量删除
var customers = context.Customers.Where(c => c.City == "New York").ToList();
context.Customers.RemoveRange(customers);
context.SaveChanges(); // 逐条删除,性能较低
var sql = "DELETE FROM Customers WHERE City = 'New York'";
context.Database.ExecuteSqlRaw(sql); // 批量删除,性能高
更新数据
using (var context = new AppDbContext())
{
// 查找要更新的客户
var customer = context.Customers.FirstOrDefault(c => c.Email == "john.doe@example.com");
if (customer != null)
{
customer.Name = "Jane Doe";
customer.LastUpdatedBy = "Admin";
context.SaveChanges();
Console.WriteLine("Customer updated successfully.");
}
else
{
Console.WriteLine("Customer not found.");
}
}
查询数据
using (var context = new AppDbContext())
{
// 查询所有客户及其订单
var customers = context.Customers
.Include(c => c.Orders)
.ToList();
foreach (var customer in customers)
{
Console.WriteLine($"Customer: {customer.Name}, Email: {customer.Email}");
foreach (var order in customer.Orders)
{
Console.WriteLine($"- Order: {order.Description}, Date: {order.OrderDate}");
}
}
}
三、加载模式
1. 延迟加载(Lazy Loading)
延迟加载是指在访问导航属性时,EF Core 会自动从数据库中加载相关数据。要启用延迟加载,需要满足以下条件:
安装 Microsoft.EntityFrameworkCore.Proxies 包。
在 DbContext 中启用延迟加载代理。
配置延迟加载
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseLazyLoadingProxies() // 启用延迟加载
.UseSqlServer("YourConnectionString");
}
使用延迟加载
using (var context = new AppDbContext())
{
var customer = context.Customers.First();
Console.WriteLine($"Customer: {customer.Name}");
// 访问 Orders 导航属性时,EF Core 会自动加载相关数据
foreach (var order in customer.Orders)
{
Console.WriteLine($"- Order: {order.Description}, Date: {order.OrderDate}");
}
}
2. 立即加载(Eager Loading)
立即加载是指在查询主实体时,通过 Include 方法一次性加载相关数据。
使用立即加载
using (var context = new AppDbContext())
{
var customers = context.Customers
.Include(c => c.Orders) // 立即加载 Orders
.ToList();
foreach (var customer in customers)
{
Console.WriteLine($"Customer: {customer.Name}");
foreach (var order in customer.Orders)
{
Console.WriteLine($"- Order: {order.Description}, Date: {order.OrderDate}");
}
}
}
加载多个导航属性
如果需要加载多个导航属性,可以使用多个 Include 方法:
var customers = context.Customers
.Include(c => c.Orders)
.Include(c => c.Address) // 加载复杂类型
.ToList();
3. 显式加载(Explicit Loading)
显式加载是指在需要时,通过 DbContext.Entry 方法显式加载相关数据。
使用显式加载
如果需要加载多个导航属性(例如 一对多关系,或者多对多关系),可以使用 Collection方法:
using (var context = new AppDbContext())
{
var customer = context.Customers.First();
Console.WriteLine($"Customer: {customer.Name}");
// 显式加载 Orders
context.Entry(customer)
.Collection(c => c.Orders)
.Load();
foreach (var order in customer.Orders)
{
Console.WriteLine($"- Order: {order.Description}, Date: {order.OrderDate}");
}
}
加载单个导航属性
如果需要加载单个导航属性(例如 一对一关系,或者多对一关系),可以使用 Reference 方法:
context.Entry(order)
.Reference(c => c.customer)
.Load();
Console.WriteLine($"Address: {customer.Name}, {customer.Email}");

Linq技术
一、两种语法
1. 查询语法(Query Syntax)
查询语法是一种声明式的语法,类似于 SQL 查询语句。它的特点是可读性高,适合编写复杂的查询。
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
foreach (var num in evenNumbers)
{
Console.WriteLine(num); // 输出:2, 4, 6, 8, 10
}
2. 方法语法(Method Syntax)
方法语法是一种基于扩展方法的链式调用语法。它的特点是灵活性高,适合编写简单的查询或与其他方法组合使用。
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = numbers.Where(num => num % 2 == 0);
foreach (var num in evenNumbers)
{
Console.WriteLine(num); // 输出:2, 4, 6, 8, 10
}
二、LINQ 的一些操作符
First/FirstOrDefault
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// First:返回第一个大于 3 的元素
var first = numbers.First(n => n > 3); // 结果:4
// FirstOrDefault:返回第一个大于 10 的元素,如果没有则返回 0
var firstOrDefault = numbers.FirstOrDefault(n => n > 10); // 结果:0
Single/SingleOrDefault
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Single:返回唯一一个等于 3 的元素
var single = numbers.Single(n => n == 3); // 结果:3
// SingleOrDefault:返回唯一一个等于 10 的元素,如果没有则返回 0
var singleOrDefault = numbers.SingleOrDefault(n => n == 10); // 结果:0
Select/SelectMany
var people = new List<Person>
{
new Person { Name = "Alice", Orders = new List<string> { "Order1", "Order2" } },
new Person { Name = "Bob", Orders = new List<string> { "Order3" } }
};
// Select:选择每个客户的订单集合
var select = people.Select(p => p.Orders); // 结果:List<List<string>>
// SelectMany:将订单集合展开为一维集合
var selectMany = people.SelectMany(p => p.Orders); // 结果:List<string>
Skip/Take
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 跳过前 3 个元素,取接下来的 4 个元素
var pagedNumbers = numbers.Skip(3).Take(4); // 结果:4, 5, 6, 7
应用实例:分页查询
var dataSource = Enumerable.Range(1, 100).ToList(); // 1 到 100 的列表
// 分页参数
int pageNumber = 3; // 当前页码
int pageSize = 10; // 每页大小
// 分页查询
var pagedData = dataSource
.Skip((pageNumber - 1) * pageSize) // 跳过前面的记录
.Take(pageSize) // 获取当前页的记录
.ToList();
// 输出结果
Console.WriteLine($"第 {pageNumber} 页数据:");
foreach (var item in pagedData)
{
Console.WriteLine(item);
}
Aggregate
Aggregate 通常用于计算总和、乘积、字符串拼接等操作。
TResult Aggregate<TSource, TResult>(
IEnumerable<TSource> source,
TResult seed, // 初始值
Func<TResult, TSource, TResult> func // 累积函数
);
计算集合中所有元素的乘积
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var product = numbers.Aggregate(1, (acc, num) => acc * num);
Console.WriteLine(product); // 输出:120
拼接字符串
var words = new List<string> { "Hello", "World", "LINQ" };
var sentence = words.Aggregate("", (acc, word) => acc + " " + word).Trim();
Console.WriteLine(sentence); // 输出:Hello World LINQ
计算集合中所有元素的总和
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var sum = numbers.Aggregate(0, (acc, num) => acc + num);
Console.WriteLine(sum); // 输出:15
查找最大值
var numbers = new List<int> { 10, 20, 5, 30, 15 };
var max = numbers.Aggregate(int.MinValue, (acc, num) => num > acc ? num : acc);
Console.WriteLine(max); // 输出:30
另一个重载
TSource Aggregate<TSource>(
IEnumerable<TSource> source,
Func<TSource, TSource, TSource> func // 累积函数
);
计算集合中所有元素的乘积(无初始值)
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var product = numbers.Aggregate((acc, num) => acc * num);
Console.WriteLine(product); // 输出:120
三、表达式树
表达式树是一种树形数据结构,用于表示代码中的表达式。它将代码(如 Lambda 表达式)表示为数据,而不是直接执行代码。表达式树是 LINQ 的核心技术之一,允许 LINQ 提供程序将查询转换为特定数据源的查询语言(如 SQL)。
1. 表达式树的结构
表达式树由节点组成,每个节点表示表达式的一部分。常见的节点类型包括:
ParameterExpression:表示参数(如 x)。
ConstantExpression:表示常量(如 5)
BinaryExpression:表示二元操作(如 x + 5)
MethodCallExpression:表示方法调用(如x.ToString())
MemberExpression:表示成员访问(如 x.Length)
2. 创建表达式树
表达式树可以通过以下方式创建:
使用 Lambda 表达式创建表达式树
using System;
using System.Linq.Expressions;
// 创建一个表示 "x => x > 5" 的表达式树
Expression<Func<int, bool>> expression = x => x > 5;
// 编译表达式树为委托
Func<int, bool> func = expression.Compile();
// 使用委托
bool result = func(10); // 结果:true
Console.WriteLine(result);
手动构建表达式树
using System;
using System.Linq.Expressions;
// 创建参数表达式
ParameterExpression param = Expression.Parameter(typeof(int), "x");
// 创建常量表达式
ConstantExpression constant = Expression.Constant(5, typeof(int));
// 创建二元表达式(x > 5)
BinaryExpression body = Expression.GreaterThan(param, constant);
// 创建 Lambda 表达式
Expression<Func<int, bool>> expression = Expression.Lambda<Func<int, bool>>(body, param);
// 编译表达式树为委托
Func<int, bool> func = expression.Compile();
// 使用委托
bool result = func(10); // 结果:true
Console.WriteLine(result);
四、LINQ的延迟执行和立即执行
延迟执行是指 LINQ 查询不会立即执行,而是在实际访问查询结果时才会执行。这意味着查询的定义和查询的执行是分离的。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 定义查询(未执行)
var query = numbers.Where(n => n > 2);
numbers.Add(6); // 修改数据源
// 执行查询
foreach (var num in query)
{
Console.WriteLine(num); // 输出:3, 4, 5, 6
}
}
}
立即执行是指 LINQ 查询在定义时立即执行,并将结果存储在内存中。通过调用 ToList()、ToArray()、Count() 等方法,可以立即执行查询。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 立即执行查询
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
numbers.Add(6); // 修改数据源
// 查询结果不受影响
foreach (var num in evenNumbers)
{
Console.WriteLine(num); // 输出:2, 4
}
}
}
延迟执行的实现原理
延迟执行是通过 迭代器(Iterator) 和 yield 关键字 实现的。LINQ 查询返回的是一个 IEnumerable 或 IQueryable,它们不会立即执行查询,而是在遍历时逐个生成结果。
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 自定义延迟执行
var query = FilterNumbers(numbers, n => n > 2);
numbers.Add(6); // 修改数据源
// 执行查询
foreach (var num in query)
{
Console.WriteLine(num); // 输出:3, 4, 5, 6
}
}
static IEnumerable<int> FilterNumbers(IEnumerable<int> source, Func<int, bool> predicate)
{
foreach (var num in source)
{
if (predicate(num))
{
yield return num; // 延迟执行
}
}
}
}

8344

被折叠的 条评论
为什么被折叠?



