简体   繁体   English

防止 EF Core 生成外键

[英]Prevent EF Core from generating foreign key

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.我想创建一个实体,它保留对自身克隆的引用,并且该克隆实际上会在保存到数据库之前被序列化为 json。

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.问题在于,因为 EF 知道Foo (它在上下文中被引用并且有一个流畅的配置文件),所以它创建了一个外键。

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).我已经使用自定义转换器成功序列化了另一种类型,但是 EF 不知道其他类型(上下文中没有引用,也没有流畅的配置文件)。

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.问题在于,因为 EF 知道 Foo(它在上下文中被引用并且有一个流畅的配置文件),所以它创建了一个外键。

You can create special type - wrapper - EF will not know about.您可以创建特殊类型 - 包装器 - EF 不会知道。

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: 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:此外,您还可以为此类实体指定一些基本的 EntityTypeConfiguration:

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:有几个缺点:

  1. Need to mark Snapshot property with [JsonIgnore]需要用[JsonIgnore]标记Snapshot属性
  2. No any constraints for T in IHasSnapshot<T> , so you can write other than Foo ( class Foo: IHasSnapshot<Bar> ), but it is not critical. IHasSnapshot<T>中的 T 没有任何约束,因此您可以编写除Foo以外的内容( class Foo: IHasSnapshot<Bar> ),但这并不重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM