-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Add Unit Of Work Above The Repository With Specifications #327
Comments
It is needed sometimes. I can show an example, but I also don't want people to think that it is always needed, so that is why it's not included thus far in the relatively simple template. |
Thanks Steve got it. |
I'd love to see an example, Steve! |
Any progress on an example? I'm currently tring to implement your specfication pattern, but I'm stuck on how to rollback if I add multiple entities to the database, without explicitly removing them when an exception is thrown. |
One option is to use a transaction, and then rollback the transaction if an exception occurs. This would be similar to but not identical to a typical UoW pattern. For the Unit of Work pattern usually it just controls the call to SaveChanges in the DbContext. |
@ThomasVague The specification pattern is mainly about fetching, not saving, aggregates. How is the rollback issue you describe impacting your ability to use specifications? |
Yeah, I probably shouldn't have said specification pattern. I was referring to your library as a whole, with using the IRepository interface as a way of saving multiple entities. Since the update and create funtions are doing a call to SaveChangesAsync, and there is no transaction exposed, I'm not sure how to do a rollback. You said that "one option is to use a transaction", but I don't see how when the dbContext is not exposed? |
You can get the dbContext anywhere you need it with DI. So if you need to do a few operations you could have an interface like IUnitOfWork or ITransactionManager. It could expose methods BeginTransaction and CommitTransaction (and probably Rollback...). You could have an implementation: EfUnitOfWork which would request AppDbContext in its constructor and inside of the Begin/End transaction methods it would use the requested dbContext to start/commit a transaction. Then your higher level code would be: try { Because dbContext is scoped, you'll have the same instance of the DbContext in each repository and in the unit of work implementation. I'll still try and write up some actual code for this (this is all off the top of my head so might not compile or work 100%) but should get you started in the right direction. |
Great, thanks! I'll definitely look into that. |
It seemd to work nice! Here's my code if anyone's interested
Any thoughts on IUnitOfWork should be transient or scoped? |
It should be scoped if there's any chance it will be used between multiple services. |
@ThomasVague are you using a convention in your service registration such that ITransientService, IScopedService results in the registration working properly (Transient, Scoped, etc.)? Curious if that's something you made custom or if you're using a NuGet package for that. |
@ardalis I actually borrowed the idea from FSH's https://github.com/fullstackhero/dotnet-webapi-boilerplate. So yeah, the interfaces inherits from a ITransientService or IScopedService respectively and gets registered by a function "AddServices". Here's the code:
|
Great, thanks. That's where I'd seen it previously, I think. |
I tried to implement unitOfWork pattern mentioned above but I am getting following error, { Can someone please correct my code: Following is my code;
Can someone please correct my code. |
I have figured it out. I was opening 2 connections with database where as System.Transaction allows only 1 or if you want to check and validate some value from database you must close the connection first before opening another connection with System.Transaction. This issue can be closed now. |
I have a similar question. |
@Tcioch I do not understand your question. Could you please share some details with example code? |
I'm currently facing a challenge related to coordinating multiple domain services that each have their own repositories in a handler. I am using the Ardalis.Specification pattern and making use of the RepositoryBase, which has the AddAsync method. However, this immediately commits the changes to the database, and I need a way to delay this action.
And here is my service
And ICarRepository just gets IBaseRepository. |
What you are trying to do is commit changes all at once like:
What you should be doing is commit changes after each service is executed like this:
|
@Tcioch Also don't call DbContext directly into your class. It is not the best practice to use it like this. Add some Unit of Work type repository into your API. Best practice is to follow the SOLID principles. |
@Sarmadjavediqbal |
This is my repository (example)
Where IRepositoryBase is from Ardalis.Specification |
@Tcioch it seems that you are trying to saveChanges without using System.Transaction. |
@Tcioch Use Unit Of Work pattern in your API. as mentioned above in #327 (comment) by using System.Transaction you can save changes and commit them without any hassle. Also if it throws an exception the API will RollBack all the Transactions committed and you will have a consistency in your data as per your expectations.
|
@Sarmadjavediqbal |
DbContext already properly implements UoW and Repository patterns. If you re-implement it yourself, consider that UoW must control the creation of DbContext by itself, rather than assume someone else (e.g. DI container) would provide it with the "right" instance. Otherwise, it's guarantees could be easily broken, like so:
One could try to counter me that by
In these examples I've simplified things - actually one service could be adding a cat, another a dog. But since both could be executed with same (scoped) |
I think the best and flexible solution for transactions in clean architecture is the |
I'd like to add the unit of work pattern above the repository and specifications. Does this make sense or it will be a useless feature and
no need for it on domain driven design philosophy ?
The text was updated successfully, but these errors were encountered: