0

I am wondering how Entity Framework works with pure functions. I have an order class with an update status method. Instead of changing the property of the current instance, it returns a new instance with the property update. This works as expected.

public class Order {

    ...

   private Order(Order order, string status){
        Id = order.Id;
        ...
        Status = status;
    }

    Guid Id { get; private set; }
    ...
    string Status { get; private set; }

    public Order UpdateStatus(string status){
        if (Status == "Complete"){
            return this;
        }
        return new Order(this, status);
    }
}

The problem comes when using Entity Framework. Since the order returned, is not the same instance being tracked by the change tracker, entity framework doesn't correctly save the changes.

var order = this.dbContext.Orders.Single(o => o.Id == id);
var newOrder = order.UpdateStatus();
this.dbContext.SaveChanges();

Is there a better way to do this to have a pure function and still have the changes tracked?

Ashley
  • 422
  • 6
  • 18
  • You kind of want your "domain" classes to be separate from your "data layer" classes anyway. If you want to go this route, I'd update the domain objects using a pure function, then handle the persistence using a [repository](https://martinfowler.com/eaaCatalog/repository.html). – David Aug 27 '21 at 22:36
  • EF is based on *change* tracking, or mutability. Trying to blend it with pure functional concepts is like mixing oil and water. But in the end it doesn't differ much from working with [disconnected entities](https://learn.microsoft.com/en-us/ef/core/saving/disconnected-entities). – Gert Arnold Aug 28 '21 at 18:00

1 Answers1

1

There is a way, but it is not better. It involves manipulating the change tracker of EntityFramework, which really defeats the purposes of having EntityFramework doing the heavy lifting and automatically track changes. Now you have to do it manually.

I don't recommend going this way since EntityFramework is not designed for immutable entities that are manipulated with pure functions.

var newOrder = order.UpdateStatus();
// Find the old entry being tracked by the change tracker
var entry = dbContext.ChangeTracker.Entries().Where(e => e.Entity == order).FirstOrDefault();
if (entry == null)
    throw new InvalidOperationException("Cannot update Order entity because it is not being tracked by the change tracker.");
// Stop tracking the old instance
entry.State = EntityState.Detached;
// Attach new entity to the context (the state is by default Unchanged);
var newEntry = dbContext.Attach(newOrder);
// Change the state from Unchanged to Modified
newEntry.State = EntityState.Modified;
dbContext.SaveChanges();

You can use dbContext.Update instead of dbContext.Attach to have the state directly modified but this will cause EntityFramework to recursively check all entities referenced by you entity and change their status too.

Sherif Elmetainy
  • 4,034
  • 1
  • 13
  • 22