BackgroundIn business applications, you often need to write boolean logic for your entities, which are calculated from a variety of data sources such as:
When deciding how and where to write such logic, you have different choices. Option A: CLRThe easiest option, is to write the logic as C# code that simply operates on the business object in the CLR. For example:
Problem: If you want to query the database for available products by using this implementation, first all records have to be fetched from the database and then the method gets evaluated, which can be very slow if you have lots of records in the database:
Option B: Database query expressionsAnother option is to write the logic as lambda expression (or even direct SQL) so that the logic gets evaluated in the database and only the matching records are fetched into the application memory:
Problem 1: The availability logic is hardcoded and you lose the IsAvailable abstraction. Problem 2: The same filter can be required in multiple other database queries. For example if you want to write another query to fetch available products in a specific category, or with a specific price range, you will have to duplicate the same expression multiple times.
In this example you see them next to each other, and might think the duplication is not a big deal. But in reality, each one can be written in a completely different parts of your application code, with no obvious connection between them. It's easy to change one, but miss the other ones. They can go out of sync and cause all sorts of hard-to-debug problems. The more complex the logic, the more problems you will have because of this duplication. Problem 3: In addition to database queries, you may need the same logic invoked from CLR routine as well. For example in the OnSaved event handler of the Product class, you may need to take an action if the product is Available. So, you will have to write the same logic again, but this time as normal C# code (not a database query lambda expression). Option C: DatabaseQuery extension methodsIn this approach, you define the logic as an extension method on
This way, the availability logic will still be evaluated in the database and so it's fast.
Also this allows you to reuse it in multiple database queries using the
Therefore, this approach will address problems 1 and 2 from option B above. Problem: You will still have problem 3 in option B above, where you'll need one implementation that operates on the CLR entity, and one that operates on the database. Although both implementations of the same IsAvailable abstraction will be very similar in many cases, but still this brings you some code duplication.
Solution: Isolation of duplicationWe have all learnt, from day one of programming school, that code duplication is bad. Yet, in this case, it's technically inevitable to have a single implementation that can would run both in CLR and in database.
One file per methodTo achieve both physical proximity and isolation, we propose to move each method into its own file. For example, for the Inside that file, we will have 2 partial classes. One for the CLR version of the code, and one for the SQL version.
Since this file has nothing except the two implementations of one concept, it's nearly impossible to change one implementation without noticing (and fixing) the other.
Introducing this Visual Studio ExtensionYou will perhaps already have boolean-based methods written directly in your entity classes. With this extension, you will see an action suggestion appearing next to the method definition reading Promote to file. When you click it, it will automatically create the new file with the above format and move the method from the original class to that. It will leave the 2nd implementation (database query) empty so you can copy the logic and apply any changes required. |