2

Je reçois une erreur étrange "impossible de convertir IStudentContext en TestStudentContext" lorsque vous essayez de tester un référentiel générique en C#. Il semble que j'ai besoin de réimplémenter .Set() dans mon interface, n'est-ce pas? Un peu de code omis pour la brièveté mais voici la configuration de base. Pardonne le gros bloc de code j'ajouterai du contexte supplémentaire en bas!Étrange erreur d'interface/TContext lors du test de l'interface générique

IStudentContext.cs

public interface IStudentContext : IDisposable 
{ 
    Database Database { get; } 

    // Irrelevant tables omitted 
    DbSet<Class> Classes { get; set; } 

    int SaveChanges(); 

    Task<int> SaveChangesAsync(); 
} 

StudentContext.cs

public class StudentContext : DbContext, IStudentContext 
{ 
    public StudentContext() : base("name=StudentContext") {} 

    // Irrelevant tables omitted 
    public virtual DbSet<Class> Classes { get; set; } 
} 

TestStudentContext.cs

public class TestStudentContext : DbContext, IStudentContext 
{ 
    public TestStudentContext(DbConnection connection) : base(connection, contextOwnsConnection: true) {} 

    // Irrelevant tables omitted 
    public virtual DbSet<Class> Classes { get; set; } 
} 

EntityFrameworkReadOnlyRepository.cs

public class EntityFrameworkReadOnlyRepository<TContext> : IGenericReadOnlyRepository where TContext : DbContext, IStudentContext 
{ 
    protected readonly TContext Context; 

    public EntityFrameworkReadOnlyRepository(TContext context) 
    { 
     Context = context; 
    } 

    // Irrelevant generic repo methods omitted 
    public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string include = null, int? skip = null, int? take = null) 
    { 
     return GetQueryable(filter, orderBy, include, skip, take).ToList(); 
    } 

    protected virtual IQueryable<TEntity> GetQueryable<TEntity>(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string include = null, int? skip = null, int? take = null) 
    { 
     include = include ?? string.Empty; 
     IQueryable<TEntity> query = Context.Set<TEntity>(); 

     if (filter != null) 
      query = query.Where(filter); 

     query = include.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, property) => current.Include(property)); 

     if (orderBy != null) 
      query = orderBy(query); 

     if (skip.HasValue) 
      query = query.Skip(skip.Value); 

     if (take.HasValue) 
      query = query.Take(take.Value); 

     return query; 
    } 
} 

StudentRepositoryTests.cs

public class StudentRepositoryTests 
{ 
    private IStudentContext Context { get; set; } 

    [TestInitialize] 
    public void Initialize() 
    { 
     Context = new TestStudentContext(Effort.DbConnectionFactory.CreateTransient()); 
    } 

    [TestMethod] 
    public void GetClasses_OrdersCorrectly() 
    { 
     // Calls to Context.Classes.Add() to set up DB omitted 

     Context.SaveChanges(); 

     var repository = new EntityFrameworkReadOnlyRepository<TestStudentContext>(Context); 

     var results = repository.Get<Classes>().ToArray(); 

     // Assertions omitted 
    } 
} 

Comme écrit, je reçois une erreur sur Context dans la ligne var repository de ma classe de test. L'erreur indique Argument 1: cannot convert from 'IStudentContext' to 'TestStudentContext'. Si je change cette ligne à var repository = new EntityFrameworkReadOnlyRepository<IStudentContext>(Context);, je reçois une erreur The type 'IStudentContext' cannot be used as type parameter 'TContext' in the generic type or method 'EntityFrameworkReadOnlyRepository<TContext>'. There is no implicit reference conversion from 'IStudentContext' to 'System.Data.Entity.DbContext'.

On dirait que si je mets en œuvre DbSet pour toutes mes tables de base de données dans IStudentContext que pourrait résoudre le problème, mais qui se sent comme si je réimplémentant beaucoup de code juste pour être en mesure de tester quelque chose, donc je pense qu'il y aurait une façon plus simple de le faire.

+0

Le type 'DbContext' est une implémentation d'un modèle UoW et le type' DbSet 'est une implémentation d'un modèle Repository. Pourquoi réemballer ces types dans votre propre implémentation du même modèle? Vous n'ajoutez rien de valeur, juste plus de code et une mauvaise abstraction qui résulte en un code plus difficile à lire, à déboguer et à utiliser. – Igor

+0

@Igor Je ne suis pas marié à cette implémentation - quelle est votre suggestion pour une meilleure implémentation? Nous sommes en train d'écrire une API orientée public et ma pensée initiale était les entités EF, un référentiel au-dessus, et un service métier supérieur (transformant les réponses du référentiel dans la structure de données qui sera retournée à l'utilisateur). –

+0

Je recommande de consommer le DbContext directement dans les services métier. – Igor

Répondre

0

Dans

var repository = new EntityFrameworkReadOnlyRepository<TestStudentContext>(Context); 

Context est de type IStudentContext. Par conséquent, vous ne pouvez pas transmettre cette valeur au ctor qui attend TestStudentContext. Une solution simple est de faire private IStudentContext Context { get; set; } de type TestStudentContext. Ou, si vous voulez le garder IStudentContext puis cast, ou trouver un autre moyen de transmettre cette valeur sans upcasting à IStudentContext d'abord perdre des informations de type dans le processus.

Il semble que j'ai besoin de réimplémenter .Set() dans mon interface, n'est-ce pas?

Cela ne devrait pas être nécessaire. Le message du compilateur pointe vers le problème que j'ai résolu.


Dans votre pension, vous avez choisi de faire l'appelant préciser où, par ordre, etc. Ce n'est pas une approche utile car il me semble. Il semble préférable de simplement avoir une méthode QueryTable<T>() et de faire en sorte que l'appelant construise la requête. Vous pourriez créer une fonction d'aide pour créer une requête comme celle-ci (je pense que cela serait inutile), mais il n'est pas nécessaire de rendre le référentiel responsable des requêtes de construction LINQ.

Je remarque également à un aspect superficiel que le paramètre TContext ne semble pas nécessaire. Vous pouvez utiliser DbContext directement et simplifier.