-1

I have a repository abstract class that encapsulates pretty much all of the CRUD functionality:

public abstract class DataRepository<T> : IRepository<T>
    where T : class
{
    public DataContext Context { get; private set; }
    public TransactionScope Transaction { get; private set; }

    /// <summary>
    /// A <see cref="bool"/> function that compares the keys for fetching a single item, for example: 
    /// return item1.Id == item2.Id (as an anonymous delegate).
    /// </summary>
    public Func<T, T, bool> KeyCompare { get; private set; }

    /// <summary>
    /// Creates a new data repository.
    /// </summary>
    /// <param name="context"></param>
    /// <param name="scope"></param>
    /// <param name="keyCompare">
    /// A <see cref="bool"/> function that compares the keys for fetching a single item, for example: 
    /// return item1.Id == item2.Id (as an anonymous delegate).
    /// </param>
    public DataRepository(DataContext context, TransactionScope scope, Func<T, T, bool> keyCompare)
    {
        Context = context;
        Transaction = scope;
        KeyCompare = keyCompare;
    }

    public virtual T Item(T item)
    {
        return Items().SingleOrDefault(e => KeyCompare(e, item));
    }

    public virtual IEnumerable<T> Items()
    {
        return DataTable.AsEnumerable();
    }

    protected virtual Table<T> DataTable { get { return Context.GetTable<T>(); } }

    /// <summary>
    /// A method that updates the non-key fields of an existing entity with those of specified <see cref="item"/>.
    /// Called by the <see cref="Save"/> method.
    /// </summary>
    /// <param name="existing">The existing record to update.</param>
    /// <param name="item">A <see cref="T"/> object containing the values to update <see cref="existing"/> object with.</param>
    /// <returns></returns>
    protected abstract void UpdateExisting(T existing, T item);

    /// <summary>
    /// A method that updates an existing item or creates a new one, as needed.
    /// </summary>
    /// <param name="item">The entity containing the values to be saved.</param>
    public virtual void Save(T item)
    {
        var existing = Item(item);
        if (existing != null)
        {
            UpdateExisting(existing, item);
        }
        else
        {
            DataTable.InsertOnSubmit(item);
        }

        Context.SubmitChanges();
    }

    /// <summary>
    /// A method that saves all specified items (creates new, updates existing).
    /// </summary>
    /// <param name="items">The entities to be saved.</param>
    public virtual void Save(IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            Save(item);
        }
    }

    /// <summary>
    /// A method that deletes specified item.
    /// </summary>
    /// <param name="item"></param>
    public virtual void Delete(T item)
    {
        var existing = Item(item);
        if (existing != null)
        {
            DataTable.DeleteOnSubmit(existing);
        }

        Context.SubmitChanges();
    }

    public virtual void Delete(IEnumerable<T> items)
    {
        var selection = Items().Where(e => items.Any(item => KeyCompare(e, item)));
        DataTable.DeleteAllOnSubmit(selection);

        Context.SubmitChanges();
    }
}

The KeyCompare property is used like this in the derived classes, so that the base class knows how to isolate a single item in the repository (not all "entities" have a "Id" property, and some keys span multiple columns - this solution attempts to resolve that particular point):

    public AuthInfoRepository(DataContext context, TransactionScope scope)
        : base(context, scope, (item1, item2) => { return item1.Id == item2.Id;})
    { }

This KeyCompare property is really the cornerstone that allows the derived classes to merely implement the UpdateExisting method, like this:

    protected override void UpdateExisting(AuthInfo existing, AuthInfo item)
    {
        existing.AuthId = item.AuthId;
        existing.ActiveDirectoryGroup = item.ActiveDirectoryGroup;
    }

The rest (actual CRUD) is all handled by the base class. With this abstract repository I have been implementing the concrete ones in minutes if not seconds, writing only the code that is specific to each implementation. So DRY I'm thirsty.

The DataRepository<T> deals with SQL Server, so I needed yet another implementation for mocking, which I've called ListRepository<T> and does pretty much exactly the same thing (except Context and Transaction properties both return null). I think the constructor's signature is all I need to post here:

public ListRepository(IEnumerable<T> items, Func<T, T, bool> keyCompare)

So now I'd be ready for testing, and I want to use Ninject as my IoC container. My problem is that I can't seem to figure out how to pass an anonymous delegate as a ConstructorArgument:

Bind<IRepository<AuthInfo>>().To<ListRepository<AuthInfo>>()
                             .WithConstructorArgument("items", _mockAuthInfo)
                             .WithConstructorArgument("keyCompare", ??????);

Is what I'm trying to do feasible, or just over-complicated? I'm not going to ask if it's good/clean code, but constructive comments are welcome over here:

https://codereview.stackexchange.com/questions/25250/code-review-for-an-abstract-repository-implementation

Community
  • 1
  • 1
Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
  • Couldn't you implement Equals on the items? So that you don't have to pass in a function for that? Or am I missing something here? – treze Apr 19 '13 at 14:09
  • @treze They are Linq to SQL classes, I'm taking them as they are. Implementing Equals would lead to confusion because you couldn't tell without looking at the Equals implementation, if it's to compare the key field(s) or if it's to compare all fields and check if one was updated. The idea is to identify the key field(s) only. – Mathieu Guindon Apr 19 '13 at 14:16

1 Answers1

3

Argh. I hate when this happens (i.e. finding the answer minutes after posting)

All I needed was an explicit cast:

.WithConstructorArgument("keyCompare", (Func<AuthInfo, AuthInfo, bool>)((item1, item2) => item1.Id == item2.Id));

Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235