I'd like to create an entity that keeps a reference to a clone of itself, and that clone would actually be serialised to json before saving to database.
public class Foo
{
public string StringProperty { get; set; }
public int IntProperty { get; set; }
public Foo Snapshot { get; set; }
}
public class FooConfiguration : IEntityTypeConfiguration< Foo >
{
public virtual void Configure( EntityTypeBuilder< Foo > builder )
{
builder.Property( e => e.StringProperty );
builder.Property( e => e.IntProperty )
.IsRequired();
builder.Property( e => e.Snapshot )
.HasConversion( new FooToJsonConverter() );
}
}
The problem is that because EF knows about Foo
(it is referenced in the context and there is a fluent configuration file for it), it creates a foreign key.
Even when I try to ignore it with
builder.Ignore( e => e.Snapshot )
I have successfully serialised another type with a custom converter, but that other type is unknown to EF (no reference in the context and no fluent configuration file).
Is there a way to achieve this?
The problem is that because EF knows about Foo (it is referenced in the context and there is a fluent configuration file for it), it creates a foreign key.
You can create special type - wrapper - EF will not know about.
public class SnapshotWrapper<T>
{
public T? Snapshot { get; set; }
public string Serialize() => JsonSerializer.Serialize(Snapshot);
public static SnapshotWrapper<T> CreateFromJson(string json)
{
if (json == null)
throw new ArgumentNullException(nameof(json));
return new SnapshotWrapper<T>
{
Snapshot = JsonSerializer.Deserialize<T>(json)
};
}
}
Then define interface to identify entities with snapshots:
public interface IHasSnapshot<T>
{
SnapshotWrapper<T> Snapshot { get; }
}
Example for Foo:
public class Foo : IHasSnapshot<Foo>
{
public Foo(int id, string name, int age) : this()
{
FooId = id;
FooName = name ?? throw new ArgumentNullException(nameof(name));
FooAge = age;
}
//to follow DRY principle
//you can specify some SnapshotBase base type for doing this
public Foo()
{
Snapshot = new SnapshotWrapper<Foo>
{
Snapshot = this
};
}
public int FooId { get; set; }
public string? FooName { get; set; }
public int FooAge { get; set; }
[JsonIgnore]
public SnapshotWrapper<Foo> Snapshot { get; set; }
}
Moreover, you can else specify some base EntityTypeConfiguration for such entities:
public abstract class WithSnapshotEntityTypeConfigurationBase<T> : IEntityTypeConfiguration<T>
where T : class, IHasSnapshot<T>
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.Property(x => x.Snapshot).HasConversion(
x => x.Serialize(),
str => SnapshotWrapper<T>.CreateFromJson(str));
}
}
public class FooConfiguration : WithSnapshotEntityTypeConfigurationBase<Foo>
{
public override void Configure(EntityTypeBuilder<Foo> builder)
{
base.Configure(builder);
builder.HasKey(x => x.FooId);
builder.Property(x => x.FooName).IsRequired().HasMaxLength(200);
builder.HasData(
new Foo(1, "John Doe", 30),
new Foo(2, "Jane Smith", 20),
new Foo(3, "Billy The Drunken", 40),
new Foo(4, "James Webb", 60),
new Foo(5, "Old president", 40));
}
}
This works. A couple of downsides:
Snapshot
property with [JsonIgnore]
IHasSnapshot<T>
, so you can write other than Foo
( class Foo: IHasSnapshot<Bar>
), but it is not critical.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.