Search code examples
c#design-patternsabstraction

Cant make an appropriate abstraction when dealing with generic method (C#)


Im trying to implement a wrapper for using System.Data.SQLite features in order to get rid of duplicate code in a good OOP manner. So, i have the following generic method:

public T SendSelectQuery<T>(string sql, Func<SQLiteDataReader, T> processResult) where T : IDBResult
{
    try
    {
        using (var dbConnection = new SQLiteConnection("path"))
        using (var cmd = new SQLiteCommand(sql, dbConnection))
        {
            dbConnection.Open();

            cmd.CommandType = CommandType.Text;

            using (SQLiteDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                return processResult(rdr);
            }
        }
    }
    catch (Exception ex)
    {
        return T ??????
    }
}

T is a result object, i.e.:

public interface IDBResult
{
    bool Completed { get; set; }
    string Exception { get; set; }
}

public abstract class CustomDBREsult : IDBResult
{
    public bool Completed { get; set; }
    public string Exception { get; set; }

    public string Payload { get; set; }
    public CustomDBREsult(bool Completed, string exception, string Payload)
    {
        this.Completed = Completed;
        this.Exception = exception;
        this.Payload = Payload;
    }
}

public class SuccessCustomDBResult : CustomDBREsult
{
    public SuccessCustomDBResult(string Payload) : base(true, string.Empty, Payload)
    {
    }
}

public class ErrorCustomDBResult : CustomDBREsult
{
    public ErrorCustomDBResult() : base(false, "exception", string.Empty)
    {
    }
}

I want to call SendSelectQuery<CustomDBREsult>(...) and get an instance of CustomDBREsult child.

As you may have already noticed the problem occurs in catch segment where i need to return a T object, but i cant instantiate an approprite Error object derived from CustomDBREsult.

I could change return type of SendSelectQuery<T> to IDBResult and return in catch segment smth like this:

public class DefaultDBError : IDBResult
{
    public bool Completed { get; set; } = false;
    public string Exception { get; set; } = "db exception";
}

But in this case i would need to cast the result of SendSelectQuery<T> from IDBResult to T. And it doesn't seem to be a very good practice.

IDBResult res = DBMethods.SendSelectQuery<CustomDBREsult>("sql query", processResult);
if (res is CustomDBREsult cdbres)
{

}
else if (res is DefaultDBError ddberror)
{

}

The other option is to "lift up" try catch block and use SendSelectQuery<T> inside it, but i would need to duplicate this block everywhere i use SendSelectQuery<T> and still convert IDBResult to T.

If smn managed to understand me, would appreciate your comments. I guess my problem is making good abstractions of logic.


Solution

  • I think you're overengineering the problem here.

    First, the IDBResult interface isn't really serving any purpose, so you could nix that.

    Having the abstract class that you then derive a success and failure versions of the class, that themselves don't seem to do anything, except indicate whether it was a failure, or success, also doesn't really serve a lot of purpose.

    I'd change your custom db result into a generic, like so, since you're talking about using a T as a payload in your original Select.

       public class CustomDBREsult<T>
       {
         public bool Completed { get; set; }
         public string Exception { get; set; }
    
        public T Payload { get; set; }
        public CustomDBREsult(bool Completed, string exception, T Payload)
        {
            this.Completed = Completed;
            this.Exception = exception;
            this.Payload = Payload;
        }
      }
    

    Again, though, unless this is specifically an exercise you're challenging yourself to, to improve your design skills, or some such, there's a lot of good libraries out there that do most of this stuff already for you and would save reinventing the wheel.