如何使用 EntityFramework 处理数据库中实体的哈希表 属性

How to deal with Hashtable property of entity in database using EntityFramework

我有一个旧项目使用 ADO.NET 访问持久存储。目前,我想将它迁移到 EF(6.1.3,如果重要的话),以便以最少的代码重复支持多个数据库提供程序。

有一个实体,其中包含 Hashtable 属性:

public class Record
{
    ...
    public Hashtable data { get; set; }
}

使用 ADO.NET,BinaryFormatter 用于将此 data 属性 转换为 BLOB,反之亦然:

using (MemoryStream stream = new MemoryStream())
{
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(stream, data);
    result = stream.GetBuffer();
}

//----------

using (MemoryStream serializationStream = new MemoryStream((byte[])value))
{
    BinaryFormatter formatter = new BinaryFormatter();
    result = (Hashtable)formatter.Deserialize(serializationStream);
}

现在我需要告诉 EF 它应该如何存储和检索 属性。

我尝试了什么

我可以在实体中再存储一个 属性:

public class Record
{
    public byte[] dataRaw { get; set; }

    [NotMapped]
    public Hashtable data {
        get {/*deserialize dataRaw */ }
        set { /*Serialize to dataRaw*/}
    }
}

但是这个解决方案容易出错,必须遵循属性的特殊工作流程。

P.S. 其实这个问题不仅仅是关于哈希表,而是关于每个必须以特殊方式存储和检索的自定义 class .

这是基于我上面提到的 answer 的完整解决方案。
我已经在 linqpad 中对其进行了测试,并且效果很好。

您不需要特殊的工作流程,因为 属性 访问器会在需要时负责保存和加载哈希 table。

主要方法

void Main()
{
    using (var ctx = new TestContext())
    {
        var hash = new Hashtable();
        hash.Add("A", "A");
        ctx.Settings.Add(new Settings { Hash = hash });
        ctx.SaveChanges();

        // load them up...
        ctx.Settings.ToArray().Select(_ => _.Hash).Dump();
    }
}

设置Class

public class Settings
{
    // a primary key is necessary.
    public int Id { get; set; }

    [NotMapped]
    public Hashtable Hash
    {
        get;
        set;
    }

    // the backing field can be protected, this helps 'hide' it.
    protected virtual byte[] _Hash
    {
        get
        {
            return Hash.ToBinary();
        }
        set     
        {
            Hash = value.FromBinary<Hashtable>();
        }
    }
}

转换值的扩展

public static class Extensions
{

    public static BinaryPropertyConfiguration BinaryProperty<T>(
        this EntityTypeConfiguration<T> mapper,
        String propertyName) where T : class
    {
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;

        PropertyInfo pi = type.GetProperty(propertyName,
            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        expr = Expression.Property(expr, pi);

        LambdaExpression lambda = Expression.Lambda(expr, arg);

        Expression<Func<T, byte[]>> expression = (Expression<Func<T, byte[]>>)lambda;
        return mapper.Property(expression);
    }

    public static byte[] ToBinary<T>(this T instance)
    {
        if (instance == null)
            return null;

        using (var stream = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, instance);
            return stream.ToArray();
        }
    }

    public static T FromBinary<T>(this byte[] buffer)
    {
        if (buffer == null)
            return default(T);

        using (var stream = new MemoryStream(buffer, false))
        {
            var formatter = new BinaryFormatter();
            var instance = formatter.Deserialize(stream);
            return (T)instance;
        }
    }
}

数据上下文

public class TestContext : DbContext
{
    public DbSet<Settings> Settings { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder
            .Entity<Settings>()
            .BinaryProperty("_Hash")
            .HasColumnName("Hashtable");
    }       
}