-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make the SaveChangesAsync call optional in RepositoryBaseOfT #395
Comments
This exact case is what we're dealing with. Paraphrasing from @ardalis 's eShopOnWeb tutorial:
His point being that 99% of requests will do some reads and then one logical write for an aggregate. To me, that makes a lot of sense... Don't know about 99%, but certainly most of them. My problem here is that for the cases that do more than one write, we will 100% certainly forget to do custom handling for the transactional part. We won't hear about it until there is an error in prod and users are stuck with corrupted data from a request failing half-way through. So, for my concerns, this is not safe enough. I've implemented a UnitOfWork pattern in my code, and I've set it up in "filters" for my Razor Pages application, making sure that every request is wrapped in a UnitOfWork, so that either the request completes all of its writes completely or it rolls back the DB transaction and yields a 500 - INTERNAL SERVER ERROR. This, however, does not work with Ardalis' repositories here, because they are hard-coded to save at each step. I'm currently looking into inheriting and overriding his implementation to see if that would work. I still want all the other great parts - particularly the specification pattern - that comes with this library 😉 Ideal scenario is that this is a setting we can set to control the behaviour of the repositories |
Ooh, or at the very least make the fields on the |
Right, I did this, which seems to do the trick: public class RepositoryBase<T> : Ardalis.Specification.EntityFrameworkCore.RepositoryBase<T> where T : class
{
private readonly DbContext _dbContext;
private readonly ISpecificationEvaluator _specificationEvaluator;
public RepositoryBase(DbContext dbContext) : base(dbContext)
{
_dbContext = dbContext;
}
public RepositoryBase(DbContext dbContext, ISpecificationEvaluator specificationEvaluator) : base(dbContext,
specificationEvaluator)
{
_dbContext = dbContext;
_specificationEvaluator = specificationEvaluator;
}
/// <inheritdoc/>
public override async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
{
_dbContext.Set<T>().Add(entity);
return entity;
}
/// <inheritdoc/>
public override async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities,
CancellationToken cancellationToken = default)
{
_dbContext.Set<T>().AddRange(entities);
return entities;
}
/// <inheritdoc/>
public override async Task UpdateAsync(T entity, CancellationToken cancellationToken = default)
{
_dbContext.Set<T>().Update(entity);
}
/// <inheritdoc/>
public override async Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
_dbContext.Set<T>().UpdateRange(entities);
}
/// <inheritdoc/>
public override async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
{
_dbContext.Set<T>().Remove(entity);
}
/// <inheritdoc/>
public override async Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
_dbContext.Set<T>().RemoveRange(entities);
}
/// <inheritdoc/>
public override async Task DeleteRangeAsync(ISpecification<T> specification,
CancellationToken cancellationToken = default)
{
var query = ApplySpecification(specification);
_dbContext.Set<T>().RemoveRange(query);
}
} All I did was copy the implementation and remove the saves. It isn't super clean, since there is now a Shouldn't be any problem, since it is the same instance, but feels a little dumb to have two references in play... If the fields of the base class were protected instead, that would be helpful... I suppose this could be done more elegantly in a PR to this repository instead, so that there would be a |
I gave some thoughts on this topic since I wrote the initial post and i can see at least 3 possibile ways to achieve the goal:
I don't really like 100% any of the three, but I can't figure out something "cleaner" |
Given the freedom to edit the library, I would probably make it a setting on the repository, which would be provided in the constructor or library netted through configuration, so that any repository would have a ’shouldIAutoSave‘ property. But I don't know if any of it is in the spirit of what the maintainers of this library is going for. For the time being, I have a workaround that does the trick, and I can wait for this issue to get some more attention 😊 |
I'm open to making the type use For example:
Then you could explicitly choose a particular flavor worked best for your needs. I'm not sure I'll get around to the sub variants any time soon but the top level Ardalis.Repository that would have my personal version is likely to happen in the next few months. |
If you like could someone open a separate issue for marking all the repo methods protected, and then do a quick PR if you want? |
Sure. Issue here #397. I'll see if I can find some time to implement it. Shouldn't take long ✌️ |
Not only that the existence of a separate SaveChanges confusing, the example from How to use the Built In Abstract Repository suggest that you need to call SaveChanges manually. |
Recently i start liking (and using) the Unit Of Work pattern and I noticed that almost all methods to perform database changes in
RepositoryBase<T>
perform_dbContext.SaveChangesAsync()
internally. IMHO this operation should, at least, be optional.If you have to deal with domain events you want to fire before a change is persisted, you are mandated to rely on EF Core interceptors only, leaving your
UnitOfWork
almost useless.Probably I'm wrong and I'm missing something, but as far as I understand, this is a pretty hard limitation.
The text was updated successfully, but these errors were encountered: