What is the point of Specifications, because what I see is that you are replacing 3-5 lines of explicit dbContext querying with multiple classes just to encapsulate a set of expressions applied to dbContext?
Honestly, when I first heard of the specification pattern it was about applying complex validations on entities, but this approach is also useful and can get even more cleaner with extension methods on the IQueryable class, which will preserve the functional style you are tending to use and make you repos cleaner because it usually scales vertically and gets very messy.
@@muhammadessam7062 i even prefer to just compose functions (railway oriented programming) than extend iqueryable with methods that don't fit in my view with what the function of that iqueryable is.
You're missing the point because the example is simple. It makes a lot more sense when you have more complex queries, need to include different navigations on different paths etc
I thought so too. I was hoping to see some use cases applied on the specification class or the evaluator that shows the point of moving query there which would probably make applying this pattern useful.
@@MilanJovanovicTech I can still do that explicitly with no indirection caused by Specification. The only reason I'd opt for it is code deduplication, if a query is used in more than 1 place and they are using the same underlying data schema/source.
I think part of the point of specifications is that you don't keep adding (OPEN CLOSED) methods to your repo, but accept the specifications which extend your repository. But, you just rewrote your repo methods to call your specifications when I don't think really shows off the power of this pattern.
You're right on that one! I sometimes focus too much on the concept and implementation that I miss obvious improvements like that. it's even how I used the Specification in a real project - GetBySpecification method + many Specification classes
Great job! but for me specification pattern: - a lot of redundant code - the code is unreadable Of course this is just my opinion but I'm curious why you recommend this pattern? 👍😎
I don't recommend anything. I present options. It's up to you to decide if this makes sense for your project or no. I have used this pattern with a lot of success on a few projects. But if you have a simple app, it's too much.
Thank you for this video! In the Domain-Driven Design Fundamentals course on pluralsight, it says that specifications should belong in the domain model. This is to keep business logic out of persistence layer. I think it is another advantage of using the specification pattern :)
Good video and interesting pattern. I like that it makes the repository class cleaner but it does feel like entity framework with a lot of extra steps.
Hi Milan, thanks for your ideas and videos. Imho, using Repository pattern in case of EF is already Over engineering. Anyway, thank you for opening the real example of specification pattern itself.
I don't think it's over engineering in the way that I use it. I do so only on the WRITE side of the application. And on the READ side, I use EF or SQL.
We use this everywhere at work and it always puzzled me, like, a lot. This is a really good explanation of it, but I still wonder why some folks like this so much.
@@MilanJovanovicTech they explained that is made unit testing much easier, but I could also see the case that unit testing isn't that hard either with in-memory DB either. Thanks for making this video, I will now checkout your newsletter and other things.
awesome Milan, in very short time your channel getting popular because it is due to you hard work and dedication. I like your teaching way. it is really understandable.
Again, the example is simple. Try to think of the concept in general. I see people missing the forest for the trees often with my videos, because they latch onto the implementation in the example
@@MilanJovanovicTech Don't mean to be rude, don't mistake that for genuine interest in code quality. I'm saying that the examples need to be more practical. This example in my opinion is not practical. Think that so many developers are going to see your videos, love what you do, take you as their teacher, and go in life creating overly-complex systems because they take your examples as good practice and then their projects become unmaintainable messes.
@@PaulSebastianM You can never stop people from building overly complex systems. Neither should any content creator be to blame for lack of technical reasoning. I'm trying to present concepts I don't see others talk about. I agree that the examples can be more practical, but until I can do this full-time, I don't have enough time to commit to that.
Hello Milan. Thank you very much for this video. Now I combine the generic repository pattern with the specification pattern to make my hexagonal architecture simpler and more maintainable 🙌🏼🙌🏼👏🏻👏🏻
Wrapping EF Core in repository and specification offers nothing really. “Abstracting your code from EF Core” is not a valid argument IMO. In the real world, no one builds a project with EF and then later replaces it with something else. “Tight coupling” - so what? Just use EF Core dbContext directly, and have a much simpler code base that is easy to understand and modify. If you want to do unit testing, and are unable/unwilling to use an in memory database, you can still create an interface for your dbcontext to implement. I did however really enjoy this video. I do like some of these OOP patterns in theory, just not in practice.
@@moneymaker7307 My contention would be that I do not think that a “common query” exists in an application. IMO if 2 pieces of functionality use the “same” query, it’s coincidence.
@@kabal911 yes they most of the time when you write a query to find a resource by Id, then that query exist for all the other resources in your code base.
@@moneymaker7307 I just don’t see the real world value in essentially wrapping “dbContext.Products.Find” in another object. I also don’t really consider load by ID a “query”. When I am thinking “query”, I am thinking feature A loads a product and includes reviews, while feature B loads a product but includes category. Being able to achieve that with a generic repository where I need to use Specification (or similar), again seems needless and offers no actual real world advantage. I’ve used repositories (generic and specialized) for most of my career (mostly Java + SpringDataJpa). But when it comes to EF Core, I do not believe that abstracting it offers any financial or human advantage. It does not result in safer to change code. It does not result in easier to change/work on code.
Hello Milan, thanks for the video, great effort and well explained. Can you please address the case when I need to use the (ThenInclude) functionality of ef core in this pattern again thanks
Great question! So you have one option which will work with this implementation. Instead of ThenInclude, just include to the level of nesting you need: AddInclude(x => x.Navigation1.NestedNavigation) You can also write a string include implementation, it works with EF. But I didn't show it in the video. You need a List, and apply them the same way with Aggregate.
I always wonder when the additional layer of abstraction stops giving you benefits. I really like an approach, where you work with IQueryable the whole time. You write extension methods like BuildProduct to include all the properties, or to group together some common operations. If there is a lot of properties, you can make a QueryDto and extension method that applies filters, orderBy etc onto the IQeuryable. You end up with something akin to builder pattern, which is understandable even by people who don't know what a builder pattern is :) I like this approch, because there is always some edge case that you need to account for (like to SplitQuery in your example, possibly used only in this one endpoint, but now you have a whole class to represent that case). Having the IQueryable accessible gives you so much flexibillity, and in practice saves a lot of DB trips. How many times did you use your repository to query a list just to do FirstOrDefaut() or Any() on it?
How many times did I query for a list to return FirstOrDefault or Any? Never. Okay, maybe when I first worked with EF 5 years ago. 😅 I also like your approach with extension methods, great option
Too much unneeded abstraction IMO. The pitfalls of OOP. I prefer a simple functional approach with clear simple code that doesn't make me jump around the codebase to understand the entire logic behind a request handler.
@@PaulSebastianM It is not unneeded abstraction. It is just wrong abstraction. What's the benefit of doing this instead of using extension methods on DbSet objects? Except for being fancy.
I think it only makes sense when you can separate your implementation from your interface. When you are going to use IQueryable you are just commiting to c# expression api and EF Core and there is nothing wrong with that approach. In this example we got complexity of custom soultion but no benefit as we are still tied to c# Expression api by our base class.
@@NoGamer256 I agree, but IQueryable and expression API is the way to go in C#. On the other hand, if you return that IQueryable from a repository, in principle, you can replace EF Core with anything: nHibernate also return IQueryable, you can write your own IQueryable to SQL converter and use Dapper or just plain old ADO. Yes, probably some behaviours will change. IQueryable is the abstraction here. Quite low level, but it has nothing to do with EF Core, it even lives in System.LINQ. It was designed to abstract persistent data sources, just like IEnumerable abstracts in memory collections.
Hi, I like your Channel, I'm using specification at the domain level to be able to use them both in the domain validations both in the queries. You are using It in the infrastructure layer but I like your method!
great video, I would've loved if you included another powerful use case for specification design pattern where you can use it for validations. you can use the expression to query the db. also, you can use expression compile method to validate your input.
@@MilanJovanovicTech Hi Milan, you promised to do this in a previous answer to this question in this video, take a look, now it's up to you to decide whether to do it.
I remember those times when EF didn't exist and queries were being written in the code. Nowadays this is a huge pain for monoliths having all this stuff super hard to rework. Furthermore, not queries themselves but a wrapped up infrastructure is a monster. While relying on a specific implementation, like EF, one day in the future that will be a new pain again. I'm not even going to emphasize the EF issues, specific to the topic, like collecting related data for related tables (properties), but lets assume you have to migrate to mongodb one day. No EF anymore. The approach based on expressions will not just smoothly be working. What I'm trying to say. I respect the author of this video, you are doing a truly great job. But I would carefully think about if this approach is really what I need. If you Milan find time to implement the same stuff for a nosql database I bet you will improve your solution.
Great video, thanks for sharing! I just have a quick question: why should we use Expression instead of just Func? Is it because EF uses expressions to convert LINQ to SQL?
Yes that's the reason, in the end, you saw that the criteria you send to the specification object is used on calling the queryable object, so that's why.
For second time - Greetings from Bulgaria. Thank you for your time and efforts to create and share those nice tutorials. Well, this time you did not win me. I fail to understand. From one class with 20 lines of code you made 5 classes with 50 lines of code !!!??? And one single change will require going through layers of classes.
Makes sense, but imagine having to reuse these specifications in quite a few places - then there's more value to it. Check out this repo also: github.com/ardalis/Specification
Hello Milan. Thanks for the video. It looks like I missed what was the reason to implement Specification Pattern here. In generally Specification pattern here is a wrapper on IQueryable which allows you do not depend on EF Core and implement Queries on business logic level and potentially you can use the same specifications for Mongo database (as an example). But what problem was solved in this case? I'm asking because I've heard about that approach many years ago, but didn't find a reason to apply into my project because no understanding why this is better then what you had before or bettern then extension methods. Thanks!
This implementation is EF specific. Perhaps the example is too simple for the pattern to make sense. Try to think of more complex queries with a lot of includes in different methods.
@@MilanJovanovicTech But what the issue having that implementation in the method of repository class? I don't think it can be bigger then 100 lines of code. I'm using projections and I think most of the case you need to use projections for optimal queries. And once you use it you don't need to write .Include(). EF will do that automatically for you. Projections itself I have in DTO clases responsible for projections. So, the only code I have in repository pattern is Where conditions.
@@volodymyrliashenko1024 No worries, I never mind my ideas being challenged 😁 I use repositories on the read side, so it makes sense to leave the Includes. If I want to use projections, that's going to be on the read side where I return data from the API. In which case the Specification pattern probably makes little sense. Agreed.
Hi Milan, first of all thank you very much for the rare content you bring us. Secondly, I saw that a lot of people said that it should be done in the Domain and I also think so, the question is what to do with functions that belong only to EF such as Include, how can you access them in the DOMAIN and still keep the domain clean.
You can't place this in the domain without leaking implementation details Maybe just place the WHERE filter in the specification, that can freely go in the domain
By instantiating a concretion to your low level module (which is the repository) wouldn't you create a tight couple between your now high level module (repo) to your Specifications? Even if you implement a generic interface ISpecification and then have them ready in your container, what would you do if you have to change your repo to not use any specification?
That's such a low level decision. You'd decide at the start what path you want to take (think carefully). And commit to it. If you decide on a whim to change implementations, that says a lot...
@@MilanJovanovicTech Hey Milan how are you? Glad you decide to respond. Even though principles are there if you want to follow them, I would said D in SOLID is very important (well... all of them), if in 3 years the company want to change db providers that don't work well with LINQ to SQL then this approach wont work as your Specifications uses LINQ as you are using the concretions directly (which you tie your I/GaterhingRepository to marry db providers that uses LINQ to SQL), but usually EF Core has a lot of NuGet packages to work with those other engines anyway. In regards changing implementations on a whim I would say... welcome to IT? This happens a lot and the people from the top decides to make this big chances surely for financial reasons more than other. Anyway, great content!
@@AnotherFancyUser If you change from SQL to something else (MongoDb) you're screwed either way. You have to rewrite everything. If you change from SQL Server to PostgreSQL, you most likely won't have any issues. Just switch EF packages. What other use case do you have in mind? 🤔
Great video and explanation. I started using Steve "Ardalis" Smith's implementation of the specification pattern a while back, and this is a great breakdown of how to implement it yourself. Love the channel, keep up the great work! What's your opinion on having a more generic Repo method that takes and applies a specification as a param, rather than creating huge numbers of custom repo methods that implement specific specifications?
I would also suggest using Steve's library, since it's awesome and more robust than what I showed. I think that's a nice option for sure, but I prefer having well named repository methods. Nothing wrong with either approach. Just a personal preference.
I am fed up with abstractions. In my latest project I got rid of repository pattern altogether, just injecting DbContext directly and doing EF Core Linq queries from my handlers. What Milan shows goes in the opposite direction - he has not one but two abstractions: specification pattern and then ageneric repository on top of it. Not sure where the value is though. Nice video though, informative.
I think all abstractions make sense in a given context. It's up to you to figure it if this makes sense to use or not. I don't use Specifications all too often, but I find it an interesting pattern.
Thanks, bro what if I need more than one condition in the where clause, for example, I need Employee where the name is "Patrick" and IsPresent. How to implement that here
I don't think that's a good idea. It is ilusion of abstraction. Yes your specification class doesn't depend on Expressions but wait your base class does. You couple your specification defining objects with c# expression api cause you know that you are going to use EF Core in your repository. In proper implementation your specification object should be only translated by separate module specific to your infrastructure. So when you use EF Core you will translate specification to expression, when you use raw sql you will translate it to raw sql and so on. Now does it makes sense and is it easy? No. Propably very hard. I would just use raw db context instead with correctly named extension methods to encapsulate query logic.
Hi Milan nice video. One question though which stop me from using it, is it possible to inject a specification via dependency injection? I would like to to inject a specification from outside. Let's say, in case of TDD, I would like to use a mocked or stubbed version of that specification, can I do that ?
I couldn't pass my point around. Sorry, my query was, if I can inject the, i.e... gatherby..specification to a mediatr command or query? I don't want to new that up inside a mediatr handler.
The specifications are now defined in the persistence layer. Should you not define them in Domain or Application, because how and what we retrieve from the database is Domain specific, is it not?
Great video made me have several ideas on how to improve my projects. A doubt that came to my mind is it a good idea to pass the specification as a parameter to the getbyid and getall functions? to have a default behavior and a specific one as I need?
You can use the existing solution, if it's not a list: AddInclude(x => x.Navigation1.Navigation2) Or implement the includes using strings instead of expressions Also, check out ardalis/Specification for a better implementation
Hi Milan! Firstly, thanks for the great video! As I read through the comments below, you and others mentioned that instead of creating specific repository methods like GetBySomethingWithXAndY writing only a GetBySpecification method would be OCP complient and more elegant. This implementation would imply that the Specification class should be moved to the Application layer so that the derived classes of the Specification could be instantiated and given to the proper GetBySpecification methods in the CQRS IRequestHandler implementations. However, this would mean that through Specification EF-specific details would be placed (eg IsSplitQuery) in the Application layer. Isn't that a break of the Clean Code Architecture? Or is there something that I misunderstood? What is your opinion?
That's correct. You would be introducing EF specific details into Application/Domain layer (not directly, but as you mentioned). Are you okay with that tradeoff?
How would you handle the case where a certain entity's repository (with its includes) needs to be reused in another repository (to not duplicate the same includes)?
@@MilanJovanovicTech Excuse me, but I don't understand how the following case could be handled: suppose I have Customer with his contact details. The specification could give me a way to get the Customers with the inclusion of the contact details. But when do I have to reuse customers with contact details within an Order specification? Suppose I need an Order with Customer details including contact details. It seems to me that I should still reply to the include contact details also in the order. Maybe I'm missing something, or I don't fully understand.
Really nice video, I followed along in my own project and got great results, simplifying my repository logic. Could this approach by used with projection as well, or do you prefer to project in service layer after data has been returned from the database?
@@MilanJovanovicTech Your example only returned FirstOrDefault but for returning a list of Companies where you only need Id and Name it would be a shame not to use the projection option in EF to elect only those two fieldsfrom the database. Concrete example: A customer picker where you want to select 1 customer but allow selection based on company name and the user can start typing until the list of (take 20) contains the customer. Here it would ba a shame to return an entire top 25 customers when You are only interested in the name and id
Does this only work if both mt specification and my dbcontext use the domain object? (TEntity). What if i want different models representing my db tables which are separate from my domain objects. Where do i map these two different models?
For ThenInclude add the following to Specification.cs: Specification.cs public List IncludeStrings { get; } = new List(); protected virtual void AddInclude(string includeString) { IncludeStrings.Add(includeString); } And here an example of a specifications function with ThenInclude for the Gathering entitie: using Gatherly.Domain.Entities; namespace Gatherly.Persistence.Specifications; internal class GatheringByNameSpecification : Specification { public GatheringByNameSpecification(string name) : base(gathering => string.IsNullOrEmpty(name) || gathering.Name.Contains(name)) { AddInclude(gathering => gathering.Attendees); AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}"); } } And in the SpecificationEvaluator.cs add: queryable = specification.IncludeStrings.Aggregate(queryable, (current, include) => current.Include(include)); * => this part is the ThenInclude: AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}"); Here we assume there is a table with Attendee skills
I'm using specification pattern same way as described in a video. And recently I faced with a problem of using Left Outer Join to access to included entities. Do you know how to use specification patter along with left outer join? (All information a googled provide only sql style examples)
There's always a situation where these patterns fall on their face. I would check if Ardalis.Specification package has a solution for that. Otherwise, just implement it with EF Core
@@MilanJovanovicTech I solved my problem with left outer join locally by adding to my table two "MyRefId" and "MyRefIdOriginal" columns. I filled both of them on created. And use one for history purpose and one fo Left Outer Join simulation by setting cascade set null for this field. So now I can just filter out rows by RefId is null. This allows me to use specification pattern. Any way if any information about left outer join with specification - please let us know.
aspx is very dated nowadays, burdening many projects that are using .NET Framework from moving to .NET Core/5+. Razor pages are the more modern approach, utilizing a similar syntax
AsNoTracking the same approach as SplitQuery. Add a flag, and if true, apply AsNoTracking. For ThenInclude, you can do AddInclude(x => x.Navigation1.Navigation2) Or, you can add support for string includes
For ThenInclude add the following to Specification.cs: Specification.cs public List IncludeStrings { get; } = new List(); protected virtual void AddInclude(string includeString) { IncludeStrings.Add(includeString); } And here an example of a specifications function with ThenInclude for the Gathering entitie: using Gatherly.Domain.Entities; namespace Gatherly.Persistence.Specifications; internal class GatheringByNameSpecification : Specification { public GatheringByNameSpecification(string name) : base(gathering => string.IsNullOrEmpty(name) || gathering.Name.Contains(name)) { AddInclude(gathering => gathering.Attendees); AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}"); } } And in the SpecificationEvaluator.cs add: queryable = specification.IncludeStrings.Aggregate(queryable, (current, include) => current.Include(include)); * => this part is the ThenInclude: AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}"); Here we assume there is a table with Attendee skills
@@MilanJovanovicTech, Thank you some much. Let me check, But I'm not fan of using strings in Include. All your video's are very much useful. Keep rocking 🙂
@@vishoek69 String include approach becomes hardly supported with rich domain model and inheritance. Maybe it can be accomplished with some fluent includes builder
Instead of ThenInclude, just include to the level of nesting you need: AddInclude(x => x.Navigation1.NestedNavigation) You can also write a string include implementation, it works with EF. But I didn't show it in the video. You need a List, and apply them the same way with Aggregate.
@@MilanJovanovicTech Thanks for the reply. The problem with level nesting is when x.Navigation1 is a list you can’t go to x.Navigation2. But I might take look at string list implementation.
For ThenInclude add the following to Specification.cs: Specification.cs public List IncludeStrings { get; } = new List(); protected virtual void AddInclude(string includeString) { IncludeStrings.Add(includeString); } And here an example of a specifications function with ThenInclude for the Gathering entitie: using Gatherly.Domain.Entities; namespace Gatherly.Persistence.Specifications; internal class GatheringByNameSpecification : Specification { public GatheringByNameSpecification(string name) : base(gathering => string.IsNullOrEmpty(name) || gathering.Name.Contains(name)) { AddInclude(gathering => gathering.Attendees); AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}"); } } * => this part is the ThenInclude: AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}"); Here we assume there is a table with Attendee skills
@@MilanJovanovicTech How about using the already mentioned "IncludeStings" implemention and some additional methods in the Specification.cs protected void AddInclude(Expression includeExpression, Expression thenIncludeExpression) where TSubEntity : BaseEntity { var path = GetExpressionMemberPath(includeExpression.Body).Select(e => e.Member.Name).ToList(); path.AddRange(GetExpressionMemberPath(thenIncludeExpression.Body).Select(e => e.Member.Name)); string queryString = string.Join('.', path); this.AddInclude(queryString); } private IEnumerable GetExpressionMemberPath(Expression expression) { if (expression is MemberExpression memberExpression) { foreach (var item in GetExpressionMemberPath(memberExpression.Expression!)) { yield return item; } yield return memberExpression; } } Usage: this.AddInclude(x=> x.Navigation1, navigation1 => navigation1.Navigation2); It will create the required string "s.Navigation1.Navigation2" for you in the background, where the nested Navigation1 is an enumerable.
Entity Framework and anonymous types totally do away with the need to use ridiculous design patterns that result in unneccessary code. Looking at you "repository" and "unit of work". Why add additional code that just bloats the footprint, increases the learning curve for other, and adds unnecessary complexity. Design patterns here are not needed! The DBContext can easily be added as a service and mocked for unit tests. EF itself can handle multiple back end databases, as well as other providers. Just stop with the patterns! The best code is the code you don't need to write. Design pattern zealots, please tell me where you work, so I don't apply there, and have to unravel the design-pattern mess that "only you" think is brilliant.
would it make sense to have a generic specification that gets passed as an argument to the repository? this way you could have powerful findOne/findMany repository methods, that do not directly rely on entity core and would allow for future refactors to use something else
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
What is the point of Specifications, because what I see is that you are replacing 3-5 lines of explicit dbContext querying with multiple classes just to encapsulate a set of expressions applied to dbContext?
Honestly, when I first heard of the specification pattern it was about applying complex validations on entities, but this approach is also useful and can get even more cleaner with extension methods on the IQueryable class, which will preserve the functional style you are tending to use and make you repos cleaner because it usually scales vertically and gets very messy.
@@muhammadessam7062 i even prefer to just compose functions (railway oriented programming) than extend iqueryable with methods that don't fit in my view with what the function of that iqueryable is.
You're missing the point because the example is simple. It makes a lot more sense when you have more complex queries, need to include different navigations on different paths etc
I thought so too. I was hoping to see some use cases applied on the specification class or the evaluator that shows the point of moving query there which would probably make applying this pattern useful.
@@MilanJovanovicTech I can still do that explicitly with no indirection caused by Specification. The only reason I'd opt for it is code deduplication, if a query is used in more than 1 place and they are using the same underlying data schema/source.
I think part of the point of specifications is that you don't keep adding (OPEN CLOSED) methods to your repo, but accept the specifications which extend your repository. But, you just rewrote your repo methods to call your specifications when I don't think really shows off the power of this pattern.
You're right on that one! I sometimes focus too much on the concept and implementation that I miss obvious improvements like that.
it's even how I used the Specification in a real project - GetBySpecification method + many Specification classes
Great job! but for me specification pattern:
- a lot of redundant code
- the code is unreadable
Of course this is just my opinion but I'm curious why you recommend this pattern? 👍😎
I don't recommend anything. I present options. It's up to you to decide if this makes sense for your project or no.
I have used this pattern with a lot of success on a few projects.
But if you have a simple app, it's too much.
Everytime I watch your videos, it makes me feel that I have not worked in the right way at all. Keep it up, Milan ❤
I'm sorry if you feel that way 😅 Make sure to take everything I show with a grain of salt. Never assume I'm 100% correct.
Thank you for this video!
In the Domain-Driven Design Fundamentals course on pluralsight, it says that specifications should belong in the domain model. This is to keep business logic out of persistence layer. I think it is another advantage of using the specification pattern :)
You make a good point there David 😁
Good video and interesting pattern. I like that it makes the repository class cleaner but it does feel like entity framework with a lot of extra steps.
I've used it on a project with many different queries on the same entity, and it shined there.
But it can certainly be overkill for simple projects.
Hi Milan, thanks for your ideas and videos. Imho, using Repository pattern in case of EF is already Over engineering. Anyway, thank you for opening the real example of specification pattern itself.
I don't think it's over engineering in the way that I use it. I do so only on the WRITE side of the application.
And on the READ side, I use EF or SQL.
We use this everywhere at work and it always puzzled me, like, a lot. This is a really good explanation of it, but I still wonder why some folks like this so much.
I think it can be useful when there is a lot of repetitive queries. And it can be overkill in other situations.
@@MilanJovanovicTech they explained that is made unit testing much easier, but I could also see the case that unit testing isn't that hard either with in-memory DB either. Thanks for making this video, I will now checkout your newsletter and other things.
awesome Milan, in very short time your channel getting popular because it is due to you hard work and dedication.
I like your teaching way. it is really understandable.
Thank you very much! 😊
12:35 did you say simplify? IMO, honestly, it looks like a lot of ceremony replacing something that was much more explicit and clear.
Again, the example is simple. Try to think of the concept in general. I see people missing the forest for the trees often with my videos, because they latch onto the implementation in the example
@@MilanJovanovicTech Don't mean to be rude, don't mistake that for genuine interest in code quality. I'm saying that the examples need to be more practical. This example in my opinion is not practical. Think that so many developers are going to see your videos, love what you do, take you as their teacher, and go in life creating overly-complex systems because they take your examples as good practice and then their projects become unmaintainable messes.
@@PaulSebastianM You can never stop people from building overly complex systems. Neither should any content creator be to blame for lack of technical reasoning. I'm trying to present concepts I don't see others talk about. I agree that the examples can be more practical, but until I can do this full-time, I don't have enough time to commit to that.
@@MilanJovanovicTech Totally understand.
Hello Milan. Thank you very much for this video. Now I combine the generic repository pattern with the specification pattern to make my hexagonal architecture simpler and more maintainable 🙌🏼🙌🏼👏🏻👏🏻
I'm not a fan of generic repository, but if it works for you that's excellent!
Nice job explaining this somewhat important topic. I’d rather return the task rather than overusing the async/await in your repository.
Thank you
It won't matter that much really
Really? Yesterday I was looking for this in your channel and didnt find it. And today you posted it. 😂 Thank you for all your videos
I've been told I read minds before 🤣
Amazing so many ideas comes to mind just watching this video, nested IncludableQueryable, Projections, etc .. thanks milan.
Glad it was helpful!
I've never smashed a like button more than I did just now. Great video! 🙂
I ejoyed saying that too much in the video. And your comment made it even better now 🤣❤️
Wrapping EF Core in repository and specification offers nothing really. “Abstracting your code from EF Core” is not a valid argument IMO. In the real world, no one builds a project with EF and then later replaces it with something else. “Tight coupling” - so what?
Just use EF Core dbContext directly, and have a much simpler code base that is easy to understand and modify.
If you want to do unit testing, and are unable/unwilling to use an in memory database, you can still create an interface for your dbcontext to implement.
I did however really enjoy this video. I do like some of these OOP patterns in theory, just not in practice.
It's just another tool in the toolbox. It made sense on some projects I worked on, tbh. But I agree it's too much for simple use cases
Using a generic repository for common queries quite beneficial. It make absolutely zero sense to litter d code base with where I’d this and that.
@@moneymaker7307 My contention would be that I do not think that a “common query” exists in an application. IMO if 2 pieces of functionality use the “same” query, it’s coincidence.
@@kabal911 yes they most of the time when you write a query to find a resource by Id, then that query exist for all the other resources in your code base.
@@moneymaker7307 I just don’t see the real world value in essentially wrapping “dbContext.Products.Find” in another object.
I also don’t really consider load by ID a “query”.
When I am thinking “query”, I am thinking feature A loads a product and includes reviews, while feature B loads a product but includes category.
Being able to achieve that with a generic repository where I need to use Specification (or similar), again seems needless and offers no actual real world advantage.
I’ve used repositories (generic and specialized) for most of my career (mostly Java + SpringDataJpa).
But when it comes to EF Core, I do not believe that abstracting it offers any financial or human advantage. It does not result in safer to change code. It does not result in easier to change/work on code.
Great video. This was the one I was looking for after last video.
Glad you liked it 😁
Hello Milan, thanks for the video, great effort and well explained.
Can you please address the case when I need to use the (ThenInclude) functionality of ef core in this pattern
again thanks
Great question! So you have one option which will work with this implementation.
Instead of ThenInclude, just include to the level of nesting you need:
AddInclude(x => x.Navigation1.NestedNavigation)
You can also write a string include implementation, it works with EF. But I didn't show it in the video.
You need a List, and apply them the same way with Aggregate.
@@MilanJovanovicTech nesting does not work.
I always love your videos. Always simple and important.
Thank you very much!
I always wonder when the additional layer of abstraction stops giving you benefits. I really like an approach, where you work with IQueryable the whole time. You write extension methods like BuildProduct to include all the properties, or to group together some common operations. If there is a lot of properties, you can make a QueryDto and extension method that applies filters, orderBy etc onto the IQeuryable. You end up with something akin to builder pattern, which is understandable even by people who don't know what a builder pattern is :)
I like this approch, because there is always some edge case that you need to account for (like to SplitQuery in your example, possibly used only in this one endpoint, but now you have a whole class to represent that case). Having the IQueryable accessible gives you so much flexibillity, and in practice saves a lot of DB trips. How many times did you use your repository to query a list just to do FirstOrDefaut() or Any() on it?
How many times did I query for a list to return FirstOrDefault or Any? Never.
Okay, maybe when I first worked with EF 5 years ago. 😅
I also like your approach with extension methods, great option
Too much unneeded abstraction IMO. The pitfalls of OOP. I prefer a simple functional approach with clear simple code that doesn't make me jump around the codebase to understand the entire logic behind a request handler.
@@PaulSebastianM It is not unneeded abstraction. It is just wrong abstraction. What's the benefit of doing this instead of using extension methods on DbSet objects? Except for being fancy.
I think it only makes sense when you can separate your implementation from your interface. When you are going to use IQueryable you are just commiting to c# expression api and EF Core and there is nothing wrong with that approach. In this example we got complexity of custom soultion but no benefit as we are still tied to c# Expression api by our base class.
@@NoGamer256 I agree, but IQueryable and expression API is the way to go in C#.
On the other hand, if you return that IQueryable from a repository, in principle, you can replace EF Core with anything: nHibernate also return IQueryable, you can write your own IQueryable to SQL converter and use Dapper or just plain old ADO. Yes, probably some behaviours will change.
IQueryable is the abstraction here. Quite low level, but it has nothing to do with EF Core, it even lives in System.LINQ. It was designed to abstract persistent data sources, just like IEnumerable abstracts in memory collections.
Hi, I like your Channel, I'm using specification at the domain level to be able to use them both in the domain validations both in the queries. You are using It in the infrastructure layer but I like your method!
Thanks! Nothing is stopping us from moving it to Domain layer
great video, I would've loved if you included another powerful use case for specification design pattern where you can use it for validations.
you can use the expression to query the db. also, you can use expression compile method to validate your input.
I haven't applied like that to be honest.
Hi Milan, I look forward to part 2 with a pagination implementation with this pattern.
Should I do that? 🤔
@@MilanJovanovicTech Hi Milan, you promised to do this in a previous answer to this question in this video, take a look, now it's up to you to decide whether to do it.
I have a feeling that you saw that the Count was 0 at 18:55 😅. Great video once again!
I left a popup that I made a mistake when implementing the Includes :(
Forgot to assign result of Aggregate back to the queryable
I remember those times when EF didn't exist and queries were being written in the code. Nowadays this is a huge pain for monoliths having all this stuff super hard to rework. Furthermore, not queries themselves but a wrapped up infrastructure is a monster.
While relying on a specific implementation, like EF, one day in the future that will be a new pain again. I'm not even going to emphasize the EF issues, specific to the topic, like collecting related data for related tables (properties), but lets assume you have to migrate to mongodb one day. No EF anymore. The approach based on expressions will not just smoothly be working.
What I'm trying to say. I respect the author of this video, you are doing a truly great job. But I would carefully think about if this approach is really what I need. If you Milan find time to implement the same stuff for a nosql database I bet you will improve your solution.
I don't think EF is going anywhere to be honest. And if you're going from SQL to MongoDb, you'll have much bigger problems than rewriting queries...
Great video, thanks a million for sharing it with us.
Glad you enjoyed it!
Very valuable and useful video.
Thank you for sharing.
You're welcome
Correct me if I'm wrong but there is a Predicate
There is indeed
Great video, thanks for sharing! I just have a quick question: why should we use Expression instead of just Func? Is it because EF uses expressions to convert LINQ to SQL?
Yes that's the reason, in the end, you saw that the criteria you send to the specification object is used on calling the queryable object, so that's why.
Because that's what EF uses, as the other comment said
// Specification Minimal Version
// Advantages
// * can add multiple criteria
// * can apply multiple ordering
public class XSpecification
where T : class
{
public List CriteriaExpressions { get; } = new();
public List IncludeExpressions { get; } = new();
public bool IsSplitQuery { get; set; }
public bool AsNoTracking { get; set; }
public Func? OrderFunction { get; set; }
public void AddCriteria(Expression expression) => CriteriaExpressions.Add(expression);
public void AddInclude(Expression expression) => IncludeExpressions.Add(expression);
public IQueryable Evaluate(IQueryable inputQuery)
{
IQueryable query = inputQuery;
CriteriaExpressions.Aggregate(query, (current, expression) => query = current.Where(expression));
IncludeExpressions.Aggregate(query, (current, expression) => query = current.Include(expression));
if (IsSplitQuery)
{
query = query.AsSplitQuery();
}
if (AsNoTracking)
{
query = query.AsNoTracking();
}
if (OrderFunction is not null)
{
query = OrderFunction.Invoke(query);
}
return query;
}
}
// usage
var name = "john";
var _context = new ApplicationDbContext();
var inputQuery = from x in _context.Users
select x;
var xspec = new XSpecification();
xspec.AddCriteria(UserExpression.IsEnabledExpression);
xspec.AddCriteria(x => (!string.IsNullOrEmpty(x.UserName) && x.UserName.Contains(name))
|| (!string.IsNullOrEmpty(x.FullName) && x.FullName.Contains(name)));
xspec.AddInclude(x => x.Branches);
xspec.AsNoTracking = true;
xspec.OrderFunction = (query) => query.OrderBy(x => x.UserName).ThenByDescending(x => x.FullName);
var resultQuery = xspec.Evaluate(inputQuery);
// Console.WriteLine(resultQuery.ToQueryString());
// Console.WriteLine();
foreach (var u in resultQuery)
{
Console.WriteLine($"{u.UserName}, {u.Email}");
foreach (var b in u.Branches)
Console.WriteLine($" - {b.Name}");
Console.WriteLine();
}
Might be worth using Ardalis.Specification
For second time - Greetings from Bulgaria. Thank you for your time and efforts to create and share those nice tutorials.
Well, this time you did not win me. I fail to understand. From one class with 20 lines of code you made 5 classes with 50 lines of code !!!??? And one single change will require going through layers of classes.
Makes sense, but imagine having to reuse these specifications in quite a few places - then there's more value to it. Check out this repo also: github.com/ardalis/Specification
Nice explanation bud. Subbed!
Thanks, Charles! 🔥
Hello Milan. Thanks for the video.
It looks like I missed what was the reason to implement Specification Pattern here.
In generally Specification pattern here is a wrapper on IQueryable which allows you do not depend on EF Core and implement Queries on business logic level and potentially you can use the same specifications for Mongo database (as an example).
But what problem was solved in this case?
I'm asking because I've heard about that approach many years ago, but didn't find a reason to apply into my project because no understanding why this is better then what you had before or bettern then extension methods.
Thanks!
This implementation is EF specific. Perhaps the example is too simple for the pattern to make sense. Try to think of more complex queries with a lot of includes in different methods.
@@MilanJovanovicTech But what the issue having that implementation in the method of repository class? I don't think it can be bigger then 100 lines of code.
I'm using projections and I think most of the case you need to use projections for optimal queries. And once you use it you don't need to write .Include(). EF will do that automatically for you.
Projections itself I have in DTO clases responsible for projections. So, the only code I have in repository pattern is Where conditions.
@@MilanJovanovicTech Please understand my comments as a knowledge sharing. This is the only goal I have. Thanks
@@volodymyrliashenko1024 No worries, I never mind my ideas being challenged 😁
I use repositories on the read side, so it makes sense to leave the Includes.
If I want to use projections, that's going to be on the read side where I return data from the API. In which case the Specification pattern probably makes little sense. Agreed.
Thank you for the video!
My pleasure 😁
Hi Milan, first of all thank you very much for the rare content you bring us.
Secondly, I saw that a lot of people said that it should be done in the Domain and I also think so, the question is what to do with functions that belong only to EF such as Include, how can you access them in the DOMAIN and still keep the domain clean.
You can't place this in the domain without leaking implementation details
Maybe just place the WHERE filter in the specification, that can freely go in the domain
By instantiating a concretion to your low level module (which is the repository) wouldn't you create a tight couple between your now high level module (repo) to your Specifications? Even if you implement a generic interface ISpecification and then have them ready in your container, what would you do if you have to change your repo to not use any specification?
That's such a low level decision. You'd decide at the start what path you want to take (think carefully). And commit to it. If you decide on a whim to change implementations, that says a lot...
@@MilanJovanovicTech Hey Milan how are you? Glad you decide to respond.
Even though principles are there if you want to follow them, I would said D in SOLID is very important (well... all of them), if in 3 years the company want to change db providers that don't work well with LINQ to SQL then this approach wont work as your Specifications uses LINQ as you are using the concretions directly (which you tie your I/GaterhingRepository to marry db providers that uses LINQ to SQL), but usually EF Core has a lot of NuGet packages to work with those other engines anyway.
In regards changing implementations on a whim I would say... welcome to IT? This happens a lot and the people from the top decides to make this big chances surely for financial reasons more than other.
Anyway, great content!
@@AnotherFancyUser If you change from SQL to something else (MongoDb) you're screwed either way. You have to rewrite everything.
If you change from SQL Server to PostgreSQL, you most likely won't have any issues. Just switch EF packages.
What other use case do you have in mind? 🤔
Very good video I want more ( mostly peerformance videos ) :)
I have a few performance videos coming! 😁
@@MilanJovanovicTech ❤
@@MilanJovanovicTech I thout that you can combain this topic with custom projecton in Select method, what do you think ? It would be greate
Can we use this pattern for something like dapper too? With manual mapping 1-m it seems a little unhandy, doesn't it?
I doubt you'll be able to make it work as easily as with EF Core
Great video, thanks as always, it would be great if you do a video specifically on cancelation token.
Thanka Cyril. I have that topic in the pipeline, I'll come through with the video sooner or later.
@@MilanJovanovicTech Really appreciate, thanks again.
Great video and explanation. I started using Steve "Ardalis" Smith's implementation of the specification pattern a while back, and this is a great breakdown of how to implement it yourself. Love the channel, keep up the great work!
What's your opinion on having a more generic Repo method that takes and applies a specification as a param, rather than creating huge numbers of custom repo methods that implement specific specifications?
I would also suggest using Steve's library, since it's awesome and more robust than what I showed.
I think that's a nice option for sure, but I prefer having well named repository methods. Nothing wrong with either approach. Just a personal preference.
I am fed up with abstractions. In my latest project I got rid of repository pattern altogether, just injecting DbContext directly and doing EF Core Linq queries from my handlers. What Milan shows goes in the opposite direction - he has not one but two abstractions: specification pattern and then ageneric repository on top of it. Not sure where the value is though. Nice video though, informative.
I think all abstractions make sense in a given context. It's up to you to figure it if this makes sense to use or not. I don't use Specifications all too often, but I find it an interesting pattern.
Thanks, bro what if I need more than one condition in the where clause, for example, I need Employee where the name is "Patrick" and IsPresent. How to implement that here
Pass the conditions as arguments to the specification
Thanks for teaching this. Not sure its for me though.
That's fair 😁
Really good! Thank you!
Glad you liked it!
I don't think that's a good idea. It is ilusion of abstraction. Yes your specification class doesn't depend on Expressions but wait your base class does. You couple your specification defining objects with c# expression api cause you know that you are going to use EF Core in your repository. In proper implementation your specification object should be only translated by separate module specific to your infrastructure. So when you use EF Core you will translate specification to expression, when you use raw sql you will translate it to raw sql and so on. Now does it makes sense and is it easy? No. Propably very hard. I would just use raw db context instead with correctly named extension methods to encapsulate query logic.
Well, this is a Specification + EF implementation. What did you expect?
if you delete all the async/await from your code...how it will affect the app ?
Less throughput
great content, congrats! Could you increment this example with the pagination part using specification?
Will do a Part 2
Very nice😀
Thanks 😊
Hi Milan nice video. One question though which stop me from using it, is it possible to inject a specification via dependency injection? I would like to to inject a specification from outside. Let's say, in case of TDD, I would like to use a mocked or stubbed version of that specification, can I do that ?
Doesn't make a lot of sense when specifications need arguments to create the parameters. But you can always inject them from the method arguments.
I couldn't pass my point around. Sorry, my query was, if I can inject the, i.e... gatherby..specification to a mediatr command or query? I don't want to new that up inside a mediatr handler.
The specifications are now defined in the persistence layer. Should you not define them in Domain or Application, because how and what we retrieve from the database is Domain specific, is it not?
True, that might've been a better example
good job!
Thank you!
Cool. I would also creare an AndSpecification and OrSpecification to combine specifications
Ah yes, I didn't want to take it that far though
Great video made me have several ideas on how to improve my projects. A doubt that came to my mind is it a good idea to pass the specification as a parameter to the getbyid and getall functions? to have a default behavior and a specific one as I need?
You can expose a specification parameter and let the consumer decide what to query, for sure
@@MilanJovanovicTech thanks
how does this work with secondary level includes ie .ThenInclude()?
Not that well 😅 Use Ardalis.Specification library for a better implementation
Hi Milan, could you please talk about Hi/Lo algorithm? and how to apply it in EF Core?
I've rarely seen it used in practice. But sure, I'll add it to my list of topics. 😁
thank you for amazing video , how can access this project ?is exit public repository for clone?
I share it on my Patreon page: www.patreon.com/milanjovanovic
I unsmashed my like when I look for the source code I was asked to pay
Sorry to hear that
Nice video, but what is solution if i want to use ThenInclude method on one to many?
You can use the existing solution, if it's not a list:
AddInclude(x => x.Navigation1.Navigation2)
Or implement the includes using strings instead of expressions
Also, check out ardalis/Specification for a better implementation
Can anyone explain when to use dynamic and object keyword????
Ideally never
how can i add projection in it?
You can't
Hi Milan! Firstly, thanks for the great video! As I read through the comments below, you and others mentioned that instead of creating specific repository methods like GetBySomethingWithXAndY writing only a GetBySpecification method would be OCP complient and more elegant. This implementation would imply that the Specification class should be moved to the Application layer so that the derived classes of the Specification could be instantiated and given to the proper GetBySpecification methods in the CQRS IRequestHandler implementations. However, this would mean that through Specification EF-specific details would be placed (eg IsSplitQuery) in the Application layer. Isn't that a break of the Clean Code Architecture? Or is there something that I misunderstood? What is your opinion?
That's correct. You would be introducing EF specific details into Application/Domain layer (not directly, but as you mentioned).
Are you okay with that tradeoff?
How would you handle the case where a certain entity's repository (with its includes) needs to be reused in another repository (to not duplicate the same includes)?
Inject and use that repository?
@@MilanJovanovicTech Excuse me, but I don't understand how the following case could be handled: suppose I have Customer with his contact details. The specification could give me a way to get the Customers with the inclusion of the contact details. But when do I have to reuse customers with contact details within an Order specification? Suppose I need an Order with Customer details including contact details. It seems to me that I should still reply to the include contact details also in the order. Maybe I'm missing something, or I don't fully understand.
Really nice video, I followed along in my own project and got great results, simplifying my repository logic. Could this approach by used with projection as well, or do you prefer to project in service layer after data has been returned from the database?
It's going to be more reusable if you don't couple projection with the Specification
@@MilanJovanovicTech Your example only returned FirstOrDefault but for returning a list of Companies where you only need Id and Name it would be a shame not to use the projection option in EF to elect only those two fieldsfrom the database.
Concrete example: A customer picker where you want to select 1 customer but allow selection based on company name and the user can start typing until the list of (take 20) contains the customer. Here it would ba a shame to return an entire top 25 customers when You are only interested in the name and id
@@grumpydeveloper69 Well you still have an IQueryable, and you can chain a Select on it before FirstOrDefault to materialize what you need?
In your specification evaluator you put else if - Is that a bug? It seems like you can only have a where clause or an order by.
You can have both, they are applied independently
@@MilanJovanovicTech , Sorry! I see now. I looked at the code 3x and managed to missread it every time. Thanks for making the videos 😎
What are the biggest benefits of using it?
Didn't I mention it in the video?
Does this only work if both mt specification and my dbcontext use the domain object? (TEntity).
What if i want different models representing my db tables which are separate from my domain objects. Where do i map these two different models?
Then you have to translate the specification somehow to create an expression of your DB model
this is great, but how do you handle thenInclude in this design?
A nested include will do, or include by string. You can check out ardalis/Specification for a full library
For ThenInclude add the following to Specification.cs:
Specification.cs
public List IncludeStrings { get; } = new List();
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
And here an example of a specifications function with ThenInclude for the Gathering entitie:
using Gatherly.Domain.Entities;
namespace Gatherly.Persistence.Specifications;
internal class GatheringByNameSpecification : Specification
{
public GatheringByNameSpecification(string name)
: base(gathering => string.IsNullOrEmpty(name) ||
gathering.Name.Contains(name))
{
AddInclude(gathering => gathering.Attendees);
AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}");
}
}
And in the SpecificationEvaluator.cs add:
queryable = specification.IncludeStrings.Aggregate(queryable,
(current, include) => current.Include(include));
* => this part is the ThenInclude: AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}");
Here we assume there is a table with Attendee skills
I'm using specification pattern same way as described in a video. And recently I faced with a problem of using Left Outer Join to access to included entities. Do you know how to use specification patter along with left outer join? (All information a googled provide only sql style examples)
There's always a situation where these patterns fall on their face. I would check if Ardalis.Specification package has a solution for that. Otherwise, just implement it with EF Core
@@MilanJovanovicTech I solved my problem with left outer join locally by adding to my table two "MyRefId" and "MyRefIdOriginal" columns. I filled both of them on created. And use one for history purpose and one fo Left Outer Join simulation by setting cascade set null for this field. So now I can just filter out rows by RefId is null. This allows me to use specification pattern. Any way if any information about left outer join with specification - please let us know.
@@MilanJovanovicTech Hi Milan, do you use Ardalis.Specification inside your projects ?
in which case we will need to set IsSplitQuery as false ?
What do you mean?
Razor pages vs aspx???? Which one is better???
aspx is very dated nowadays, burdening many projects that are using .NET Framework from moving to .NET Core/5+. Razor pages are the more modern approach, utilizing a similar syntax
I can't say, didn't work MVC in ages
@Milan Jovanović, how to add AsNoTracking() and ThenInclude
AsNoTracking the same approach as SplitQuery. Add a flag, and if true, apply AsNoTracking.
For ThenInclude, you can do AddInclude(x => x.Navigation1.Navigation2)
Or, you can add support for string includes
For ThenInclude add the following to Specification.cs:
Specification.cs
public List IncludeStrings { get; } = new List();
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
And here an example of a specifications function with ThenInclude for the Gathering entitie:
using Gatherly.Domain.Entities;
namespace Gatherly.Persistence.Specifications;
internal class GatheringByNameSpecification : Specification
{
public GatheringByNameSpecification(string name)
: base(gathering => string.IsNullOrEmpty(name) ||
gathering.Name.Contains(name))
{
AddInclude(gathering => gathering.Attendees);
AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}");
}
}
And in the SpecificationEvaluator.cs add:
queryable = specification.IncludeStrings.Aggregate(queryable,
(current, include) => current.Include(include));
* => this part is the ThenInclude: AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}");
Here we assume there is a table with Attendee skills
@@MilanJovanovicTech, Thank you some much. Let me check, But I'm not fan of using strings in Include.
All your video's are very much useful. Keep rocking 🙂
@@vishoek69 String include approach becomes hardly supported with rich domain model and inheritance. Maybe it can be accomplished with some fluent includes builder
How to implement it with thenInclude?
Instead of ThenInclude, just include to the level of nesting you need:
AddInclude(x => x.Navigation1.NestedNavigation)
You can also write a string include implementation, it works with EF. But I didn't show it in the video.
You need a List, and apply them the same way with Aggregate.
@@MilanJovanovicTech
Thanks for the reply.
The problem with level nesting is when x.Navigation1 is a list you can’t go to x.Navigation2.
But I might take look at string list implementation.
For ThenInclude add the following to Specification.cs:
Specification.cs
public List IncludeStrings { get; } = new List();
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
And here an example of a specifications function with ThenInclude for the Gathering entitie:
using Gatherly.Domain.Entities;
namespace Gatherly.Persistence.Specifications;
internal class GatheringByNameSpecification : Specification
{
public GatheringByNameSpecification(string name)
: base(gathering => string.IsNullOrEmpty(name) ||
gathering.Name.Contains(name))
{
AddInclude(gathering => gathering.Attendees);
AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}");
}
}
* => this part is the ThenInclude: AddInclude($"{nameof(gathering.Attendees)}.{nameof(Attendees.Skills)}");
Here we assume there is a table with Attendee skills
@@MilanJovanovicTech How about using the already mentioned "IncludeStings" implemention and some additional methods in the Specification.cs
protected void AddInclude(Expression includeExpression, Expression thenIncludeExpression) where TSubEntity : BaseEntity
{
var path = GetExpressionMemberPath(includeExpression.Body).Select(e => e.Member.Name).ToList();
path.AddRange(GetExpressionMemberPath(thenIncludeExpression.Body).Select(e => e.Member.Name));
string queryString = string.Join('.', path);
this.AddInclude(queryString);
}
private IEnumerable GetExpressionMemberPath(Expression expression)
{
if (expression is MemberExpression memberExpression)
{
foreach (var item in GetExpressionMemberPath(memberExpression.Expression!))
{
yield return item;
}
yield return memberExpression;
}
}
Usage:
this.AddInclude(x=> x.Navigation1, navigation1 => navigation1.Navigation2);
It will create the required string "s.Navigation1.Navigation2" for you in the background, where the nested Navigation1 is an enumerable.
Have you uploaded this project to github? please share a link
I only share the code with my Patreon supporters
Entity Framework and anonymous types totally do away with the need to use ridiculous design patterns that result in unneccessary code. Looking at you "repository" and "unit of work". Why add additional code that just bloats the footprint, increases the learning curve for other, and adds unnecessary complexity. Design patterns here are not needed! The DBContext can easily be added as a service and mocked for unit tests. EF itself can handle multiple back end databases, as well as other providers. Just stop with the patterns! The best code is the code you don't need to write. Design pattern zealots, please tell me where you work, so I don't apply there, and have to unravel the design-pattern mess that "only you" think is brilliant.
Show me a good example of mocking the DbContext for unit tests?
var storeContext = new Mock();
var orders = new List();
var ordersCount = 0;
storeContext.Setup(m => m.SaveChanges()).Returns(() =>
{
count += Math.Max(ordersCount, orders.Count);
return count;
});
mocks.storeContext = storeContext.Object;
devOpsContext.SetupGet(m => m.Orders).Returns(() =>
{
// add initial orders
}
I dunno. Seems like another level of indirection to me. Interesting patterh, though.
Interesting, not for every project though
@@MilanJovanovicTech Good for RDD as well (Resume Driven Development)
would it make sense to have a generic specification that gets passed as an argument to the repository? this way you could have powerful findOne/findMany repository methods, that do not directly rely on entity core and would allow for future refactors to use something else
This becomes pretty complex pretty soon (but yes you can do it)
GetQuery() sounds more like an ApplySpecificationToQuery...
Alright 🤷♂️