-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Looking for suggestions for marking entities with a property using custom serialization logic as dirty. #23789
Comments
You must implement System.ComponentModel.INotifyPropertyChanged interface for your Here is an example: //Entities:
...
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class BindableBase : INotifyPropertyChanged
{
protected virtual bool SetProperty<T>(ref T item, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(item, value)) return false;
item = value;
OnPropertyChanged(propertyName);
return true;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class JsonObject : BindableBase, INotifyPropertyChanged
{
private string stringJsonProperty;
private int intJsonProperty;
public int IntJsonProperty { get => intJsonProperty; set => SetProperty(ref intJsonProperty, value); }
public string StringJsonProperty { get => stringJsonProperty; set => SetProperty(ref stringJsonProperty, value); }
}
public class MyEntity : BindableBase, INotifyPropertyChanged
{
private JsonObject json;
private int id;
public int Id { get => id; set => SetProperty(ref id, value); }
public JsonObject Json
{
get => json;
set
{
if (json != null)
{
json.PropertyChanged -= JsonChanged;
}
SetProperty(ref json, value);
if (json != null)
{
json.PropertyChanged += JsonChanged;
}
}
}
private void JsonChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(Json));
}
}
...
//DbContext:
...
using Newtonsoft.Json;
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>()
.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications)
.Property(x => x.Json)
.UsePropertyAccessMode(PropertyAccessMode.Property)
.HasConversion(
obj => JsonConvert.SerializeObject(obj),
json => JsonConvert.DeserializeObject<JsonEntity>(json)
);
}
}
... We can use the following code to change values inside the JsonObject instance: var e = db.MyEntities.FirstOrDefault();
e.Json.StringJsonProperty = "I like ef core!" ;
db.SaveChanges(); |
Is there any way of doing this without having the component model namespace bleed into my domain and having to write all that extra code in my models themselves? Or I guess alternatively, leverage something similar to the change tracker? I don't have to implement any interfaces on my regular domain objects for EF to notice changes to their regular properties... |
@atrauzzi You need to implement a value comparer, as described here: https://docs.microsoft.com/en-us/ef/core/modeling/value-comparers For example: modelBuilder
.Entity<MyData>()
.Property(configurable => configurable.Configuration)
.HasConversion(
configuration => JsonSerializer.Serialize(configuration, null),
json => JsonSerializer.Deserialize<Discriminable>(json, null),
new ValueComparer<IDiscriminable>(
(l, r) => l.Equals(r),
v => v.GetHashCode(),
v => (IDiscriminable)new Discriminable())); However, your code is also trying to map the (unusually named) interface |
Ah yes, my apologies, that should have been the concrete entity type. |
On a separate note @ajcvickers -- Is there any way that EF could make its change tracking mechanism available for people to tap into instead of requiring them to write what will ultimately be a shabby duplication of EF's own change tracking logic? My assumption is that somewhere in the codebase of EF, there's something that's really good at just keeping a list of objects to watch and noticing when any of their properties change. I'd love to have access to that functionality, which was largely my motivation for opening #23790, separate to this. Taking #23790 a little further, the overall idea was, to get the same change tracking semantics for my sub-object as what EF already does in a handy way. I just want it to trigger a dirty state on the containing entity, not the tracked non-entity object itself... |
@atrauzzi Value converters and comparers are the way to hook into this mechanism. That being said, you could replace the |
Well, I'm not dispiting how to hook that mechamism, let's not focus too hard on that for the rest of this. I understand that it exists and will give me a very convoluted path to what I want. But now I'm trying to make a case for an opportunity EF has here: I'm not looking to replace the change detector. I just want to use the one that's already there instead of incurring the overhead and maintenance of any change tracker I'd have to write myself (and probably get wrong) on top of EF's battle-tested change tracker. I don't think the objective of #23790 is outside of EF's scope either considering that many people have and will continue to come to need this functionality. Especially with the rise in popularity of JSON columns. At the core of all this is a two-part feature request:
Genuinely, I don't think I'm being biased by my own need when I say: People would probably find this quite useful. 🤞 |
This is what value converters and value comparers do.
I really don't think this is necessary, but maybe I don't understand what you mean by a "tracked non-entity object ". To me, this can only mean an object referenced in a property of an entity. Which then means again that value converters and value comparers are the way to do this. If the comparer reports that the non-tracked object has changed, then the entity is marked as modified. |
Let me answer in the other thread... |
Cross posting my StackOverflow question here, but I feel like all the experts who will be able to answer my question live 'round these parts. 😉
I have the following:
I'm running into an issue right now where I can save and load data fine, so long as I manually force the entity to be marked as dirty. Either by simply scanning for all entities of type
Configurable
and then manually marking them as dirty, or by telling the change tracker in my procedural code that they're dirty - somewhat painstakingly.This is necessary because the values inside instances of
Discriminable
aren't noticed by the change tracker and as such, won't queue the containingConfigurable
instances for updates by marking them as dirty.Sadly, my current approach results in a problem in some scenarios, like when I add
MyData
toOtherData
and then try to save it. Because theMyData
instance is already in the change tracker, EF attempts to update twice and I end up getting this exception:My question at this point: Is there any way for me to lean on the change tracker to notice when values inside of my
Discriminable
instances have changed? Are there potentially any other techniques that I can use to avoid having to add manual state tracking throughout my procedural code?The text was updated successfully, but these errors were encountered: