ServiceStack / Issues

Issue Tracker for the commercial versions of ServiceStack
11 stars 6 forks source link

OrmLite handling of inheritance with blobbed objects #174

Closed wastaz closed 9 years ago

wastaz commented 9 years ago

I'm not sure if this is a supported use case, so feel free to tell me that it's not if that is the case. :) Gist showing the problem available here: https://gist.github.com/wastaz/2dc870142679ea49797c

I have a table sort of like the gist where I store events. There are a lot of different events in the system and the only real reason that I'm saving them is for audit logging so no use in searching for them. This seems like a great place to use OrmLite object blobbing, however I do not want to use a separate column per event type (that would be unuseable). My events share an abstract base class containing some very basic information (but if need be I could have them implement an interface instead).

When running the code in the gist you will see the problem, when you read the events back from the database the Event field will always be null. I'm guessing that this is because the serializer can't figure out which class to deserialize the event back into because when I look at the data store I can't find any type information (which is stored when serializing as json instead of the default jsv-serialization). Currently I'm working around this problem by serializing manually into json and storing that, but I'd prefer to not have to maintain that code myself and just be able to take advantage of OrmLite's blobbing support instead.

One possibility might be to make it possible to configure if you want the blobbing to be done via JSV, JSON, XML, etc?

mythz commented 9 years ago

You will need to tell the underlying text serializer to serialize the type information with the serialized data, e.g:

JsConfig<FooEvent>.IncludeTypeInfo = true;
JsConfig<BarEvent>.IncludeTypeInfo = true;

Which will now work as expected:

using (var db = OpenDbConnection())
{
    db.CreateTableIfNotExists<EventOccurance>();

    db.Insert(new EventOccurance
    {
        OccuredAt = DateTime.Now,
        Event = new FooEvent { EventNumber = 1, Foo = "Baz" },
    });
    db.Insert(new EventOccurance
    {
        OccuredAt = DateTime.Now,
        Event = new BarEvent { EventNumber = 2, Bar = 3.14 },
    });

    var events = db.Select<EventOccurance>();
    events.Each(e => e.PrintDump());
}

But honestly I think it's a very fragile approach to rely on inheritance in serialized data for much of the reasons I've mentioned earlier.

i.e. here's what the blob data now needs to look like:

{
    __type: "ServiceStack.OrmLite.Tests.FooEvent, ServiceStack.OrmLite.Tests",
    Foo: Baz,
    EventNumber: 1
}

Which apart from bloating the database, means that even a simple refactor into an alternative namespace would break your code/data. My preference is to generalize and flatten out the heirachy when persisting data, e.g:

public class Event
{
    public string Type { get; set; }
    public int EventNumber { get; set; }
    public string Foo { get; set; }
    public double Bar { get; set; }
}
wastaz commented 9 years ago

Ah! JsConfig! Of course, I was searching for it all around the ormlite namespaces...

I know it's a fragile approach. Sadly I can't really flatten the data. In this small example flattening would have worked, but in my real life application I have a large amount of different types with wildly different (sometimes hierarchical) data. Flattening everything into a single table would make it impossible to work with, and one table per type would become unwieldy in the other direction instead. I would prefer to save this data in a schemaless datastore like GetEventStore/Redis/Mongo/Raven instead - however I've gotten a no-go on introducing a secondary data store for this purpose. :(

I can live with the database bloat (even though it doesn't make me happy) - and I don't think knocks on wood that we need to refactor namespaces/typenames for these object. Of course - I've been wrong before and I'm probably going to be wrong in this case as well...

Thanks for the advice, I'll try to revisit the idea figuring out an alternative approach to saving this data - but for right now I'll probably use the JsConfig-trick in the mean time. :)