C# 9 - 使用实体框架更新记录上的 init-only 属性

mcl*_*ton 3 c# entity-framework c#-9.0

C# 9 引入了记录init-only 属性,以便更轻松地编写不可变的引用对象。

我一直在尝试将旧的 Entity Framework 项目转换为使用这些功能,但在具有 init-only 属性的不可变 C# 记录和尝试对底层 SQL 记录进行更改之间遇到了一些摩擦。

也许我只是在推动流程,但是是否有一种模式可以将您的 C# 类定义为不可变的 init-only 记录,但仍然允许更新底层 SQL 数据?

我当前使用可变类的(工作)代码:

MyReport.cs

namespace MyNamespace 
{
    public sealed class MyReport
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ReportId { get; set; }

        public DateTime ReportDate { get; set; }
        public bool ReadyToUse { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的应用程序

using (var dbContext = DbContext.Create(connectionString))
{
    // create a new report
    var myReport = new MyReport
    {
        ReportDate = reportDate,
        ReadyToUse = false
    };

    dbContext.SaveChanges();

    ... do some other stuff ...

    // update the report status
    usageReport.ReadyToUse = true;
    dbContext.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我将 MyReport 的实现更改为使用 C# 9 记录和 init-only 属性:

MyReport.cs

namespace MyNamespace 
{
    public sealed record MyReport
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ReportId { get; init; }

        public DateTime ReportDate { get; init; }
        public bool ReadyToUse { get; init; }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我开始收到错误:

CS8852 - 仅初始化属性或索引器只能在对象初始值设定项中分配

在线上

usageReport.ReadyToUse = true;
Run Code Online (Sandbox Code Playgroud)

我对这个错误没有任何抱怨,因为您显然无法在构造函数之外更新 init-only 属性,但我想知道是否有一种很好的方法来处理 Entity Framework 中的 init-only 属性和可变 SQL 数据。

我想过这样做:

usageReport.ReadyToUse = true;
Run Code Online (Sandbox Code Playgroud)

但是我不确定如何告诉将dbContext新的 usageReport 视为对底层 SQL 数据的更改,而不会触发大量删除和重新插入。

小智 5

您当然可以在实体框架中使用 C# 9 记录。您不能做的是将它们与“检索实体,更新其属性并调用.SaveChanges().

相反,您必须将记录的复制和更新语法与 DbContext.Update() 函数结合使用:

var report = dbContext.Set<MyReport>().AsNoTracking().First(...some linq query...);
var updatedReport = report with {ReadyToUse = true};
dbContext.Update(updatedRecord);
dbContext.SaveChanges();

Run Code Online (Sandbox Code Playgroud)

重要提示:您需要调用.AsNoTracking ()每个查询或禁用更改跟踪器以防止 EF 跟踪检索到的实体。如果你不这样做,它会在``.SaveChanges()'' 中抛出一个异常,表示另一个具有相同键的实体已经被跟踪。如果您决定将所有实体声明为记录,则禁用跟踪系统是最佳选择,这将对应用程序的整体性能产生积极影响。

额外:使用 F# 记录时,解决方案完全相同。