C# 入门经典11-12 章节 EF Core ORM 框架和 Linq技术-

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); // 级联删除
            });

    }
}
  1. 在 EF Core 中,所有的更改(如添加、更新、删除)都是在内存中进行的,只有在调用 SaveChanges() 或 SaveChangesAsync() 时,这些更改才会被提交到数据库。

  2. XxxRange()函数实际上是单个操作的循环,并不是真正意义上的批量操作,但 EF Core 仍然会为每个实体生成单独的 SQL 语句,而不是真正的批量操作。

  3. 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; // 延迟执行
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ctgu20-律

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值