0

I'm trying to create a way to make an unique search into the database and build the right object for my needs. I mean, I use a SQL query that returns me a lot of rows and then I build the collections based on that database rows. E.g.:

We have a table called People and another table called Phones.

Let's suppose that this is my SQL query and will return the following below:

SELECT 
   P.[Id], P.[Name], PH.[PhoneNumber]
FROM
   [dbo].[People] P
INNER JOIN 
   [dbo].[Phones] PH ON PH.[Person] = P.[Id]

And that's the results returned:

1   NICOLAS    (123)123-1234
1   NICOLAS    (235)235-2356

So, my class will be:

public interface IModel {
    void CastFromReader(IDataReader reader);
}

public class PhoneModel : IModel {
    public string PhoneNumber { get; set; }

    public PhoneModel() {  }
    public PhoneModel(IDataReader reader) : this() {
         CastFromReader(reader);
    }

    public void CastFromReader(IDataReader reader) {
        PhoneNumber = (string) reader["PhoneNumber"];
    }
}

public class PersonModel : IModel {
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<PhoneModel> Phones { get; set; }

    public PersonModel() {  
        Phones = new List<PhoneModel>();
    }

    public PersonModel(IDataReader reader) : this() {
         CastFromReader(reader);
    }

    public void CastFromReader(IDataReader reader) {
        Id = Convert.ToInt32(reader["Id"]);
        Name = (string) reader["Name"];

        var phone = new PhoneModel();
        phone.CastFromReader(reader);
        Phones.Add(phone);

        // or
        Phones.Add(new PhoneModel {
            PhoneNumber = (string) reader["PhomeNumber"]
        });
    }
}

This code will generate a PersonModel object with two phone numbers. That's good so far.

However, I'm struggling to make some good way to deal when I want to manage more tables with this process.

Let's suppose, then, I have a new table called Appointments. It stores the user's appointments to the schedule.

So, adding this table to the query, the result will be:

1   NICOLAS   (123)123-1234    17/09/2014
1   NICOLAS   (123)123-1234    19/09/2014
1   NICOLAS   (123)123-1234    27/09/2014
1   NICOLAS   (235)235-2356    17/09/2014
1   NICOLAS   (235)235-2356    19/09/2014
1   NICOLAS   (235)235-2356    17/09/2014

As you guys can see, the problem is to manage the phones and the appointments this way. Do you can think in anything that could solve this issue?

Thank you all for the opinions!

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kiwanax
  • 1,265
  • 1
  • 22
  • 41
  • What *is* the question? The question should follow *from* a defined problem. – user2864740 Sep 17 '14 at 20:05
  • 1
    Well, perhaps you should investigate how to use an ORM. This is usually their bread and butter – Steve Sep 17 '14 at 20:11
  • This question is to gather opinions about this approach. Can not I ask for opinions here, just problems? – Kiwanax Sep 18 '14 at 11:08
  • @Steve, in this case I HAVE TO use ADO.NET, it's not possible to use an ORM, so I'm struggling to find the better way to work, searching for specific columns for many tables and straight translating in DTOs. – Kiwanax Sep 18 '14 at 11:09
  • 2
    Well, that's a decision up to you. I am only suggesting to not reinvent the wheel. For example a lightweight ORM like [Dapper](https://github.com/StackExchange/dapper-dot-net) is more than capable to satisfy your requisite and it is just a .cs file to add to your projects and IT USES ADO.NET. Performance are very good also. – Steve Sep 18 '14 at 12:07
  • I suggest writing down strongly typed classes to handle each case (with some optional properties), it will come handy in the long run. A similar situation, but different use-case: http://stackoverflow.com/questions/15211437/how-to-read-dynamic-properties-from-database – nawfal Aug 01 '15 at 11:31

2 Answers2

1

If you want to manually write a data type for each combination of columns resulting from your queries, then you have a lot of work to do, and you will end up with lots of very similar, but slightly different classes that are hard to name. Note also that these data types should not be treated as something more than what they are: Data Transfer Objects (DTOs). They are not real domain objects with domain-specific behaviour; they should just contain and transport data, nothing else.

What follows are two suggestions, or ideas. I will only scratch at the surface here and not go into too many details; since you haven't asked a very specific question, I won't provide a very specific answer.

1. A better approach might be to determine what domain entity types you've got (e.g. Person, Appointment) and what domain value types you have (e.g. Phone Number), and then build an object model from that:

struct PhoneNumber { … }

partial interface Person
{
    int Id { get; }
    string Name { get; }
    PhoneNumber PhoneNumber { get; }
}

partial interface Appointment
{
    DateTime Date { get; }
    Person[] Participants { get; }
}

