Шаблон IRepository с общим шаблоном Factory

У меня есть следующая модификация DBML (я использую Linq для SQL в качестве DAL).

public interface ILinqSQLObject { }

// these are objects from SQL Server mapped into Linq to SQL
public partial class NEWDEBT : ILinqSQLObject { }
public partial class OLDDEBT : ILinqSQLObject { }
public partial class VIPDEBT : ILinqSQLObject { }

С этим я могу более правильно манипулировать своими объектами Linq в других областях.

Я только что сделал реализацию шаблона IRepository.

public interface IDebtManager<T>
    {
        IQueryable<T> GetAllDebts();
        IQueryable T GetSpecificDebt(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
        void Insert(T debt);
        // other methods
    }

public class DebtManager<T> : IDebtManager<T> where T : class, ILinqSQLObject 
    {
        DebtContext conn = new DebtContext();
        protected System.Data.Linq.Table<T> table;

        public DebtManager()
        {
            table = conn.GetTable<T>();
        }

        public void Insert(T debt)
        {
            throw new NotImplementedException();
        }

        public IQueryable<T> GetSpecificDebt(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {
            return table.Where(predicate);
        }

        public IQueryable<T> GetAllDebts()
        {
            return table;
        }
    }

И это работает без нареканий. Но иногда я не знаю, во время компиляции, какую конкретную таблицу я буду использовать. Для этого я попытался создать простую универсальную фабрику для моего DebtManager.

public static class DebtFactoryManager
{

    public static DebtManager<ILinqSQLObject> GetDebtManager(string debtType) 
    {
        switch (debtType)
        {
            case "New Client":
                return new DebtManager<NEWDEBT>();
            case "Old Client":
                return new DebtManager<OLDDEBT>();
            case "VIP Client":
                return new DebtManager<VIPDEBT>();
            default:
                return new DebtManager<NEWDEBT>();
        }

        return null;
    }

}

Однако это не работает. Он говорит, что я не могу преобразовать безбожие DebtManager<NEWDEBT> в DebtManager<ILinqSQLObject>', но если NEWDEBT реализует ILinqSQLObject, почему компилятор не распознает его? Очевидно, я делаю какую-то ошибку, но не вижу ее.

1 ответ

Решение

Эта ошибка вызвана тем, что дженерики неявно поддерживают ковариацию; то есть обрабатывать конкретный универсальный тип параметра, как если бы он был одним из его базовых типов.

Пара способов обойти это. Во-первых, вы можете определить неуниверсальный базовый класс DebtManager, от которого унаследован универсальный DebtManager, и вернуть его. Во-вторых, вы можете определить общий интерфейс, который реализует DebtManager; универсальные интерфейсы могут быть определены как ковариантные, используя ключевое слово out перед параметром универсального типа.

РЕДАКТИРОВАТЬ: Давайте вернемся к основной необходимости. Вы можете не знать, во время компиляции, с каким типом объекта вам потребуется работать, и поэтому вы не знаете, какой репозиторий вам нужен. Могу ли я предположить, что вместо архитектуры Репозиторий на таблицу вы используете Репозиторий на базу данных. DebtManager уже универсален для любого типа Linq to SQL; почему бы тогда не сделать методы общими, позволяя им быть общими от вызова к вызову?

public interface IRepository<T> where T:class, ILinqSqlObject
{
    IQueryable<TSpec> GetAllDebts<TSpec>() where TSpec : T;
    IQueryable<TSpec> GetSpecificDebt<TSpec>(System.Linq.Expressions.Expression<Func<TSpec, bool>> predicate) where TSpec : T;
    void Insert<TSpec>(TSpec debt) where TSpec:T;
    // other methods
}

interface IDebtObject : ILinqSqlObject

public interface IDebtManager:IRepository<IDebtObject> { }

public class DebtManager:IDebtManager
{
    DebtContext conn = new DebtContext();

    public DebtManager()
    {            
    }

    public void Insert<T>(T debt) where T:IDebtObject
    {
        throw new NotImplementedException();
    }

    public IQueryable<T> GetSpecificDebt(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T:IDebtObject
    {
        return conn.GetTable<T>().Where(predicate);
    }

    public IQueryable<T> GetAllDebts<T>() where T:IDebtObject
    {
        return conn.GetTable<T>();
    }

}
Другие вопросы по тегам