1

I have created the following class, that works with a specific database table. How can I make this class a base class so that other classes can have the same properties and methods, but return the correct type and the only thing I have to do is to assign the correct tableName?

Thanks in advance, I hope my question is clear.

Here is the class:

public class AccountType
{
    private static string tableName = "accountTypes";

    private int id = -1;
    public int Id
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
        }
    }

    private string name = "";
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    private static List<AccountType> accountTypes = new List<AccountType> ();
    public static List<AccountType> AccountTypes
    {
        get
        {
            return accountTypes;
        }
    }

    public AccountType ()
    {
    }

    public AccountType Clone ()
    {
        AccountType o = (AccountType)this.MemberwiseClone ();

        return o;
    }

    public static AccountType Fill (DataRow row)
    {
        int id = Convert.ToInt32 (row["id"].ToString ());
        string name = row["name"].ToString ();

        AccountType o = new AccountType ();
        o.id    = id;
        o.name  = name;

        return o;
    }

    public static List<AccountType> FillAll (DataRowCollection rows)
    {
        List<AccountType> objs = new List<AccountType> ();

        foreach (DataRow row in rows)
        {
            AccountType o = Fill (row);

            if (o != null)
                objs.Add (o);
        }

        return objs;
    }

    public static List<AccountType> GetAll ()
    {
        if (AccountType.accountTypes.Count > 0)
            return AccountType.accountTypes;

        List<AccountType> objs = new List<AccountType> ();

        string query = "SELECT      * \r\n" +
                        "FROM   " + AccountType.tableName + " \r\n" +
                        "WHERE      id > -1 \r\n" +
                        "ORDER BY   name";

        DataSet result = Global.Db.ExecuteQuery (query);

        if (
                    (result == null)
                ||  (result.Tables[0] == null)
                ||  (result.Tables[0].Rows.Count < 1)
            )
        {
            return objs;
        }

        objs = FillAll (result.Tables[0].Rows);

        return objs;
    }

    public static AccountType GetById (int id)
    {
        foreach (AccountType at in AccountType.accountTypes)
        {
            if (at.id == id)
                return at;
        }

        AccountType o = null;

        string query = "SELECT  * \r\n" +
                        "FROM   " + AccountType.tableName + " \r\n" +
                        "WHERE  id = " + id + " \r\n";

        DataSet result = Global.Db.ExecuteQuery (query);

        if (
                    (result == null)
                ||  (result.Tables[0] == null)
                ||  (result.Tables[0].Rows.Count < 1)
            )
        {
            return o;
        }

        o = Fill (result.Tables[0].Rows[0]);

        return o;
    }

    public static void Load ()
    {
        AccountType.accountTypes = AccountType.GetAll ();
    }

    public void Save ()
    {
        string tn = AccountType.tableName;
        string query =  "INSERT INTO " + tn + " (name) " +
                        "VALUES (               @name)";

        SQLiteCommand command = new SQLiteCommand ();
        command.CommandText = query;
        command.CommandType = CommandType.Text;

        command.Parameters.Add (new SQLiteParameter("@currencyPair",    this.name));

        Common.Global.Db.ExecuteNonQuery (command);
    }

    public void Update ()
    {
        string query =  "UPDATE " + AccountType.tableName + " \r\n" +
                        "SET    name    = @name \r\n" +
                        "WHERE  id      = @id";

        SQLiteCommand command = new SQLiteCommand ();
        command.CommandText = query;
        command.CommandType = CommandType.Text;

        command.Parameters.Add (new SQLiteParameter("@id",      this.id));
        command.Parameters.Add (new SQLiteParameter("@name",    this.name));

        Common.Global.Db.ExecuteNonQuery (command);
    }
}
Luca
  • 333
  • 2
  • 13
  • 5
    This formatting is something else – maccettura May 16 '18 at 19:03
  • Sorry, what do you mean? – Luca May 16 '18 at 19:05
  • The `#regions` are just clutter, you have way too much spacing between your method signature items (the access modifier, the return type, the name and then the parentheses). The comment lines are just in the way and do nothing – maccettura May 16 '18 at 19:06
  • You could make another class an extension method to this class. Then all you would have to do is set proper accessibility levels for your Account-type methods and variables. [Extension Methods.](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) – Lee Toffolo May 16 '18 at 19:10
  • @maccettura I have edited the code. The fact is that I use a lot the folding options of VS and therefore I like to have regions and method names spaced equally so that I can see them very quickly. Anyway now it should be much better – Luca May 16 '18 at 19:13
  • 2
    Is all you want make the class `abstract` only with the `tableName` to be perhaps provided initially as a constructor parameter? – PiotrWolkowski May 16 '18 at 19:19
  • 1
    I know it's not on-topic, but you can (and should) use Auto-Properties: `public int Id { get; set; } = -1;` `public string Name { get; set; } = string.Empty;` `public static List AccountTypes { get; } = new List();` – Ivan García Topete May 16 '18 at 19:24
  • @PiotrWolkowski but if I create an abstract class, I cannot write the body of the functions, am I correct? What I want to do is to use this code once and be able to change the Type and the tableName. – Luca May 16 '18 at 19:27
  • @IvanGarcíaTopete I already use them but, is there any benefit to them other than conciseness and clarity? (i.e. performance?) – Daxtron2 May 16 '18 at 19:29
  • 1
    @TJWolschon other than that, [no](https://stackoverflow.com/a/6001941/9453080) – Ivan García Topete May 16 '18 at 19:39
  • With an Interface you cannot write the body of functions, if you use an abstract class you can. Abstract class follows single inheritance rules whereas Interface does not have this restriction. – Keith May 16 '18 at 19:52
  • 1
    @Keith Just an example... If I create a class called [code]Cat : AccountType[/code] how can I make the function `GetAll` to return `List` instead of `List`? – Luca May 16 '18 at 19:59
  • @Luca this is kind of an advanced topic so it might require some further research, generally this would be handled with dependency injection. You essentially push the execution down into the derived class with a factory that creates the object you need at runtime. Here is a link that describes it better. https://www.codeproject.com/Articles/615139/An-Absolute-Beginners-Tutorial-on-Dependency-Inver – Keith May 16 '18 at 20:12
  • @Luca the example used is for interfaces but the same concept applies to abstract classes as well – Keith May 16 '18 at 20:12
  • @Luca Of course you can write a body of a function when you have an `abstract` class. You only cannot instantiate this class. You can have a regular method with a body and an abstract method that needs to be overridden by an inheriting class. – PiotrWolkowski May 17 '18 at 01:47

1 Answers1

0

I'm 80 % sure I understand you're question so hopefully this answer helps. I think the Curiously Recurring Template Pattern might help

The idea is you do this (I've just implemented random bits to hopefully give you an idea of what it can do for you)

public abstract class BaseClass<T> where T : BaseClass<T>
{

    public string Id {get; set;}
    public string OtherProperty {get; set;}
    public T WorkWithTAndReturn(T instanceOfASubClass)
    {
        //you can access base properties like
        var theId = T.Id;
        T.OtherProperty = "Look what I can do";
        return T
    }
    public T Clone()
    {
        var newT = DoCloneStuff(instanceToClone);//do clone stuff here
        return newT;
    }

    public static T Clone(T instanceToClone)
    {
        return (T)instanceToClone.MemberwiseClone();
    }
}

Note: 'BaseClass' is abstract because you don't want to instantiate instances of that. Because T has to be a subclass of BaseClass<T> you instantiate the sub class, you dont do this new BaseClass because thats just unneccessary and odd. Making it abstract also allows you to make implementers provide some functionality if needed

So then you create an class that derives from BaseClass<T>

public class MySubClass : BaseClass<MyClass>
{
    //put what ever implementation you want in here
}

and it can this be used

var mySubClass = new MySubClass();
SubClass clone = mySubClass.Clone();
SubClass otherClone = SubClass.Clone(mySubClass);

Edit: Further information

public static T Fill(DataRow row)
    {
        int id = Convert.ToInt32(row["id"].ToString());
        string name = row["name"].ToString();
        T o = new T();
        o.id = id;
        o.name = name;
        return o;
    }
    public static List<T> FillAll(DataRowCollection rows)
    {
        List<T> objs = new List<T>();
        foreach (DataRow row in rows)
        {
            T o = Fill(row);
            if (o != null)
                objs.Add(o);
        }
        return objs;
    }

This pattern solves some of the issues you have with the static functions. However static fields or properties are only accessible on the class they are implemented on. They are not inherited. A static string prop on Class A does not belong to Class B even if Class B inherits off Class A.

I think this pattern is going to point you in the right direction but you are going to need to rethink your pattern somewhat


Edit 2: OK so heres my idea for getting around this road block of the static table name field you have

public abstract class TableDesciptor
{
    public abstract string TableName { get; }
}

public class AccountType<T,U>
    where T : AccountType<T,U>, new()
    where U : TableDesciptor, new()
{
    private static string tableName = new U().TableName;
    public static List<T> AccountTypes
    {
        get
        {
            return accountTypes;
        }
    }

    public int Id { get; set; }

    public string Name { get; set; }

    private static List<T> accountTypes = new List<T>();

    public AccountType()
    {
    }

    public T Clone()
    {
        T o = (T)this.MemberwiseClone();

        return o;
    }

    public static T Fill(DataRow row)
    {
        int id = Convert.ToInt32(row["id"].ToString());
        string name = row["name"].ToString();

        T o = new T();
        o.Id = id;
        o.Name = name;

        return o;
    }

    public static List<T> FillAll(DataRowCollection rows)
    {
        List<T> objs = new List<T>();

        foreach (DataRow row in rows)
        {
            T o = Fill(row);

            if (o != null)
                objs.Add(o);
        }

        return objs;
    }

    public static List<T> GetAll()
    {
        if (accountTypes.Count > 0)
            return accountTypes;

        List<T> objs = new List<T>();

        string query = "SELECT      * \r\n" +
                        "FROM   " + AccountType<T,U>.tableName + " \r\n" +
                        "WHERE      id > -1 \r\n" +
                        "ORDER BY   name";

        DataSet result = Global.Db.ExecuteQuery(query);

        if (
                    (result == null)
                || (result.Tables[0] == null)
                || (result.Tables[0].Rows.Count < 1)
            )
        {
            return objs;
        }

        objs = FillAll(result.Tables[0].Rows);

        return objs;
    }

    public static T GetById(int id)
    {
        foreach (T at in accountTypes)
        {
            if (at.Id== id)
                return at;
        }

        T o = null;

        string query = "SELECT  * \r\n" +
                        "FROM   " + AccountType<T,U>.tableName + " \r\n" +
                        "WHERE  id = " + id + " \r\n";

        DataSet result = Global.Db.ExecuteQuery(query);

        if (
                    (result == null)
                || (result.Tables[0] == null)
                || (result.Tables[0].Rows.Count < 1)
            )
        {
            return o;
        }

        o = Fill(result.Tables[0].Rows[0]);

        return o;
    }

    public static void Load()
    {
        accountTypes = GetAll();
    }

    public void Save()
    {
        string tn = AccountType<T,U>.tableName;
        string query = "INSERT INTO " + tn + " (name) " +
                        "VALUES (               @name)";

        SQLiteCommand command = new SQLiteCommand();
        command.CommandText = query;
        command.CommandType = CommandType.Text;

        command.Parameters.Add(new SQLiteParameter("@currencyPair", this.Name));

        Common.Global.Db.ExecuteNonQuery(command);
    }

    public void Update()
    {
        string query = "UPDATE " + AccountType<T,U>.tableName + " \r\n" +
                        "SET    name    = @name \r\n" +
                        "WHERE  id      = @id";

        SQLiteCommand command = new SQLiteCommand();
        command.CommandText = query;
        command.CommandType = CommandType.Text;

        command.Parameters.Add(new SQLiteParameter("@id", this.Id));
        command.Parameters.Add(new SQLiteParameter("@name", this.Name));

        Common.Global.Db.ExecuteNonQuery(command);
    }
}
Dave
  • 2,829
  • 3
  • 17
  • 44
  • 1
    thank you very much! I think this is what I need. This evening I'll try it and let you know if this is the answer I'm looking for! – Luca May 17 '18 at 08:54
  • I looked at it, but I don't understand how it can solve the problem that from the base class I want to access static variables like `tableName` or static functions like `Fill` of the children classes – Luca May 17 '18 at 19:49
  • @Luca Static members belong to the declaring class they cannot be inherited. There isnt a way round that. This means you cannot access a static variable of a derived class without knowing the type ie. `DerivedClass.StaticVariable`. The Fill function is a perfect fit for this. I'll post it in my next comment – Dave May 17 '18 at 20:27
  • @Luca I edditted the question as I had more space – Dave May 17 '18 at 20:37
  • @Luca check this out https://dotnetfiddle.net/bsWHJh.That has the code and an example inheritor. I've also put some of it in the answer so others can see without having to click through. Its a slight different architecture and some might say a bit of a hack with the throw away object. But as I've said you cannot use static properties like you want to so it was always going to need something different – Dave May 17 '18 at 21:04
  • For some reason ".That" got appended to the end. Copy the text that looks like the link not including ".That" and it should word – Dave May 17 '18 at 22:06
  • I am experimenting, but unfortunately it seems that I cannot have `ChildClass : BaseClass` and then `ChildClassEx : ChildClass`. I mean, I can, but it doesn't work as expected... Is this correct? – Luca May 19 '18 at 20:53
  • Whats your reason for wanting to do that? The reason it won't work if I have understand you is that all the logic in `BaseClass` is designed to work with whatever T is. So it works with ChildClass as ChildClass is the Generic argument when implementing BaseClass. However ChildClassEx inherits off ChildClass so the generic Argument is still ChildClass – Dave May 20 '18 at 08:22
  • in the end I think that what I want to do is not doable, but in any case you helped me, so I'll set your as the answer, because it may help someone else. – Luca May 20 '18 at 20:15