Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support immutable entity updates #11457

Open
Tracked by #31238 ...
ajcvickers opened this issue Mar 28, 2018 · 27 comments
Open
Tracked by #31238 ...

Support immutable entity updates #11457

ajcvickers opened this issue Mar 28, 2018 · 27 comments

Comments

@ajcvickers
Copy link
Member

Issue #10703 covers materialization of entities with constructors, which allows immutable entities to be used with EF Core. However, since these entities are immutable it means that they are read-only from an EF perspective--that is, the database row will never get updated. This issue is about enabling patterns in EF that are commonly used with immutable objects, such as creating new instances to represent changes. See also discussion in #3129.

@divega
Copy link
Contributor

divega commented May 2, 2019

Some notes from a recent discussion:

One way EF Core could work with immutable objects is that immutability also extends to the database, so you can insert, and maybe delete, but never ever update any row.

Another approach that seems more pragmatic would be to provide a way to explicitly replace one tracked immutable object with another one with the same key (or if the key is in shadow state, just replace it assuming it has the same key).

It is possible that this coudl be implemented by extending other other functionality that already exists in EF Core in some form or at least are in our roadmap. Here are some ideas/questions:

  • What if replace is handled as just a delete plus an insert?

  • Our ability to make sense of an object deleted and another inserted with the same key in the same unit if work has been evolving recently. We keep finding cases in which we need to improve our topological sort, for example because things like owned entities already can be replaced.

  • What if we could extend this from owned to “root” objects?

  • Then what if EF Core’s update pipeline had the ability to fuse a delete and an insert on the same key into a database update?

  • What happens to the graphs of objects connected to the object replaced and the replacement? Maybe aggregates help rationalize things?

  • Should Attach stop throwing that there is a tracker object with the same key and the object is immutable? Or should we require that something more explicit, like a new Replace method, is called? Right now I am leaning towards the latter, I think currently most times Attach throws not because you want Replace but because you are doing it wrong.

@richardpawson
Copy link

Is there any update on plans for supporting immutable entities?

@ajcvickers
Copy link
Member Author

@richardpawson This issue is in the Backlog milestone. This means that it is not going to happen for the 3.1 release. We will re-assess the backlog following the 3.1 release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

@roji
Copy link
Member

roji commented Nov 5, 2020

With C# 9 records, this becomes a bit more relevant.

Apart from allowing the user to replace a tracked instance with another (Replace or non-throwing Attach), we could imagine a proxy-based system that would leverage that. For example, the With methods on record types could be wrapped so that it calls Replace on the DbContext with the new instance.

@isaacabraham
Copy link

@ajcvickers Please please please if and when you look at this, spend just a tiny bit of time getting this working with F# records too. Under the hood, F# records are just C# classes with a single non-default constructor that has parameters for every field on the record, and getter-only properties for each field.

This is probably one of the main issues stopping people using EF on F# - there's just too much friction at the moment.

@roji
Copy link
Member

roji commented Nov 15, 2020

@isaacabraham we unfortunately probably won't have time to look anytime soon, but it would be useful to know more about how mutation works with F# records. The new C# records have the with pattern, does something similar exist in F#?

@petarrepac
Copy link

@roji
Copy link
Member

roji commented Nov 16, 2020

OK, thanks. It's safe to say that if/when we do get around to supporting updates for immutable types, that would include C# records, and therefore probably also support F# records.

@isaacabraham
Copy link

@roji great. Just to clarify, by "probably", does you mean that either:

  1. You're going to target C# records and hopefully F# records will just magically work
  2. That your team will actually design for supporting both C# and F# record?

Thanks!

@roji
Copy link
Member

roji commented Nov 16, 2020

@isaacabraham it's too early to say exactly how we'll approach this and what scope we'll have, as we don't plan to work on this right away. However, assuming there isn't going to be a major, fundamental difference between F# and C# record support, I indeed expect us to specifically target F# support as well.

@ghost
Copy link

ghost commented Jan 22, 2021

Hello, is there some workaround in EF Core 5.0.1 if I have all entity types immutable? I tried both Tracking and NoTracking, but neither did work.

@roji
Copy link
Member

roji commented Jan 22, 2021

@RichardBlazek for NoTracking queries, things should work well - please open a new issue with a full code sample and error message.

