0

I would like to be able to pass a Tuple to the following method, however I can't since I have to instantiate it.

    public List<T> Select<T>(string sql, DbCommand command) where T : new()
    {
        DbDataReader dataReader = null;
        List<T> ls = new List<T>();
        Type tType = typeof(T);
        T t;
        PropertyInfo prop = null;
        string[] propertiesNames = GetPropertiesNamesFromSQL(sql);

        try
        {
            dataReader = command.ExecuteReader();
            if (dataReader == null || !dataReader.HasRows) return ls;

            while (dataReader.Read())
            {
                t = new T();

                for (int i = 0; i < dataReader.FieldCount; i++)
                {
                    prop = tType.GetProperty(propertiesNames[i]);

                    prop.SetValue(t, dataReader.GetValue(i));
                }

                ls.Add(t);
            }
        }
        catch (Exception){  }
        finally
        {
            if (dataReader != null) dataReader.Close();
        }

        return ls;
    }

As I said in the comments, I'm trying to build a bare bone ORM, I am only interested in the mapping to objects part. It works as is, however it would be practical sometimes for quick queries to be able to pass a Tuple instead of a full class.

Is there any solution ?

JMN - RTTB
  • 36
  • 7
  • Not sure I understand... Why can't you pass the Tuple as a parameter? – Ruslan Nov 22 '17 at 10:58
  • 2
    To ensure this isn't a XY Problem (https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) can you take a step back and explain why you need this code? – mjwills Nov 22 '17 at 11:07
  • When you say pass a Tuple are you asking about using it as the generic parameter? That you won't be able to do as a Tuple is a struct not a class. Nor would it make sense to with the inner code as a Tuple will not have a property called "ClientName". Unless you're a talking about a TupleClass (which to be honest I've not investigated) – d219 Nov 22 '17 at 11:36
  • I am building a bare-bone ORM (I'm only interested in the mapping to objects part, I'll still write my sql queries) and sometimes for a quick query it would be practical to pass a Tuple instead of creating a full class. @Ruslan there is no empty default constructor for tuple so T t = new T(); would throw an exception if T is a Tuple. – JMN - RTTB Nov 22 '17 at 13:05
  • @JMN-RTTB I see. That makes more sense. Tuples in C# use `ValueTuple` as the underlying struct, and while you can dynamically create one, I don't think there is a way to dynamically generate the elements' names. This might be of help: https://stackoverflow.com/questions/43565738/name-valuetuple-properties-when-creating-with-new I think you might have much more luck using reflection to generate a `struct` with the correct field names dynamically. Or even use the `dynamic` type, which would be much easier and make sense since this code is not typesafe anyway. – Ruslan Nov 22 '17 at 13:29
  • 1
    Possible, yes. Easy, no. You'll need to drop the `new` constraint, check if `T` is a `Tuple` or `ValueTuple` or something else, and invoke the appropriate constructors with reflection. Note that tuples would only really be useful for very small result sets, as tuple members have generic names that are not conducive to easy access (and the name mapping of `ValueTuple` relies on compile-time information that's not available in your member). You really don't want to read code where `Item7` is the customer name. In fact, I'd go so far as to say tuple support isn't worth it in an ORM. – Jeroen Mostert Nov 22 '17 at 13:31
  • An alternative to tuples that still allows you to write queries without explicitly writing out a full class is using anonymous types: `Select(sql, () => new { Id = default(int), Name = default(string), ... })` with `Select` taking a `Func`. As a bonus, you no longer need to figure out how to construct the object as you're getting a delegate that does it for you, so it generalizes. – Jeroen Mostert Nov 22 '17 at 13:43
  • I don't need specific names, I would be fine with keeping Item1, Item2. My use case for Tuples would be for small programs that need to quickly get something from the database and where creating a full class wouldn't bring much value. – JMN - RTTB Nov 22 '17 at 13:43
  • "Small programs that need to quickly get something from the database" ought to be LINQPad scripts. This gives you on the fly static typing and is far faster than even writing a full program. If it *is* a full program, that is maintained and all that, do yourself a favor and don't give in to the temptation of sacrificing long-term readability for a *very* slight increase in production speed. `Select>` just isn't worth it. – Jeroen Mostert Nov 22 '17 at 13:48
  • Visual Studio allows you to simply start using a class as if it existed (`A a = new A(); a.Id = 5;` and offers refactoring hints to generate the class plus members without ever writing them yourself, if that's the major problem. If even that is too much, I'd still prefer `dynamic` to tuples... but we're getting into opinion territory now. Yes, it is possible to write your function to accept tuple types, I'll leave it at that. I would never use them because they would break the instant even something trivial like the column order changes, and that is *hell* to debug. – Jeroen Mostert Nov 22 '17 at 13:51

1 Answers1

0

It does not work this way. Creating a method which will generate anything from a database is not a good solution for many reasons.

A proper way to do this would be a bit more complicated. I think you need a factory interface, like this:

public interface IMyFactory<T> where T : new() 
{ 
    T Create ( string name ); 
}

then a concrete factory:

public class MyFactory : IMyFactory<MyClient>
{
    public MyClient Create ( string name )
    {
        var t = new MyClient();
        t.ClientName = name;
        return t;
    }
}

and then pass it to your method like:

public List<T> SelectFromDataBase<T> ( IMyFactory<T> factory ) where T : new()
{
    var ls = new List<T>();

    // highly simplified here
    T t = factory.Create("Mr Smith");

    ls.Add(t);

    return ls;        
}

Then you could call your method as:

var myList = SelectFromDataBase(new MyFactory());

With this, you keep transforming data from database into an object separated from the object creation logic.

rs232
  • 1,248
  • 8
  • 16