IRepository pattern with generic Factory pattern
-
04-07-2021 - |
質問
I have the following DBML modification (i'm using Linq to SQL as a 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 { }
With that i can manipulate my Linq objects more properly on other areas.
I've just done an IRepository pattern implementation.
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;
}
}
And that works flawlessly. But, there are sometimes that i don't know, on compilation time, which specific table i'll be using. For that i tried to create a simple generic factory for my 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;
}
}
However it doesn't work. It says that i cannot 'implicity convert DebtManager<NEWDEBT>
to DebtManager<ILinqSQLObject>
', but if NEWDEBT implements ILinqSQLObject, why isn't the compiler recognizing it? Obviously i'm doing some mistake but i can't see it.
解決
This error is caused by the fact that generics do not implicitly support covariance; that is, treating a specific generic parameter type as if it were one of its base types.
Couple ways around this. First, you can define a non-generic DebtManager base class that the generic DebtManager inherits from, and return that. Second, you can define a generic interface that DebtManager implements; generic interfaces CAN be defined to be covariant, by using the keyword out
before the generic type parameter.
EDIT: Let's go back to the primary need. You may not know, at compile-time, what type of object you will be required to work with and therefore you don't know which Repository you need. Might I suggest, then, that instead of a Repository-per-table architecture, you use a Repository-per-database. DebtManager is already generic to any Linq to SQL type; why not then make the methods generic as well, allowing them to be generic from call to call?
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>();
}
}