@jtleaming
Copy link

I found this workaround (https://stackoverflow.com/a/57892450) for anyone who is still looking...

            contexts
                .Entry(requests.Find(request.Key))
                .CurrentValues
                .SetValues(request);
            contexts.SaveChanges();

I'm no sure if this has unintended consequences compared to using Update.

@ajcvickers
Copy link
Member Author

@jtleaming I believe what you are doing with that workaround is mutating the values of properties (essentially, but not actually, by Reflection) of what is intended to be an immutable object. So I'm not sure it is really a valid workaround if you are really trying to work with immutable types.

@jtleaming
Copy link

@ajcvickers If the intention of immutable types is to protect unintended data changes from happening in code does the database need to follow the same pattern?

I was calling Update like this originally.

dbSet.Update(request with {
Something = "Changed"
});
context.SaveChanges();

But I was getting ThrowIdentityConflict errors.
Would the intended changes here allow to use DbSet<T>.Update with records?

@ajcvickers
Copy link
Member Author

@jtleaming This issue is about supporting updates for immutable types. How that will happen is not yet defined.

@MattFenner
Copy link

MattFenner commented Apr 27, 2021

My 2c is that the final design should not assume that a call to the with operator means you want to modify an existing row,

e.g. both of these should be supported:

create a copy with updates:

var post = dbContext.Posts.Find(123);
dbContext.Posts.Add(post with { Title = post.Title + " (copy)" };
dbContext.SaveChanges();

update:

var post = dbContext.Posts.Find(123);
dbContext.Posts.Update(post with { Title = "new title" }
dbContext.SaveChanges()

NOTE: I have added the update method here just to illustrate my point, there may be better ways to design this.

@im424
Copy link

im424 commented Jun 11, 2021

As a F# newbie, I googled on this issue for days. To me, AsNoTracking with Linq looks horrible in F#.

While there is not much progress on this issue, a workaround may help. Maybe there are better ways to do this but the compiler and I are ok to detach the record manually.

    let record =  context.Users.Find 1
    context.Entry(record).State <- EntityState.Detached 
    let record' = { record with 
                     Username = "New Name"
                    }
    context.Users.Update record' |> ignore
    context.SaveChanges() |> ignore

@dnperfors
Copy link
Contributor

Currently I am using the following workaround myself:

var post = dbContext.Posts.Find(123);
var updatedPost = post with { Title = "new title" };
dbContext.Posts.Remove(post);
dbContext.Posts.Update(updatedPost);
dbContext.SaveChanges()

I wouldn't mind having a Replace method which supports this:

var post = dbContext.Posts.Find(123);
dbContext.Posts.Replace(post with { Title = "new title" });
dbContext.SaveChanges()

In my opinion this makes it clearer that you know what you are doing and are working with immutable objects/records. The behaviour of the Add and Update methods will then stay the same.

@xperiandri
Copy link

Any plans for EF7?

@roji
Copy link
Member

roji commented Dec 19, 2021

@xperiandri this isn't currently planned: the milestone above shows it still in the backlog.

@xperiandri
Copy link

@roji it would be nice to have it at least in EF7 😉

@ajcvickers
Copy link
Member Author

@xperiandri Please vote (👍) for this issue if it is important to you. It currently has 36 votes, which puts it at 34th place in the list of top voted issues. See The Planning Process for more information on how we decide what to work on.

@abdu292
Copy link

abdu292 commented Aug 14, 2022

@ajcvickers @roji This looks to be having more votes now. Any plan to consider this anytime sooner. Really curious to know if any further discussions are happening on this internally. Thank you!

@ajcvickers
Copy link
Member Author

@abdu292 We'll consider it in the normal planning process. There have been no internal discussions.

@roji
Copy link
Member

roji commented Aug 14, 2022

Note that with the introduction of ExecuteUpdate/ExecuteQuery, you can now perform (untracked) updates/deletes on immutable entity updates. Of course, that means it's up to you to know what needs to be changed, but that's fine in many scenarios (especially disconnected ones).

@abdu292
Copy link

abdu292 commented Aug 15, 2022

@ajcvickers @roji Thank you!
I wasn't aware of this new feature "ExecuteUpdate/ExecuteQuery".

Thanks so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests