Шаблон 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>();
}
}