Search code examples
entity-frameworkentity-framework-coreentity

How can I set a DbSet as readonly with Entity Framework Core/6


I am trying to use EntityFramework Core/6 to interact with the database. I would like to keep the DbContext class as slim and possible. One way I am doing it is by moving any repository building configuration a specific class like this

public class ProductViewConfiguration : IEntityTypeConfiguration<ProductView>
{
    public void Configure(EntityTypeBuilder<ProductView> builder)
    {
        builder.ToTable("viewProducts");
    }
}

As you can see, I am trying to configure DbSet<ProductView> for a view. so I don't need to track any changes in this repository. So I somehow need to build/configure this DbSet<> with AsNoTracking().

How can I use the EntityTypeBuilder<> builder to configure the DbSet as a read-only?


Solution

  • When working with views which you want to treat as read-only, you can mark entity property setters as protected to discourage any future code from expecting that these entities can be set, then in the DbContext itself:

    protected DbSet<ProductView> ProductViewsProtected { get; set; }
    public IQueryable<ProductView> ProductViews => ProductViewsProtected.AsNoTracking();
    

    A caveat of using AsNoTracking is that all entities returned from a query based off of ProductViews won't be tracked, so if the ProductView entity has navigation properties that you would want to update (for any reason) then those won't be tracked.

    Another compromise which provides runtime protection without the limitation on related entities or any other way a ProductView entity might slip into a Change Tracker would be to implement the protected setters in the entity, then override the OnSaveChanges in the DbContext to inspect inserted, updated, and deleted entities for ProductView and/or any other read-only view. If found, throw an exception. These entities could be marked by an Attribute such as [ReadOnlyEntity] which you could inspect for prior to committing changes. You have a compile-time guard in the property setters within the entity which should deter abuse or at least prompt questions from new developers. The main guard is at runtime though, but if it were ever to trigger that exception that would be grounds for a serious talk within the team if someone ignored the established rules. (There's nothing stopping them from removing the [ReadOnlyEntity] attribute if they are messing with changing protected setters and the DbContext would likely still fail attempting to update via the view.) Ultimately the goal is make it easy for developers to understand the patterns used and reasons for them rather than trying to make it hard for them to make a mistake.

    Overall when it comes to entities I try to use DDD approaches where access to setters is restricted. Entity construction is handled by factory methods where the constructor is scoped internal, while most "complex" updates are handed in the entity via methods rather than individual property setters. This ensures that updates provide all necessary details to ensure an entity is always in a valid state. Optional, independent properties /w no complex validation beyond length/range/null checks have public setters. Read-only entities would simply have no visible setters. If developers are instructed in the patterns used, the first assumption when dealing with an entity with no setters should be "This a read-only entity" and at least prompt a discussion about their requirement.