6

I have a lot of methods that follow in large parts the same algorithm, and I ideally want to be able to make calls to a generic method that eliminates a lot of code duplication.

I have tons of methods like the ones below, I would optimally want to be able to just call Save<SQLiteLocation>(itemToSave); but i have a great deal of trouble since that the methods SQLiteConnection.Find<T> won't accept an abstract data type like T in generics.

Is there any way to get around this, if i could get it fixed i would save as many as 150 lines of code

Here's my code:

    public bool SaveLocation(ILocation location, ref int primaryKey)
    {
        var dbConn = new SQLiteConnection (dbPath);

        SQLiteLocation itemToSave = new SQLiteLocation ();
        itemToSave.LocationName = location.LocationName;
        itemToSave.Latitude = location.Latitude;
        itemToSave.Longitude = location.Longitude;
        itemToSave.PrimaryKey = location.PrimaryKey;

  ----------------------------------------------------------------------------------------

        SQLiteLocation storedLocation = dbConn.Find<SQLiteLocation>
                                        (x => x.PrimaryKey == location.PrimaryKey);

        if (storedLocation != null)
        {
            dbConn.Update(itemToSave);
            return true;
        }

        else if (storedLocation == null)
        {
            dbConn.Insert(itemToSave);
            primaryKey = itemToSave.PrimaryKey;
            return true;
        }
        return false;
    }

And here another method see how the code in both methods below my dashed line is basically the same thing

    public bool SaveInvitation(IInvitation invitation, ref int primaryKey)
    {
        var dbConn = new SQLiteConnection(dbPath);

        SQLiteInvitation itemToSave = new SQLiteInvitation ();
        itemToSave.GroupName = invitation.GroupName;
        itemToSave.InviterName = invitation.InviterName;
        itemToSave.ParseID = invitation.ParseID;
        itemToSave.GroupParseID = invitation.GroupParseID;
        itemToSave.PrimaryKey = invitation.PrimaryKey;

---------------------------------------------------------------------------------------

        SQLiteInvitation storedInvitation = dbConn.Find<SQLiteInvitation> 
                                            (x => x.PrimaryKey == invitation.PrimaryKey);

        if (storedInvitation != null)
        {
            dbConn.Update(itemToSave);
            return true;
        }
        else if (storedInvitation == null)
        {
            dbConn.Insert(itemToSave);
            primaryKey = itemToSave.PrimaryKey;
            return true;
        }
        return false;
    }
John Saunders
  • 160,644
  • 26
  • 247
  • 397
Guano
  • 139
  • 1
  • 9
  • You can use AutoMapper or something like it to move the properties. Also, if you move `itemToSave` out of the method and pass it in as a parameter, then I believe you could make the rest of the code generic. – John Saunders Dec 04 '14 at 20:01
  • Have you considered looking into expression trees to build the `Find` statement? – Bradford Dillon Dec 04 '14 at 20:02
  • `storedX` is either `null` or not, so no need to use `else if`. Due to this you'll never return false, so you could remove the return value. Left is `if(storedX != null){ dbConn.Update(itemToSave); } else{ dbConn.Insert(itemToSave); primaryKey = itemToSave.PrimaryKey; }`. 3 lines removed right there – default Dec 04 '14 at 20:17
  • I will look into AutoMapper and Expression tree, though I'm unfamiliar with it I will look into it, thank you for your inputs. And yes you are right its always either null or not, thank you – Guano Dec 04 '14 at 21:25

2 Answers2

1

Shouldn't you be able to do something like this: Note: ICommonInterface is anything that is common between any allowable classes you would want to use as T. Preferably, looking at your code, an interface or class that exposes the PrimaryKey property.

public bool SaveItem<T>(T item, ref int primaryKey) where T : ICommonInterface, new()
{
    var dbConn = new SQLiteConnection(dbPath);


    T storedItem = dbConn.Find<T>(x => x.PrimaryKey == item.PrimaryKey);

    if (storedItem != null)
    {
        dbConn.Update(item);
        return true;
    }
    else if (storedItem == null)
    {
        dbConn.Insert(item);
        primaryKey = item.PrimaryKey;
        return true;
    }
    return false;
}

EDIT: Added the new() constraint to the method.

gmiley
  • 6,531
  • 1
  • 13
  • 25
  • You are correct this fixed my code and you saved my many many lines of code – Guano Dec 04 '14 at 21:28
  • Actually it appears i was too rash, i get this error: 'T' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'SQLite.SQLiteConnection.Find(System.Linq.Expressions.Expression>)' (CS0310) (ShoppingAssistant) – Guano Dec 04 '14 at 22:13
  • See the modified answer. Add a new() constraint to the method. ex: where T : ICommonInterface, new() – gmiley Dec 05 '14 at 12:20
0

For some reason it threw an not supported exception when i used :

    T storedItem = dbConn.Find<T>(x => x.PrimaryKey == item.PrimaryKey);

The code below fixed my woes though, thanks for the help everyone, I love this site! Also i added another constraint on T seeing as T has to be a non abstract type and an interface is just that I suppose

    private void SaveItem<T>(T item, ref int primaryKey) 
        where T : ISQLiteClass, new()
    {
        var dbConn = new SQLiteConnection(dbPath);

        T storedItem = dbConn.Find<T>(primaryKey);

        if (storedItem != null)
        {
            dbConn.Update(item);
        }
        else if (storedItem == null)
        {
            dbConn.Insert(item);
            primaryKey = item.PrimaryKey;
        }
    }
Guano
  • 139
  • 1
  • 9