2016-09-15 2 views
6

J'ai un ancien projet qui utilisait ADO.NET pour accéder au stockage persistant. Actuellement, je veux le migrer vers EF (6.1.3, si c'est important), afin de supporter plusieurs fournisseurs de BD avec un minimum de duplication de code.Comment gérer la propriété Hashtable de l'entité dans la base de données en utilisant EntityFramework

Il y a une entité qui contient Hashtable propriété:

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

Avec ADO.NET, le BinaryFormatter a été utilisé pour convertir cette propriété data au blob, et vice versa:

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

Maintenant, je dois dire à EF comment stocker et récupérer cette propriété.

Qu'ai-je essayé

je pouvais stocker une plus propriété de l'entité:

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

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

Mais cette solution est sujette à des erreurs, et flux de travail spécial avec cette propriété doit être suivie.

P.S. En fait, cette question ne concerne pas uniquement Hashtable, mais toutes les classes personnalisées qui doivent être stockées et retrivées d'une manière spéciale.

+0

Honnêtement, je ne pense pas que ce soit possible. Je ne pense pas qu'il y ait des hooks qui permettraient à la valeur d'être contrainte dans et hors d'une primitive sql. Je regarde attentivement cette question pour voir s'il y a des réponses qui se rapprochent. – Jim

+0

il y a une question similaire http://stackoverflow.com/questions/16135642/in-entity-framework-is-there-a-cleaner-way-of-converting-an-object-type-to-a-str, il utilise un champ de support comme le vôtre. Je ne pense pas qu'il y ait un autre moyen. – Jim

Répondre

5

Voici une solution complète basée sur le answer mentionné ci-dessus.
Je l'ai testé dans linqpad, et ça marche plutôt bien.

Vous n'avez pas besoin d'un flux de travail spécial, car les accesseurs de propriété prennent soin d'enregistrer et de charger la table de hachage en cas de besoin.

Méthode principale

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

Paramètres classe

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

Extensions pour convertir les valeurs

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

Contexte des données

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"); 
    }  
} 
+0

Je me demande si le _Hash va travailler avec le framework d'entité – Monah

+0

@hadi oui, ça marche, je l'ai essayé, enregistré dans un varbinary (max) - qui est ce qui est généré, et le recharger - pas de problème. – Jim

+0

@Jim, c'est une solution plus élégante. – stukselbax