Appearance
实体构造函数与属性初始化
概述
EF Core 在实例化实体时支持多种构造方式,理解这些机制对于正确初始化实体、实现领域驱动设计(DDD)和控制对象生命周期至关重要。
核心概念
| 概念 | 说明 |
|---|---|
| 无参构造函数 | EF Core 默认使用无参构造函数创建实体 |
| 带参构造函数 | EF Core 6+ 支持使用带参构造函数 |
| 属性初始化器 | C# 属性初始化语法 |
| 字段支持 | EF Core 可以直接写入私有字段 |
| 工厂方法 | 静态工厂方法创建实体 |
无参构造函数
默认行为
csharp
// EF Core 默认使用无参构造函数
public class Product
{
// ✅ EF Core 会自动生成无参构造函数(如果没有定义任何构造函数)
public int Id { get; set; }
public string Name { get; set; } = null!;
public decimal Price { get; set; }
}
// 等价于:
public class Product
{
public Product() // 编译器自动生成
{
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public decimal Price { get; set; }
}显式定义无参构造函数
csharp
public class Product
{
// ⚠️ 一旦定义了带参构造函数,必须显式定义无参构造函数
private Product()
{
// EF Core 使用
}
// 业务代码使用的构造函数
public Product(string name, decimal price)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Price = price;
}
public int Id { get; set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
}带参构造函数(EF Core 6+)
基础用法
csharp
// EF Core 6+ 支持使用带参构造函数
public class Order
{
// ✅ EF Core 会匹配参数名与属性名
public Order(int id, string customerEmail, DateTime orderDate)
{
Id = id;
CustomerEmail = customerEmail;
OrderDate = orderDate;
}
public int Id { get; private set; }
public string CustomerEmail { get; private set; }
public DateTime OrderDate { get; private set; }
public OrderStatus Status { get; private set; }
}
// 配置
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(o => o.Id);
// EF Core 自动识别构造函数
});参数匹配规则
csharp
public class Customer
{
// EF Core 按以下顺序匹配:
// 1. 参数名与属性名完全匹配(不区分大小写)
// 2. 参数名去掉下划线后与属性名匹配
public Customer(int id, string firstName, string lastName, string email)
{
Id = id;
FirstName = firstName;
LastName = lastName;
Email = email;
}
public int Id { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Email { get; private set; }
}
// 也支持这种命名:
public Customer(int id, string first_name, string last_name, string email_address)
{
// EF Core 会匹配到 FirstName, LastName, EmailAddress
}部分参数 + 属性设置
csharp
public class Article
{
// 构造函数设置必需属性
public Article(string title, string content)
{
Title = title ?? throw new ArgumentNullException(nameof(title));
Content = content ?? throw new ArgumentNullException(nameof(content));
CreatedAt = DateTime.UtcNow;
Status = ArticleStatus.Draft;
}
// EF Core 通过属性设置器设置其他属性
public int Id { get; private set; }
public string Title { get; private set; }
public string Content { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime? PublishedAt { get; set; } // EF Core 直接设置
public ArticleStatus Status { get; private set; }
}字段支持(Backing Fields)
直接写入字段
csharp
public class Blog
{
private string _url = null!; // backing field
public int Id { get; set; }
// EF Core 可以直接写入 _url 字段
public string Url
{
get => _url;
private set => _url = value ?? throw new ArgumentNullException(nameof(value));
}
}
// 配置
modelBuilder.Entity<Blog>(entity =>
{
entity.Property(b => b.Url)
.HasField("_url") // 指定 backing field
.UsePropertyAccessMode(PropertyAccessMode.Field);
});自动识别 Backing Field
csharp
public class Product
{
private decimal _price; // EF Core 自动识别为 Price 的 backing field
public int Id { get; set; }
public decimal Price
{
get => _price;
set
{
if (value < 0)
throw new ArgumentException("Price cannot be negative");
_price = value;
}
}
}
// EF Core 自动识别以下命名约定:
// - _price, _Price, m_price, m_Price
// - price_, Price_只读集合字段
csharp
public class Order
{
private readonly List<OrderItem> _items = new();
public int Id { get; set; }
// EF Core 写入 _items 字段
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
// 业务方法添加订单项
public void AddItem(Product product, int quantity)
{
_items.Add(new OrderItem(product, quantity));
}
}
// 配置
modelBuilder.Entity<Order>(entity =>
{
entity.HasMany(o => o.Items)
.WithOne()
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade);
});属性初始化器
C# 属性初始化
csharp
public class User
{
public int Id { get; set; }
// ✅ 属性初始化器
public string Username { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public List<string> Roles { get; set; } = new();
}
// EF Core 行为:
// 1. 调用构造函数(如果有)
// 2. 执行属性初始化器
// 3. 从数据库加载值覆盖属性注意事项
csharp
public class Product
{
// ⚠️ 注意: 每次 new Product() 都会创建新的 List 实例
public List<Tag> Tags { get; set; } = new();
// ✅ 更好的做法: 使用构造函数初始化
public Product()
{
Tags = new();
}
}工厂方法模式
静态工厂方法
csharp
public class Money
{
private Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
public static Money FromDecimal(decimal amount, string currency)
{
return new Money(amount, currency);
}
public static Money Zero(string currency)
{
return new Money(0, currency);
}
public decimal Amount { get; private set; }
public string Currency { get; private set; }
}
// EF Core 配置
modelBuilder.ComplexType<Money>(money =>
{
money.Property(m => m.Amount).HasColumnName("Amount");
money.Property(m => m.Currency).HasColumnName("Currency");
});工厂方法创建聚合根
csharp
public class Order : AggregateRoot
{
private Order()
{
// EF Core 使用
}
public static Order Create(string customerId, List<OrderItemRequest> items)
{
var order = new Order
{
Id = Guid.NewGuid(),
CustomerId = customerId,
OrderDate = DateTime.UtcNow,
Status = OrderStatus.Pending
};
foreach (var item in items)
{
order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
}
// 发布领域事件
order.AddDomainEvent(new OrderCreatedEvent(order.Id, customerId));
return order;
}
private void AddItem(int productId, int quantity, decimal unitPrice)
{
_items.Add(new OrderItem(productId, quantity, unitPrice));
RecalculateTotal();
}
public Guid Id { get; private set; }
public string CustomerId { get; private set; }
public DateTime OrderDate { get; private set; }
public OrderStatus Status { get; private set; }
private readonly List<OrderItem> _items = new();
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
public decimal TotalAmount { get; private set; }
}DDD 实体构造函数最佳实践
保护不变量
csharp
public class EmailAddress
{
private EmailAddress(string value)
{
if (!IsValidEmail(value))
throw new ArgumentException("Invalid email format");
Value = value.ToLowerInvariant();
}
public static EmailAddress Create(string email)
{
return new EmailAddress(email);
}
public string Value { get; private set; }
private static bool IsValidEmail(string email)
{
// 邮箱验证逻辑
return !string.IsNullOrWhiteSpace(email) &&
email.Contains("@") &&
email.Contains(".");
}
}
// EF Core 配置
modelBuilder.ComplexType<EmailAddress>(email =>
{
email.Property(e => e.Value).HasColumnName("Email");
});值对象构造函数
csharp
public record Address
{
public Address(string street, string city, string zipCode)
{
Street = street ?? throw new ArgumentNullException(nameof(street));
City = city ?? throw new ArgumentNullException(nameof(city));
ZipCode = zipCode ?? throw new ArgumentNullException(nameof(zipCode));
}
public string Street { get; init; }
public string City { get; init; }
public string ZipCode { get; init; }
}
// EF Core 8+ JSON 列
modelBuilder.Entity<Customer>(entity =>
{
entity.OwnsOne(c => c.Address, address =>
{
address.ToJson();
});
});构造函数注入(非 EF Core 场景)
DbContext 构造函数注入
csharp
public class ProductService
{
private readonly AppDbContext _context;
// ✅ 依赖注入
public ProductService(AppDbContext context)
{
_context = context;
}
public async Task<Product?> GetByIdAsync(int id)
{
return await _context.Products.FindAsync(id);
}
}❌ 不要在实体中注入服务
csharp
// ❌ 错误: 实体不应该有服务依赖
public class Order
{
private readonly IPaymentGateway _paymentGateway; // ⚠️ 不要这样做
public Order(IPaymentGateway paymentGateway)
{
_paymentGateway = paymentGateway;
}
}
// ✅ 正确: 在服务层处理外部依赖
public class OrderService
{
private readonly AppDbContext _context;
private readonly IPaymentGateway _paymentGateway;
public OrderService(AppDbContext context, IPaymentGateway paymentGateway)
{
_context = context;
_paymentGateway = paymentGateway;
}
public async Task PayOrderAsync(int orderId)
{
var order = await _context.Orders.FindAsync(orderId);
await _paymentGateway.Charge(order.TotalAmount);
order.Status = OrderStatus.Paid;
await _context.SaveChangesAsync();
}
}性能考虑
构造函数 vs 属性设置
csharp
[Benchmark]
public void WithConstructor()
{
var product = new Product("Laptop", 999.99m);
}
[Benchmark]
public void WithProperties()
{
var product = new Product();
product.Name = "Laptop";
product.Price = 999.99m;
}
// 结果:
// | Method | Mean | Allocated |
// |--------------- |---------:|----------:|
// | WithConstructor| 5.2 ns | 48 B |
// | WithProperties | 8.7 ns | 48 B |
// 差异很小,选择可读性更好的方式避免构造函数中的复杂逻辑
csharp
// ❌ 错误: 构造函数中执行耗时操作
public class Product
{
public Product(int categoryId)
{
// ⚠️ 不要在构造函数中查询数据库
Category = dbContext.Categories.Find(categoryId);
}
}
// ✅ 正确: 延迟加载或使用工厂
public class Product
{
private Product()
{
}
public static async Task<Product> CreateAsync(
AppDbContext context,
int categoryId)
{
var product = new Product();
product.Category = await context.Categories.FindAsync(categoryId);
return product;
}
}最佳实践
✅ 推荐做法
1. 使用私有构造函数保护不变量
csharp
public class Money
{
private Money(decimal amount)
{
if (amount < 0)
throw new ArgumentException("Amount cannot be negative");
Amount = amount;
}
public static Money FromDecimal(decimal amount)
{
return new Money(amount);
}
public decimal Amount { get; private set; }
}2. 优先使用不可变属性
csharp
// EF Core 6+
public class Product
{
public Product(string name, decimal price)
{
Name = name;
Price = price;
}
public int Id { get; private set; }
public string Name { get; private set; } // init-only
public decimal Price { get; private set; }
}3. 明确 EF Core 使用的构造函数
csharp
public class Order
{
// 标记为 EF Core 使用
private Order()
{
// EF Core only
}
// 业务代码使用
public Order(string customerId)
{
CustomerId = customerId;
OrderDate = DateTime.UtcNow;
}
}❌ 避免的错误
1. 不要在构造函数中执行业务逻辑
csharp
// ❌ 错误
public class Order
{
public Order(string customerId)
{
// ⚠️ 不要发送电子邮件
_emailService.SendConfirmation(customerId);
}
}
// ✅ 正确: 在 service 层处理
public class OrderService
{
public async Task<Order> CreateOrderAsync(string customerId)
{
var order = new Order(customerId);
_context.Orders.Add(order);
await _context.SaveChangesAsync();
await _emailService.SendConfirmation(customerId);
return order;
}
}2. 不要忘记无参构造函数
csharp
// ❌ 错误: 定义了带参构造函数但没有无参构造函数
public class Product
{
public Product(string name) // ⚠️ EF Core 无法实例化
{
Name = name;
}
}
// ✅ 正确: 添加私有无参构造函数
public class Product
{
private Product() // EF Core 使用
{
}
public Product(string name) // 业务代码使用
{
Name = name;
}
}总结
构造函数选择决策树
需要创建实体?
│
├─ EF Core 从数据库加载?
│ ├─ 简单实体 → ✅ 无参构造函数
│ └─ 复杂实体 → ✅ 带参构造函数(EF Core 6+)
│
├─ 业务代码创建?
│ ├─ 需要验证 → ✅ 静态工厂方法
│ └─ 简单创建 → ✅ 公共构造函数
│
└─ DDD 聚合根?
└─ ✅ 私有构造函数 + 静态工厂方法核心要点
- 无参构造函数: EF Core 默认使用,必要时设为 private
- 带参构造函数: EF Core 6+ 支持,参数名匹配属性名
- Backing Fields: EF Core 可直接写入私有字段
- 工厂方法: 用于复杂创建逻辑和验证
- 不可变性: 优先使用
private set或init - 避免副作用: 构造函数中不要执行 IO 操作
- DDD 实践: 私有构造函数 + 静态工厂保护不变量