C# 9 - updating init-only properties on records with Entity Framework

Viewed 1896

C# 9 introduced records and init-only properties to make it easier to write immutable reference objects.

I've been trying to convert an old Entity Framework project to use these features, but I've hit a bit of friction between immutable C# records with init-only properties and trying to make changes to the underlying SQL records.

Maybe I'm just pushing against the flow, but is there a pattern for defining your C# classes as immutable init-only records but still allowing for updates to the underlying SQL data?

My current (working) code which uses mutable classes:

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; }
    }
}

MyApp.cs

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();
}

However, if I change the implementation of MyReport to use C# 9 records and init-only properties:

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; }
    }
}

then I start getting an error:

CS8852 - Init-only property or indexer can only be assigned in an object initializer

on the line

usageReport.ReadyToUse = true;

I've got no complaints about the error because you obviously can't update an init-only property outside of the constructor, but I was wondering if there's a good way to work with init-only properties and mutable SQL data in Entity Framework.

I thought about doing this:

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 = usageReport with { ReadyToUse = true };
    dbContext.SaveChanges();
}

but then I'm not sure how to tell the dbContext to treat the new usageReport as a change to the underlying SQL data without triggering a massive delete and re-insert.

1 Answers

You certainly can use the C# 9 records with Entity Framework. What you can not do is to use them with the default behavior of "retreive an entity, update its properties and call .SaveChanges().

Instead you must use the copy-and-update syntax of records in conjunction with the DbContext.Update() function:

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

IMPORTANT: You need to call .AsNoTracking () on every query or disable the change tracker to prevent EF from tracking the retrieved entity. If you don't, it will throw an exception in `` .SaveChanges () '' saying that another entity with the same key is already being tracked. If you decide to declare all your entities as records, disabling the tracking system is the best option and will have a positive impact on the overall performance of the application.

EXTRA: The solution is exactly the same when working with F# records.

Related