Dependency Injection and Generic Repository Windows Forms C#

Asked

Viewed 113 times

2

I have a windows Forms application where I am trying to use dependency injection for some services, for this I did the following configuration initially in Program.Cs I register the services:

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    var services = new ServiceCollection();
    ConfigureServices(services);

    using (ServiceProvider serviceProvider = services.BuildServiceProvider())
    {
        var mainForm = serviceProvider.GetRequiredService<MainForm>();
        Application.Run(mainForm);
    }
}

private static void ConfigureServices(ServiceCollection services)
{
    services.AddDbContext<AnalisarDbContext>();

    services.AddSingleton<MainForm>();
    services.AddScoped<Form1>();
    services.AddScoped<Form2>();
    services.AddScoped<Form3>();

    services.AddSingleton<IEmpRepository, EmprRepository>();
    services.AddSingleton<ISisRepository, SisRepository>();
}

So far everything working, there are 3 Forms I made to test the functionalities, in Form1 I inject the services I need:

private readonly ISisRepository _sisRepository;
private readonly IEmpRepository _empRepository;
public SelecionarEmpresa(ISisRepository sistRepository,
                         IEmpRepository empRepository)
{
    _sisRepository= sistRepository;
    _empRepository = empRepository;

    InitializeComponent();
}

And the idea is to use for example _sisRepository to update a record, the first time I save works, if I click to save again, an Exception is fired, before putting the exception I already inform that I am using a generic repository, which is the following:

public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : Entity, new()
{
    protected readonly AnalistDbContext Db;
    protected readonly DbSet<TEntity> DbSet;
    protected Repository(AnalistDbContext db)
    {
        Db = db;
        DbSet = db.Set<TEntity>();

        Db.ChangeTracker.AutoDetectChangesEnabled = false;

    }
    public IEnumerable<TEntity> Buscar(Expression<Func<TEntity, bool>> predicate)
    {
        return DbSet.Where(predicate).AsNoTracking().ToList();
    }

    public virtual TEntity ObterPorId(Guid id)
    {
        return DbSet.AsNoTracking().FirstOrDefault(s => s.Id == id);
    }

    public virtual List<TEntity> ObterTodos()
    {
        return DbSet.AsNoTracking().ToList();
    }

    public void Adicionar(TEntity entity)
    {
        DbSet.Add(entity);
        SaveChanges();
    }

    public void Atualizar(TEntity entity)
    {
        DbSet.Update(entity);
        SaveChanges();
    }

    public void Remover(Guid id)
    {
        DbSet.Remove(new TEntity { Id = id });
        SaveChanges();
    }
    public int SaveChanges()
    {
        return Db.SaveChanges();
    }

    public void Dispose()
    {
        Db?.Dispose();
    }
}

The Exception is as follows:

Exceprion lançada no Update

Although the clarity of the message could not understand if this is a problem caused by using dependency injection in the Form constructor and having caused some problem regarding the repository instance, it seems that my object is still the same already previously instantiated, As I said, this error occurs only in the second call. If this is really the problem, how could I fix it? Or in the case of windows Forms I would abandon the use of dependency injection?

  • I put an answer.

1 answer

1


This happens in Desktop programming, because the entity is already in your Context and has not been removed in the first recording, so that you do not have this problem in all operations after the SaveChanges() should call the method Detach or change the state of that object as follows:

dbContext.Entry(entity).State = EntityState.Detached; 

When one of these two codes is executed, the entity is not treated in the Context and so will not have the bother of an exception.

Abstract: under development Desktop have to pay attention to the methods Add, Update, Find because they continue to be managed by Context and if you try to do any operation in that entity you may receive an exception because it already exists in that Context (usually when you record one Id = 1 and try to do some operation with that code, but, already exists in the Context and the Entity Framework does not allow).

In your code something like this solves in the methods Adicionar and Atualizar, example:

public abstract class Repository<TEntity> : 
    IRepository<TEntity> where TEntity : Entity, new()
{
    protected readonly AnalistDbContext Db;
    protected readonly DbSet<TEntity> DbSet;
    protected Repository(AnalistDbContext db)
    {
        Db = db;
        DbSet = db.Set<TEntity>();
        Db.ChangeTracker.AutoDetectChangesEnabled = false;    
    }
    public IEnumerable<TEntity> Buscar(Expression<Func<TEntity, bool>> predicate)
    {
        return DbSet.Where(predicate).AsNoTracking().ToList();
    }

    public virtual TEntity ObterPorId(Guid id)
    {
        return DbSet.AsNoTracking().FirstOrDefault(s => s.Id == id);
    }

    public virtual List<TEntity> ObterTodos()
    {
        return DbSet.AsNoTracking().ToList();
    }

    public void Adicionar(TEntity entity)
    {
        DbSet.Add(entity);
        SaveChanges();
        //solução
        Db.Entry(entity) = EntityState.Detached;
    }

    public void Atualizar(TEntity entity)
    {
        DbSet.Update(entity);
        SaveChanges();
        //solução
        Db.Entry(entity) = EntityState.Detached;
    }

    public void Remover(Guid id)
    {
        DbSet.Remove(new TEntity { Id = id });
        SaveChanges();
    }

    public int SaveChanges()
    {
        return Db.SaveChanges();
    }

    public void Dispose()
    {
        Db?.Dispose();
    }
}

even in your code is correct:

return DbSet.Where(predicate).AsNoTracking().ToList();

utilise AsNoTracking() this disables checking by Context and performance improves significantly as it is read-only data.

  • 1

    Great explanation, thanks so much for sharing.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.