Unit of Work in ASP.NET Core

Поделиться
HTML-код
  • Опубликовано: 1 авг 2024
  • The Unit of work "design pattern" 🤦‍♂️ is presented as a wrapper around a db context and some repositories, which is a LIE. Unit of Work is the object that does the change tracking and makes the decision of what get's saved to the database.
    Patreon 🤝 / raw_coding
    Courses 📚 learning.raw-coding.dev
    Shop 🛒 shop.raw-coding.dev
    Discord 💬 / discord
    Twitter 📣 / anton_t0shik
    Twitch 🎥 / raw_coding
    🕰️ Timestamps
    00:00 Introductory Rant
    03:20 Implementation
    11:20 Child Services
    #csharp #aspnetcore #patterns

Комментарии • 83

  • @stefan-d.grigorescu
    @stefan-d.grigorescu Год назад +13

    Man! I found out the same thing when I was told to implement unit of work for a college project! I was asking around "Why do we need this if a Scoped DbContext already does that stuff?"
    And more people then seemed to make the same wrappers, no one gave an answer why

    • @kneeh
      @kneeh 10 месяцев назад

      Chances are low but to keep the right to migrate from EF to Dapper for example.
      You can ask the same question as to why work with repositories if "EF is already a database abstraction".

    • @stefan-d.grigorescu
      @stefan-d.grigorescu 10 месяцев назад

      @@kneeh actually I use repos only a few times, for stuff that are queried the same way all the time. But must of the times I find that using dbContext directly in a query handler makes perfect sense, since queries are generally different for each case

    • @kneeh
      @kneeh 10 месяцев назад

      ​@@stefan-d.grigorescu And that's perfectly fine, that all depends on the requirements.
      1. I was involved in a project that needed fast POC, so we use EF and migrated to Dapper, Abstraction was our savior.
      2. I rather see a method name than a chain of LINQ calls from EF, and if I already refactor LINQ to a method, why would abstract it entirely, it will also clean up testing by much (the testing suite doesn't need to know about EF).

    • @stefan-d.grigorescu
      @stefan-d.grigorescu 10 месяцев назад

      @@kneeh For 2. I also like extracting such parts of a bigger method into more focused smaller methods in the same handler class
      That way, the top level method still reads nice

  • @ardavaztterterian1211
    @ardavaztterterian1211 Год назад +8

    Finally, someone said it! However, in some cases we do need to save the changes of a service operation to be able to proceed with the next service operation which depends on the previous result. How do we achieve this using the method in the video?

    • @RawCoding
      @RawCoding  Год назад +6

      unit of work is meant to be a buffer for an operation, so to speak.
      Transition between the operations is not a concern of unit of work, so the answer is you don't..

    • @adrian_franczak
      @adrian_franczak Год назад

      its more like hack but you can cast and save manually xD

  • @chiro5533
    @chiro5533 Год назад +7

    Here's a tip if you're looking for information on design and architecture, consider checking out the articles and books written by Martin Fowler, Robert C. Martin, Kent Beck, and others. In my personal opinion, Microsoft's documentation is inadequate when it comes to best practices in these areas. It seems that the Microsoft developers may not be well-versed in best practices and patterns.

    • @RawCoding
      @RawCoding  Год назад +3

      I recommend Christopher Alexander

    • @fahimahmedali
      @fahimahmedali Год назад

      ​@@RawCoding Hello Anton
      Are you talking about a specific book or something? Can you please share the name or some URL. Thanks.

  • @allinvanguard
    @allinvanguard Год назад +13

    Man, spot on. I don't know what it is, but the space around EF, UoW and Repositories is apparently something people just love to regurgitate something they read somewhere else without doing their own research. Love that you are calling this out.

  • @Kosa39
    @Kosa39 Год назад +9

    5:45 - calling BuildServiceProvider is dangerous because this will instantiate your singletons twice (i know that you don't have any but in real case scenerio you'd probably have some). Singleton as the name suggests it is something that is single - only one, sometimes instantiating singleton twice wouldn't do any harm but other times it can break stuff, process something twice etc. .It will be better to use decorator pattern in here i think. Besides that, great work ;)

    • @Kosa39
      @Kosa39 Год назад +8

      Another thing is that without "bulk update" and no rollback mechanisms in place the implementation of SaveChangesAsync does not provide atomicity which UnitOfWork pattern should provide, so again in real world you should have rollback mechanism for situation in which saving throws an exception half way through your dirty objects.

    • @RawCoding
      @RawCoding  Год назад +3

      good shout!

    • @vasiliyfofanov2605
      @vasiliyfofanov2605 Год назад

      @@RawCoding It's easy to use Decorate extension for IServiceCollection from Scrutor nuget package, IMHO

    • @dondaniel7643
      @dondaniel7643 Год назад

      @@Kosa39 I would love to see a gist of your solution on that. Would you catch the exception, delete all the inserted objects and throw a new exception? And I'm not following the first comment about solving it with a decorator (sry I'm plowing a lot of videos about this right now, not grasping it yet)

    • @Kosa39
      @Kosa39 Год назад +2

      Basically answer to the question: "How to handle exceptions when adding/modyfing multiple records in a atomic manner" is as always: It depends.
      You need to find out what happens in a given situation, maybe no rollback mechanism in your case would be sufficient enough. Let's say you're saving using unit of work items to the redis cache, half way through your objects redis crashes and exception is raised, now let's think how much damage it will do to the system. Well if it is just the cache, user requesting the data will have to reach out to the source system to get the fresh data, is it painfull ? Sometimes it would but more often not i would say, so you can just idk. log the exception and forget about it. However i would highly reconsider is the pattern used correctly, because we know that this is not atomic, and we cannot guarantee atomicity, so we have not acomplished main principle of this pattern.
      Now, another scenerio, in your UnitOfWork firing bunch of POST requests in attempt to store some data in some external API, again, halfway through API service dies (maybe because you spammed it so hard, lol), is it painfull ? I would say yes, you only saved half of the data that should be saved in a "all or nothing" manner (atomicity), at this point we already know that UnitOfWork pattern is misused in here, you cannot guarantee atomicity (it would be a different matter if your POST request accepted in the body list of the objects you're about to save, in that case you can guarantee the atomicity). Now we're entering the world of distributed computing etc. but in a nutshell you have three options:
      1. Eventually rollback commited data
      2. Eventually save everything (eventual consistency)
      3. Inform a user in some way that the operation succeded partially, and let him handle that
      There're numbers of ways to achieve this i.e. SAGA pattern for multi staged distributed transactions, Outbox pattern etc.
      For your second question about decorator pattern, I would simply use Scrutor nuget which has Decorate extension method and does everything for you. An example can be found here: andrewlock.net/adding-decorated-classes-to-the-asp.net-core-di-container-using-scrutor/#using-scrutor-to-register-decorators-with-the-asp-net-core-di-container

  • @adrian_franczak
    @adrian_franczak Год назад

    good stuff - never have chance to implement UoW so idea of saving this in middleware is something what I will save for the future - thanks

  • @CarlosMoreno-go8fx
    @CarlosMoreno-go8fx 8 месяцев назад

    it's the first video that i saw of your channel and i understood the name: "Raw Coding" hahaha nice content !

  • @gerarduab9960
    @gerarduab9960 Год назад +2

    I full agree with you. But what if several dbcontext must perform some transaction you ending with some kind of interface that manage that behavior between them? It's not considered a uow right?

    • @RawCoding
      @RawCoding  Год назад +4

      I've read the definition of UoW at the beginning, let me know if that sounds like one to you. hint: what's considered or what you might have heard might not be correct.
      imo if you need to use 2 dbcontext's in the same place, you fked up

  • @numannebuni
    @numannebuni Год назад +2

    These kind of videos is why I love this channel.

  • @kostasgkoutis8534
    @kostasgkoutis8534 Год назад +1

    Hmm, this explanation eerily reminds me of 2 phase commit.. In any case, buffering the changes to delay their flashing to datastore is one thing, but I didn't see how you would handle rollbacks in case of exceptions. How do you bring everything inside the body of the same transaction? Does the redit client do it automatically for you?

    • @RawCoding
      @RawCoding  Год назад

      no youd have to implement that

  • @milutinbeslic4754
    @milutinbeslic4754 Год назад +1

    Quick question
    Why do you remove redis cache service from the service collection? Won't redis implementation be overwritten by the Services.AddScoped(...)?

    • @RawCoding
      @RawCoding  Год назад +2

      correct, I unregister it because not many people know how to do it so I wanted to show it, and hopefully someone asks a question like you did.
      If we don't unregister there will be 2 implementations in DI, so you could resolve an IEnumerable which I didn't want.

    • @milutinbeslic4754
      @milutinbeslic4754 Год назад

      @@RawCoding Ah ok, fair enough... Threw me for a loop for a moment.
      Thanks for cutting through the bullshit with your videos!

  • @dmoth1610
    @dmoth1610 Год назад +2

    Great video, thanks! I also was questioning this myself a lot in the past. I feel that many RUclipsrs simply need to produce content, which is why they always talk about these buzzwords and patterns…
    However, I did come up with a situation where I personally liked to still have a wrapper around the DbContext, which is when having a large Domain layer in DDD with different kinds of persistence technologies. Because here, I feelt it useful to separate the persistence logic from the domain part completely. In this layered architecture, you have the hierarchy Application -> Persistence -> Domain, where a UnitOfWork type living in the Persistence layer proxies the SaveChanges call and is used to submit events from the aggregate root to an event bus within the same transaction. I like to use one repository per aggregate root to have an explicit bounded context that must be taken care of. This has the advantage that you can use different technologies as persistence store (like ef core + nosql) for different bounded context within the same application, but you can manage cross bounded context changes within one transaction.

  • @rankarat
    @rankarat Год назад +2

    Can you use larger fonts in your ide/editor?

    • @RawCoding
      @RawCoding  Год назад

      Are you watching on a phone?

  • @zagoskintoto
    @zagoskintoto 11 месяцев назад +1

    In case of EF it may look kind of ridiculous, but I think people mostly do it because "you have to implement the SaveChanges" somewhere. If you have an operation that calls for multiple repositories, following the uow you would have to just allow one call for save changes from the unit of work. If you expose that functionality in your repositories, it doesn't matter which one calls save changes if all of them use the same context. But yeah it looks kind of weird with EF.

  • @TheAzerue
    @TheAzerue Год назад +1

    Hi
    One point i didn't understand. DbContext implements UnitOfWork right. Normally its a scoped service means if it is requested many times during the life cycle of a request, we will get same instance and changes will not be written to database unless SaveChangesAsync is called. Why do we need IDistributedCache assuming we are not dealing with microservices. DbContext will temporarily hold the data until all the services are done and then finally changes can be written to database.

    • @RawCoding
      @RawCoding  Год назад

      Scoped means 1 per request.
      The reason we’re doing cache is because I’m illustrating what implementing a unit of work entails - it’s entity change tracking per scope.

  • @mdzieg
    @mdzieg Год назад +2

    so if we want to abstract data store implementation we should name it differently. what would you suggest? reason for this is to be able to replace ef with sth else without having to touch services layer.

    • @RawCoding
      @RawCoding  Год назад +3

      People talk a great deal about "abstracting data store implementation"
      But their wrapper/interface whatever will still rely on the underlying change tracking done by EF Core, effectively having a leaking abstraction, if you were to replace it with something else you'd have your whole app break.
      And then there is obviously the "easy to test" camp of people, again due to leaking abstraction your tests are useless in this scenario because they depend on the leaking behaviour.
      My suggestion is use DbContext directly, you are abstracting the data provider via their Provider implementations.

    • @Kosa39
      @Kosa39 Год назад

      ​@@RawCoding So basically what you're saying is that Clean Architecture or Port and Adapters architecture doesen't make any sens since it's all about seperating business concerns from the implementation details ?

    • @RawCoding
      @RawCoding  Год назад +2

      They do make sense, I'm saying DbContext is that separation

    • @Kosa39
      @Kosa39 Год назад

      @@RawCoding got ya

    • @roko567
      @roko567 Год назад +2

      @@RawCoding repositories are a leaky abstraction in this sense only if you assume that they commit changes. Which they should, if you're being dogmatic about the meaning of repositories. But from a pragmatic point of view, separating the DAL into repositories and unit of work makes testing and development so much easier and faster. As long as you don't assume that a repository is committing changes and assert that SaveChanges is called, the tests are not useless.

  • @mikewiebel
    @mikewiebel Год назад

    Subscribed 25 seconds in. Keep up the great work.

  • @soohyunjeon9348
    @soohyunjeon9348 5 месяцев назад +1

    I would like to purchase and watch your lectures. However, I am Korean and I hope that your paid lecture videos will have Korean subtitles. Is it possible?

  • @tanglesites
    @tanglesites Год назад

    Wow! I guess I am guilty of this too! I thought the UoW was just a way of abstracting out the Database implementation: aka My Repositories. But this makes more sense. It seems like UoW is a way of exposing as much of the DB API to the framework or application. This can be used to manage transactions, snapshots, and pooling, which is very interesting.

  • @zaharivaklinov
    @zaharivaklinov Год назад +1

    Overall, all of what you just showed us is already implemented by default by the Db context's UnitOfWork, correct?
    This seems too complicated to me, probably because you're not going into details around each of the lines of code.

    • @RawCoding
      @RawCoding  Год назад +1

      exactly, DbContext implements unit of work putting stuff around it and calling it unit of work is bullshit
      "change tracking" and "figuring out what to write to db" are capabilities of unit of work

  • @Metalyga
    @Metalyga Год назад +1

    UoW and clean architecture is most topics where people mash things together and not doing it in a proper way...

  • @gordonfreimann
    @gordonfreimann Год назад +1

    finally someone did put an end to this horse crap. btw redis has pipelines so you can bulk send the entire operations.

  • @anthonytrad
    @anthonytrad Год назад +2

    Great that you're investing more in the channel!
    Explaining the concept and use cases is what makes things usable...
    I totally agree with the shit part, wait till you see the DDD and "Clean Architecture" videos.

    • @RawCoding
      @RawCoding  Год назад +4

      I am planning on coming after the OOP & Architecture community later, need more data :D

    • @pyce.
      @pyce. 9 месяцев назад

      Yeah, cause anemic models are waaaay better...

  • @pavfrang
    @pavfrang Год назад

    Building UnitofWork around the EF context is silly, however it is more meaningful to build around Dapper connections.

  • @HighWeed08
    @HighWeed08 Год назад +1

    No more LinqPad?

  • @roko567
    @roko567 Год назад +6

    At the current project I'm working on, we're using the unit of work pattern to:
    - keep a buffer of integration events
    - ensure the integration events do not get flushed if the database transaction fails
    - implement a rollback functionality that allows us to discard events and tracked entity changes
    The last one is important because sometimes you want to discard and rollback the previous changes (entity modifications and integration events) and make new changes (for example, setting a job state to failed) within the same scope.

    • @henkoberholzer8081
      @henkoberholzer8081 Год назад

      I'm interested to know, how do you ensure the integration events are flushed after confirming that the database transaction succeeded? Could the system be left in an inconsistent state if flushing the events fails for some reason?

  • @austin.valentine
    @austin.valentine Год назад +1

    The issue is really more caused from needing custom Unit of Work and Repository wrapping EF. There are features that people want or need that aren’t built into EF that people are putting in their wrapper abstractions. Some that I have seen include caching, translation to DTOs, and simply defining a locked down but clear data access interface specifically to see preclude IQueryable abuse and be able to see all the “queries” in one place easily. I’m not saying all of these are good, but they are reasons. And since Microsoft hasn’t built and provided the necessary abstractions to reuse as a buffer layer, you have to build your own unfortunately. Most people do it half-ass and let the EF context UoW do the change tracking on the actual data, but they do everything else in their Unit of Work/repository, even if it is just writing a query and returning the results. The whole thing seems crummy and like it takes away the power of Ef, but people don’t want to deal with entities in their application or UI code

    • @RawCoding
      @RawCoding  Год назад

      Sounds like blind dogmatic belief

    • @austin.valentine
      @austin.valentine Год назад +1

      @@RawCoding To some extent, yes. But, which part are you referring to? Caching data temporarily for performance reasons is legitimate, so is translating to DTOs in certain contexts. So while there is generally some dogma involved with people using “UoW/Repository over EF”, you can see why they think it is necessary, right? I am asking you to provide an alternative explanation because I have learned things on your channel.

    • @RawCoding
      @RawCoding  Год назад

      I’m referring to bolting things on to / around the db context and calling it unit of work.

    • @austin.valentine
      @austin.valentine Год назад +2

      @@RawCoding I don’t think we disagree, but what you are saying is vague. As a counterpoint, doesn’t it follow logically that extending a library unit of work needs to still support and “hook into” that unit of work? I am trying to nail down the crux of what makes people do this specifically with EF. Your point about the general concept of unit of work is correct, I am not disagreeing there. But instead of trying to understand why people do this with specifically over EF, you demonstrated the general concept of unit of work using a completely unrelated example. While I think that is helpful in its own right, it fails to address why people do the behavior you are addressing. It comes down to more than just not understanding what unit of work is, even though I agree that many don’t. The issue is needing to extend EFs unit of work functionality and/or provide abstraction from Ef. There is some legitimacy to those ideas (not that the conclusions are correct) that I think addressing would clear the air more in addition to the content in this video, which I am saying is good; I’m not disagreeing with it. It just isn’t enough.

    • @RawCoding
      @RawCoding  Год назад

      It’s hard to explain why, tho let’s start with a list of how people try to extend the dbcontext
      1. The main thing I see is they wrap the dbcontext behind interfaces to remove a dependency (which is bs) or to make it more testable (which is also bs)
      They rely on change tracking behind the interface which the interface doesn’t guarantee, so it’s a bad interface and bad tests which don’t test what actually happens. People can’t see this because they just took someone elses word for it that this is best practice, how can you not when Microsoft blog says this.
      If you can continue this list I’ll let you know which points are legit and perhaps why people try to bolt this on to the dbcontext

  • @TheHrt5
    @TheHrt5 Год назад +1

    Isnt the whole point of implementing UoW on your own in order to be able to swap out implementation without worrying about it in the future ? You mentioned that EF Core already has that built in so that adding a UoW "wrapper" is "bunch of bullshit". But what if you decide to replace EF Core with something which does not have UoW implemented in it ? Isnt that the whole point of implementing additional wrapper ? So that you always have UoW ready, regardless of the implementation ? Im pretty sure that most of the "youtubers" you called out are quite aware of the fact that EF already uses UoW. I mean, im not a professional, but even to me it was quite obvious the moment I started using EF.

    • @RawCoding
      @RawCoding  Год назад

      As soon as you swap out ef core it’s no longer unit of work because no tracking is happening of what should be written back to the database. You have to make sure whatever you are swapping it to has UoF under the hood as well.
      So no interfaces and wrappers are not unit of work.

    • @TheHrt5
      @TheHrt5 Год назад

      @@RawCoding Hmm, valid point. Will try to dive more into this once I have more time. Thanks for the explanation.

  • @ivandrofly
    @ivandrofly Год назад +2

    I like how you call "shit" by its name 😂😂

  • @quranthecompanion4528
    @quranthecompanion4528 Год назад

    bro, spot on. there's a lot bandwagons out there.

  • @meetingattender8132
    @meetingattender8132 Год назад +1

    Lmao this was awesome

  • @pyce.
    @pyce. 9 месяцев назад

    The purpose of hiding DbContext behind an interface is to be implementation agnostic.
    You did demonstrate how to implement unit of work for something that doesn't support it built-in, but you failed to point out why hiding DbContext's existence (which comes from a library you might not want to depend on later) is, as you phrased, "shit".

  • @thethomasproject
    @thethomasproject 9 месяцев назад

    This is great but the real problem here is that there isn't a good overall tutorial that takes you from start to finish, explaining the proper way to implement. Your tutorial does point out the problems, but I'd love to see something a little more expansive. Remember some of us are a bit dumber, lol.

  • @julealgon
    @julealgon Год назад

    This video does a disservice to the community in how the decorator is registered. Building the service provider manually like that is a _terrible_ practice that one should _never_ do. If you need a decorator registration, use a manual factory registration or even better, rely on Scrutor to do it for you.
    It's ironic how you called out other youtubers for misrepresenting the unit of work pattern and then you go and create a video that spreads an absolutely terrible practice due to sheer lack of skill and/or understanding.
    You should consider doing another video correcting this massive mistake at least so your community is not too negatively affected.

  • @pickle1987
    @pickle1987 7 месяцев назад

    Actually you don't really understand why people use UoW

    • @diamondkingdiamond6289
      @diamondkingdiamond6289 2 месяца назад

      Is there anything this implementation would not cover for a reason you would use the UoW pattern?

  • @dimitryjo0n
    @dimitryjo0n 4 месяца назад

    One of the worst teaching videos I've ever seen is this video. He is working for himself and explain for himself. Too bad!