and then have your database code map to these. If, for example, some query returns a Person Id, Person Name, Phone Number, and an Appointment Date, then each attribute will have to be put into the correct entity type, and they will have to be linked together (e.g. via Participants) correctly. Quite a bit of work. Look into LINQ to SQL, Entity Framework, NHibernate or any other ORM if you don't want to do this manually. If your database model and your domain model are too different, even these tools might not be able to make the translation.

2. If you want to hand-code your data query layer that transforms data into a domain model, you might want to set up your queries in such a way that if they return one attribute A of entity X, and entity X has other attributes B, C, and D, then the query should also return these, such that you can always build a complete domain object from the query result. For example, if a query returned a Person Id and a Person Phone Number, but not the Person Name, you could not build Person objects (as defined above) from the query because the name is missing.

This second suggestion will at least partially save you from having to define lots of very similar DTO types (one per attribute combination). This way, you can have a DTO for a Person record, another for a Phone Number record, another for an Appointment record, perhaps (if needed) another for a combination of Person and Phone Number; but you won't need to distinguish between types such as PersonWithAllAttributes, PersonWithIdButWithoutNameOrPhoneNumber, PersonWithoutIdButWithPhoneNumber, etc. You'll just have Person containing all attributes.

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • thanks for your answer. What I'm trying to do is generate the DTO object straight from a SQL query, instead to create the POCO classes and then translate them to a DTO, you know? In some cases, it could be better for my scenario. So, I'm struggling to translate that query above into the DTOs objects. – Kiwanax Sep 18 '14 at 11:11
  • Basic fact: You cannot create an object without first having created that object's type. Some ideas for you to follow up: **1.** Define interfaces or abstract base classes and build a framework that implements them automatically at runtime, by reflecting over the SQL DB schema and using `System.Reflection.Emit`. **2.** Use the DLR (Dynamic Language Runtime), i.e. `dynamic` and e.g. `ExpandoObject`. But you're going to lose compile-time IntelliSense support this way. **3.** Automatically generate types at compile-time, e.g. using T4 templating. **4.** Use an ORM. – stakx - no longer contributing Sep 18 '14 at 12:06
  • Speaking of #2, you could derive a new type from `DynamicObject` that automatically matches (late-bound) properties to a `IDataRecord`'s field of the same name. – stakx - no longer contributing Sep 18 '14 at 12:08
1

You cannot transfer your query result to strongly typed objects without first defining these objects' types. If you want to keep query data in memory, I recommend that you transfer it into objects of a previously defined type at some point.

What follows is therefore not something that I would actually recommend doing. But I want to demonstrate to you a possibility. Judge for yourself.

As I suggested in a previous comment, you can mimick strongly typed DTOs using the Dynamic Language Runtime (DLR), which has become available with .NET 4.

Here is an example for a custom DynamicObject type that provides a seemingly strongly-typed façade for a IDataReader.

using System.Data;
using System.Dynamic; // needs assembly references to System.Core & Microsoft.CSharp
using System.Linq;

public static class DataReaderExtensions
{
    public static dynamic AsDynamic(this IDataReader reader)
    {
        return new DynamicDataReader(reader);
    }

    private sealed class DynamicDataReader : DynamicObject
    {
        public DynamicDataReader(IDataReader reader)
        {
            this.reader = reader;
        }

        private readonly IDataReader reader;

        // this method gets called for late-bound member (e.g. property) access   
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            int index = reader.GetOrdinal(binder.Name);
            result = index >= 0 ? reader.GetValue(index) : null;
            return index >= 0;
        }
    }
}

Then you can use it like this:

using (IDataReader reader = someSqlCommand.ExecuteReader(…))
{
    dynamic current = reader.AsDynamic(); // façade representing the current record
    while (reader.Read())
    {
        // the magic will happen in the following two lines:
        int id = current.Id; // = reader.GetInt32(reader.GetOrdinal("Id"))
        string name = current.Name; // = reader.GetString(reader.GetOrdinal("Name"))
        …
    }
}

But beware, with this implementation, all you get is a façade for the current record. If you want to keep data of several records in memory, this implementation won't help a lot. For that purpose, you could look into several further possibilities:

  • Use anonymous objects: cachedRecords.Add(new { current.Id, current.Name });. This is only any good if you access the cachedRecords in the same method where you build it, because the anonymous type used will not be usable outside of the method.

  • Cache current's data in an ExpandoObject.

Community
  • 1
  • 1
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • I liked this dynamic approach you introduce here. I'll check it deeper and I'll see if it works with my scenario. The cache thing is an interesting stuff as well. Thanks! – Kiwanax Sep 18 '14 at 13:24