3

Consider this example:

Database (similar to Stackoverflow) has a table with questions and answers in the same table distinguishable by PostType field. I would like to get back a list of lastest posts be it questions or answers.

As questions and answers share common properties, both classes inherit from an abstract class ContentBase. They only add few additional fields that are relevant for each type.

How do I get a list of content items cast to IList<ContentBase> while individual items are of a concrete type Question or Answer based on the criteria field.

public abstract class ContentBase
{
    public int Id { get; set; }

    public string Title { get; set; }

    public User Author { get; set; }

    public abstract ContentType Type { get; }

    ...
}

public class Question : ContentBase
{
    public override ContentType Type
    {
        get { return ContentType.Question; }
    }

    ...
}

public class Answer: ContentBase
{
    public override ContentType Type
    {
        get { return ContentType.Answer; }
    }

    ...
}

My repository would then have a call:

IList<ContentBase> result = db.GetLatest();

I'm using PetaPoco/NPoco, but I suppose the same problem would arise if one would use Dapper.

Question

How does one instruct DAL to instantiate correct concrete types based on particular field's value (ContentType in this case)?

Explanation

I should be doing something along these lines:

db.Fetch<ContentBase, User>(...)

But I can't because ContentBase is an abstract class. If I change this line to:

db.Fetch<dynamic, User>(...)

it still won't work, because I get an InvalidOperationException from NPoco saying Can't auto join User.

I suppose the only way to do this is to create a new class inherited from PocoData and provide my own implementation of GetFactory delegate. I think. I don't think there's any other extensibility point where concrete POCOs get instantiated and I'm not sure whether I should do it this way and how since I'm handling abstract ancestor class.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • if you use `var result = db.Fetch("Select * from ContentBase .."` you will get Questions... but if you want a list with Question and Answers, it's not the DAL job to separate them. – Eduardo Molteni Mar 31 '14 at 21:42
  • @EduardoMolteni: Exactly. DAL should not be aware of different content types hence should just return appropriate instances. You're implying I should be doing **N** DB calls, one per each type and combine them? – Robert Koritnik Mar 31 '14 at 22:09
  • No no no. Do 1 DB call and separate or cast using LINQ etc – Eduardo Molteni Mar 31 '14 at 23:07
  • @EduardoMolteni: that's exactly what I'm trying to do. I'm getting questions and answers from the DB simultaneously, but my result should include correct type (they both inherit from `ContentBase`). I just don't know how to do it? How should I do that? Can you write an answer with some code so it becomes more clear what you're trying to tell me? Let me update my question a bit... – Robert Koritnik Mar 31 '14 at 23:42
  • Sorry, I miss the `abstract` part, now I see your point. – Eduardo Molteni Apr 01 '14 at 13:40

1 Answers1

2

Not sure I understand this right.

  1. Could you solve this by having a base class which takes the integer value of the Enum, load Q&A's into one collection, and then in memory split it beteween Q'a and A's? This seems to be much simpler.

  2. Maybe getting Q&A's separtely:

    var s = @"select q.Id, q.Title, q.ContentType as Type , q.Author as Name from QA q where q.ContentType = @0;";
    var q = db.Fetch<Question,User>(s,1);
    var a = db.Fetch<Answer, User>(s,2);
    
  3. Could you solve this by using multiple result sets?

    var sql  = @"select * from QA where contenttype = 1; select * from qa where
    contenttype  =1;";
    var com = db.FetchMultiple<Question,Answer>(sql);
    com.Dump();
    

NOTE: My db table used nvarchar for User, and made the string in your base for User.

Created an enum for ContentType

void Main()
{
    var sql  = @"select * from QA where contenttype = 1; select * from QA where contenttype  = 2;";
    var com = db.FetchMultiple<Question, Answer>(sql);

    com.Dump();
}

Other methods and types used here

public enum ContentType
{
    Question,
    Answer
}

public abstract class ContentBase
{
    public int Id { get; set; }

    public string Title { get; set; }

    public string Author { get; set; }

    public abstract ContentType Type { get; }
}

public class Question : ContentBase
{
    public override ContentType Type
    {
        get { return ContentType.Question; }
    }
}

public class Answer: ContentBase
{
    public override ContentType Type
    {
        get { return ContentType.Answer; }
    }
}
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
Rigalo
  • 21
  • 2
  • Basically I don't need slitting. I just need an intermix of appropriate classes based on database records. **#1.** Yes I currently have a separate DAO type inherited from `ContentBase` that represents any content and I'm later changing item types using Linq. This way I'm getting results in correct mixed order without getting separate types separately and mixing them in higher layer. **#2 & #3** These work similarly except that we either use N calls (N content types) or one multi call. They're also very similar to #1 except that we omit the need for a `AnyContent` class. – Robert Koritnik Apr 01 '14 at 09:35
  • Of all 3, #1 seems to be the most appropriate because we don't have to mix content items on upper layer. Hence I implemented it for now. – Robert Koritnik Apr 01 '14 at 09:36
  • Reference: NPoco GitHub raised issue: [Add support for abstract class/interface instantiation using provided factory/mapper or similar](https://github.com/schotime/NPoco/issues/97) – Robert Koritnik Apr 01 '14 at 09:36