EF Core 7脚手架生成模型无集合setter的迁移适配问题咨询
EF Core 2迁移至EF Core 7:只读集合属性的问题与解决方案
背景
将项目从EF Core 2迁移到EF Core 7后,发现脚手架生成的实体模型中,导航集合属性改为只读无setter的形式,示例实体代码如下:
public class Blog { public int Id { get; set; } public string Name { get; set; } public string Author { get; set; } public ICollection<Post> Posts { get; } = new List<Post>(); } public class Post { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime PostedOn { get; set; } public Blog Blog { get; set; } }
原先EF Core 2中常用的对象初始化器赋值集合的方式,现在因无setter无法使用:
// 现在无法运行:Posts属性无setter,无法直接赋值新集合 var blog = new Blog() { Name = "", Author = "", Posts = new List<Post>() { new Post(){Title="", Content = "" }, new Post(){Title="", Content = "" }, new Post(){Title="", Content = "" }, } }; _context.RetailLot.Add(blog); _context.SaveChanges();
一、插入新实体的可行方案
除了你提到的「添加init关键字(易被脚手架覆盖)」和「手动调用Add添加元素」外,还有以下实用方案:
1. 使用集合初始化器语法
利用C#的集合初始化器特性,直接向已实例化的只读集合中添加元素,无需setter:
var blog = new Blog() { Name = "", Author = "", // 注意这里是{...}而非new List<Post>{...},直接添加元素到现有集合 Posts = { new Post(){Title="", Content = "" }, new Post(){Title="", Content = "" }, new Post(){Title="", Content = "" } } }; _context.RetailLot.Add(blog); _context.SaveChanges();
这种方式和手动Add效果一致,但写法更简洁,且不会被脚手架覆盖。
2. 自定义脚手架模板
修改EF Core的T4脚手架模板,让生成的集合属性默认带有init关键字:
- 导出EF Core的脚手架模板到项目中(可通过VS的「添加->新建项->EF Core脚手架模板」操作)
- 编辑模板文件,将集合属性的生成逻辑改为
public ICollection<{EntityName}> {PropertyName} { get; init; } = new List<{EntityName}>(); - 之后每次重新生成实体,都会保留
init关键字,无需手动修改。
二、查询投影的解决方案
针对自定义Select时无法给只读集合赋值的问题,除了「添加init」和「继承实体重写属性」外,更推荐以下方案:
1. 使用匿名类型或DTO
避免直接投影到实体类,改用匿名类型或专门的DTO(数据传输对象):
// 方案1:匿名类型 var result = _context.Blogs.Include(x=>x.Posts).Select(b => new { Name = "MyBlog_" + b.Name, Author = b.Author, Posts = b.Posts }); // 方案2:自定义DTO类 public class BlogDto { public string Name { get; set; } public string Author { get; set; } public ICollection<Post> Posts { get; set; } } var result = _context.Blogs.Include(x=>x.Posts).Select(b => new BlogDto{ Name = "MyBlog_" + b.Name, Author = b.Author, Posts = b.Posts.ToList() });
这种方案更符合领域模型设计原则:实体类作为核心领域对象,应保持其只读特性,而投影后的结果用DTO承载,避免破坏实体的封装性。
2. 使用实体构造函数投影
如果必须投影到实体类,可以给Blog添加带参数的构造函数,EF Core支持通过构造函数进行投影:
public class Blog { public Blog(string name, string author, ICollection<Post> posts) { Name = name; Author = author; // 直接向只读集合添加元素 foreach (var post in posts) { Posts.Add(post); } } public int Id { get; set; } public string Name { get; set; } public string Author { get; set; } public ICollection<Post> Posts { get; } = new List<Post>(); }
之后投影时可以调用构造函数:
var result = _context.Blogs.Include(x=>x.Posts).Select(b => new Blog( "MyBlog_" + b.Name, b.Author, b.Posts ));
注意:需要确保EF Core能够识别构造函数,且构造函数参数与属性匹配。
内容的提问来源于stack exchange,提问作者Mark Kordahi
相关产品推荐
相关产品推荐

