Как работать со свойством Hashtable объекта в базе данных с помощью 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. На самом деле этот вопрос касается не только Hashtable, но и каждого пользовательского класса, который должен храниться и извлекаться особым образом.


person stukselbax    schedule 15.09.2016    source источник
comment
Я честно не думаю, что это возможно. Я не думаю, что есть какие-либо ловушки, которые позволили бы принуждать значение к примитиву sql и из него. Я с нетерпением слежу за этим вопросом, чтобы увидеть, есть ли какие-либо ответы, которые приближаются.   -  person Jim    schedule 15.09.2016
comment
есть подобный вопрос stackoverflow.com/questions/16135642/, он использует резервное поле, подобное вашему. Я не думаю, что есть другой способ.   -  person Jim    schedule 15.09.2016


Ответы (1)


Вот полное решение, основанное на ответ Я упоминал выше.
Я протестировал его в linqpad, и он работает довольно хорошо.

Вам не нужен специальный рабочий процесс, так как методы доступа к свойствам позаботятся о сохранении и загрузке хэш-таблицы при необходимости.

Основной метод

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();
    }
}

Класс настроек

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");
    }       
}
person Jim    schedule 15.09.2016
comment
@hadi да, это работает, я пробовал, сохранил его в varbinary (max) - это то, что сгенерировано, и загрузил его обратно - без проблем. - person Jim; 15.09.2016
comment
@ Джим, это более элегантное решение. - person stukselbax; 15.09.2016
comment
@stukselbax, если это решение, то вы можете его принять, нет? - person Sampath; 15.09.2016
comment
@sampath - спасибо за это, но, возможно, ответа не хватает? - person Jim; 16.09.2016
comment
@Sampath Я хотел бы немного подождать, прежде чем принять. Это решает проблему, но я хочу подождать, пока другие придут на вечеринку. - person stukselbax; 16.09.2016
comment
К вашему сведению: я опубликовал проблему на GitHub, и оказалось, что эта функция будет реализовано позже, но только для нового EF. - person stukselbax; 13.10.2016