1

How can I properly deserialize POCOs using OrmLite from ServiceStack when I dont know the exact type of the POCO at design-time, but I only get a Type at runtime?

So, something like this:

// Returns the object and can be cast to the correct subobject
public BaseClass ReadObject(Type typeOfObject, int id) 
{
     using (var db = _dbFactory.Open())
     {
         baseObject = db.Select<BaseClass>(typeOfObject, id);  // need something here...
         return baseObject;
     }
}

...
BaseClass bc = ReadObject(someType, 3); // someType = Customer for example

...
class BaseClass { ... }
class Actor : BaseClass { ... }
class Customer : Actor { ... }
class Operator : Actor { ... }

I am persisting Actor and Customer, but when I read them back, I basically only have a Type and possibly the primary key. The .Select<> doesn't work, as I need to know the type at runtime.

I found this SO: Create (and select from) a table dynamically using servicestack ormlite

And if I understand correctly, the only way to fetch a POCO where the type is not known at design-time, is to do a manual SQL statement, like in the answer:

var modelDef = employeeType.GetModelMetadata();
var tableName = db.GetDialectProvider().GetQuotedTableName(modelDef);
var sql = $"SELECT * FROM {tableName}";

var results = db.Select<List<object>>(sql);
var results = db.Select<Dictionary<string,object>>(sql);
var results = db.Select<dynamic>(sql);

However, this does not create any of the classes listed at the top; I need it to typecasted to BaseClass, but it should of course be the correct object (so, 'Customer', 'Operator', etc).

How do I achieve this?

(I have also noted that Mythz has said that using inheritance when persisting data is a bad idea and that he proposes to "flatten" the hierachy. However, I would stick my neck out and say that when dealing with Object Oriented languages, inheritance and polymorphism is unavoidable and not something 99.9% of the coders out there can do away with ;-))

halfer
  • 19,824
  • 17
  • 99
  • 186
Ted
  • 19,727
  • 35
  • 96
  • 154

1 Answers1

3

There is very limited support for using runtime types in OrmLite.

See OrmLite's Untyped API Support which gives you access to some Insert, Update and Delete APIs when dealing with runtime types.

But SELECT's need to specify the concrete Type to select into, i.e. you could select into the base class with:

var results = db.Select<BaseClass>(typeOfObject);

But results would only populate the BaseClass properties.

One potential solution is to use the Dynamic Dictionary ResultSets API to select results into a list of object dictionaries, e.g:

 List<Dictionary<string,object>> results = db.Select<Dictionary<string,object>>(q);

Then use FromObjectDictionary Reflection Utils to convert it to a late-bound Type, e.g:

List<BaseClass> customers = results.Map(x => (BaseClass)x.FromObjectDictionary(customerType));

Quoting sources

(I have also noted that Mythz has said that using inheritance when persisting data is a bad idea and that he proposes to "flatten" the hierachy. However, I would stick my neck out and say that when dealing with Object Oriented languages, inheritance and polymorphism is unavoidable and not something 99.9% of the coders out there can do away with ;-))

If you're going to quote someone, please do so verbatim including a link to the source you're quoting as this is a misrepresentation of what I've said.

You're likely referencing my answer highly recommending against using inheritance and base type properties and unknown late-bound types like interfaces and objects in DTOs. This is to avoid coupling to specific serialization implementations and is a major source of runtime serialization issues which is contra to the goals of creating well-defined interoperable Services. You can avoid this guidance but your services will only work with specific serializer implementations, fail in different languages and have limited metadata support as they'll be unable to statically infer the holes in your Service contract from using unknown types.

But this quote doesn't have anything to do with inheritance in OrmLite where your POCOs can happily have any levels of inheritance. The issue is that you're trying to query an unknown late-bound Type where as OrmLite is a code-first ORM with a Typed API that needs access to the concrete type in order to support its Typed Expression API and populate its typed Results. My answer above includes OrmLite's limited support for untyped access.

mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thanks for input. Would it be hard for you guys to support these cases, if the type info is persisted (using `JsConfig.IncludeTypeInfo = true;`)? If type info is there, then the object could be deserialized completely and casted to the base class, as in my example? – Ted Jul 16 '18 at 18:26
  • @Ted This doesn't have anything to do with Serialization so JsConfig does not apply. OrmLite's a code-first POCO ORM that's primarily accessed by Typed API so has limited support for this use-case. My answer includes the available APIs you could use in addition the the custom SQL examples you've discovered. – mythz Jul 16 '18 at 18:33
  • Alright. I've tested the relection approach, and it works. If I run the same benchmark I have used before (https://stackoverflow.com/questions/51083201/servicestack-benchmark-continued-why-does-persisting-a-simple-complex-to-json), it looks like this: *REFLECTION LOAD took 2641 ms, loaded 10000 customers* and *NORMAL LOAD took 1380 ms, loaded 10000 customers* – Ted Jul 16 '18 at 18:36
  • As per quoting sources: In this post (https://github.com/ServiceStack/Issues/issues/174), persisting audit/log data is discussed, and you say "But honestly I think it's a very fragile approach to rely on inheritance in serialized data" and then refer to the same post as you linked to above in your answer. The discussion was not regarding services, it was merely to persist data for later retrieval. So, when i read it, it definately sounds to me like you suggest to not use inheritance if persisting data...? – Ted Jul 16 '18 at 21:34
  • This is for serialization not for inheritance of OrmLite tables. The issue is trying to use unknown types like base type/interface or object properties which requires emitting serialization-specific info in the serialized payload which has all the issues I've mentioned. – mythz Jul 16 '18 at 21:40
  • So, having a Typed API - I have a hard time understanding how that should work, when there is a central server that handles the db. For example, a client wants to delete an object. The client sends a request, `Delete` with properties `Id=123`. In a Typed API, this is useless. Sending another prop `Type=obj.GetType()` wont work either, as above. The only solution for a client to delete an object, is to have separate "requests", so `DeleteCustomer/DeleteCar/DeleteXXX` and then have *separate implementations* on the server for each type of object, basically massive amounts of boilerplate code? – Ted Jul 16 '18 at 21:54
  • OrmLite's generic Typed API lets you delete any object i.e. `db.Delete(instance)` so I don't see what boilerplate are you referring to? HTTP APIs expects having a separate endpoint for deleting each resource, e.g: `DELETE /customers/1` or `DELETE /car/1`. If you want to delete objects of any type with single implementation then you're circumventing the Type system at which point you can just execute custom Delete SQL with your custom filter which offers the most flexibility otherwise the Untyped API I linked to lets you delete runtime types with a custom filter. – mythz Jul 16 '18 at 22:04
  • 1) `db.Delete(instance)` sends in entire object; sending the entire object that is to be deleted is generally not wanted. (a ref should be enough, so "type + id"), and if so you need to have separate requests for each type of object = boilerplate. Same goes for Insert, modify etc: if you have a `BaseClass` and you don't want to know exactly what sub-type it is, you cannot do it. Trying to insert a `Customer` object that comes in as `BaseClass baseObject` will try to create a table called `BaseClass` but the object is a `Customer`. So, a general `Add(BaseClass obj)` wont work = boilerplate – Ted Jul 16 '18 at 22:14
  • And I agree: the Untyped or custom SQL can be used, of course. But that is sort of what we have today, custom built SQL, serialization etc. Thats why I am trying to investigate a not-custom-built-system, but Typed API assumes you always have the type at compile time, which is a very different approach than have generalised methods. – Ted Jul 16 '18 at 22:16
  • @Ted Have you looked into the Untyped API? It supports Insert/Delete/Update of a late-bound type. If you want to delete an object without the instance you can also run: `db.Delete(typeof(Customer), "Id = @id", new { id = 1 })` – mythz Jul 16 '18 at 22:16
  • The [UntypedApiTests.cs](https://github.com/ServiceStack/ServiceStack.OrmLite/blob/master/tests/ServiceStack.OrmLite.Tests/UntypedApiTests.cs#L53) shows inserting of a base type with the typed API created with `var typedApi = db.CreateTypedApi(subType)`;. – mythz Jul 16 '18 at 22:25
  • Right, I made a mistake when i tested UntypedAPI, I got it to work now. That just might open up some possibilites for me... Thanks for the input, once again! I will try this out in the morning and see where it leads me! – Ted Jul 16 '18 at 22:26
  • I have to revisit this question and ask again, to confirm what I think I already know: Using OrmLite to `Select` all objects that inherits a certain type, is a no-go, as I see it. Say, you have a class `Actor` and subclasses like `Customer`, `Driver`, `Operator` etc etc. If I want to look for any Actor that has IQ > 125, I have to manually write one Select for each subclass, instead of just doing `List list = db.Select(x => x.IQ > 125);`, and the List then contains objects of typ Customer, Driver etc. – Ted Aug 02 '18 at 14:48
  • The typed query needs to use the concrete Type alternatively you could use a Custom SQL filter, there’s several examples of this on OrmLite project page. – mythz Aug 02 '18 at 16:42