22

I have a class that I want to keep meta data for -- there a several interaction scenarios so meta allows me to keep different meta for different interaction types.

class Feed()
{
    Guid FeedId { get; set; }
    ObjectMetaDictionary Meta { get; set; }
}

I would like EF to serialize this ObjectMetaDictionary and store it as a string/VarChar in the database. When I retrieve a record I want it to be deserialized as an ObjectMetaDictionary.

Does EF support this? How can I do it?

I am using Entity Framework Code First.

SOLVED: I provided an answer below that solved my problem. I will accept this answer as soon as SO allows me to.

kingdango
  • 3,979
  • 2
  • 26
  • 43
  • UPDATE: I started using MongoDB shortly after writing this question. With Mongo this question is not even relevant because it's so easy to do. I loved EF when using SQL but I'd rather have Mongo any day of the week -- and based on 14 years experience I'd say mongo performs better for web/apps any day. – kingdango Dec 19 '13 at 13:39

3 Answers3

25

Apparently this is actually quite easy. I was able to get it working thanks to some help from this previous SO answer.

Fluent configuration in OnModelCreating allows us to tell EF what to use as the value property for serializing to the DB and back out again.

Here's my solution:

public class Feed
{
    public virtual Guid FeedId { get; set; }

    public virtual FeedMetaData Meta { get; set; }

    public virtual string Title { get; set; }
    public virtual string Description { get; set; }

}

public class FeedMetaData
{
    public Dictionary<string, string> Data { get; set; }

    public string Serialized
    {
        get { return JsonConvert.SerializeObject(Data); }
        set
        {
            if(string.IsNullOrEmpty(value)) return;

            var metaData = JsonConvert.DeserializeObject<Dictionary<string, string>>(value);

            Data = metaData ?? new Dictionary<string, string>();
        }
    }

    // addl code removed...
}

public class FeedsDbContext : DbContext
{
    public DbSet<Feed> Feeds { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.ComplexType<FeedMetaData>()
                    .Property(p => p.Serialized)
                    .HasColumnName("Meta");
        modelBuilder.ComplexType<FeedMetaData>().Ignore(p => p.Data);

        base.OnModelCreating(modelBuilder);
    }
}
Community
  • 1
  • 1
kingdango
  • 3,979
  • 2
  • 26
  • 43
  • 2
    That is a very slick piece of work, and a technique I've not found discussed elsewhere, and one I would not have easily worked out on my own. Thank you for sharing! – Stephen M. Redd Oct 16 '14 at 01:37
  • Don't forget that EF (at least EF6) append "_Serialized" or whatever you called your property to the field your saving. In the case of a DB first approach, this info might be useful – Guillaume Raymond Mar 29 '15 at 16:53
  • 6
    Or you can just put `[NotMapped]` on the Data property. – particle Jun 08 '15 at 06:43
  • This marked the record as dirty, resulting in a db query. Any ideas how to prevent this? – Bouke Nov 10 '16 at 14:32
  • I wish I had seen this answer before I re-invented the wheel. My slightly different take a https://stackoverflow.com/a/37207034/492 – CAD bloke Jun 27 '17 at 05:19
1

Have your Entity Framework object be simple and have a String property for the column in the database.

class Feed()
{
    Guid FeedId { get; set; }
    String Meta { get; set; }
}

Create methods that save and load the property as such: (it's been a while since I've used EF, so i'm not sure if you can create a transient property with these getter/setters or if you need something else)

//Reading from string column Meta
(ObjectMetaDictionary) XamlServices.Load(new StringReader(someFeed.Meta));

//Saving to string column Meta
someFeed.Meta = XamlServices.Save(value);

This brings another whole issue to your project though. Changing your ObjectMetaDictionary might cause it to not deserialize from the database correctly. Your ObjectMetaDictionary becomes essentially part of your database schema and you will need to handle versioning or changes accordingly.

Daniel Moses
  • 5,872
  • 26
  • 39
  • Yes, this is essentially how I have it now but this design is weak because it means I always have to have a superclass to handle the serialization/deserialization. I'm looking to be able to use the Feed.Meta AS an ObjectMetaDictionary. – kingdango Feb 08 '13 at 19:42
  • What kind of EF mapping are you doing, code first, edmx first, ...? – Daniel Moses Feb 08 '13 at 19:57
  • 1
    Code First, I updated the question to indicate this in case anyone misses the tags I set. – kingdango Feb 09 '13 at 03:05
1

The feature HasConversion saved my life. Unlock all json formats! Enjoy it!

public partial class Feed
{
    public int Id { get; set; }

    //this column will be mapped to a "nvarchar(max)" column. perfect!
    public Dictionary<string, string> Meta { get; set; }
}

public class FeedsDbContext : DbContext
{

    public FeedsDbContext(DbContextOptions<FeedsDbContext> options)
        : base(options)
    {
    }

    public virtual DbSet<Feed> Feed { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<Feed>(entity =>
        {
            entity.Property(p => p.Meta).HasConversion(
                x => JsonConvert.SerializeObject(x) //convert TO a json string
                , x => JsonConvert.DeserializeObject<Dictionary<string, string>>(x) //convert FROM a json string
            );
        });
    }

}
Dongdong
  • 2,208
  • 19
  • 28