This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
Say I have a Person table and a birth day field in a table. I want to have another field representing the age (how many years old is the person). Of course, it is a calculated data. Is it better to set this calculation in the database as a calculated field, within the model as a method in a partial class or within another class outside the model? Using LightSpeed what would you say it's the best approach? Besides, that age field or data is a qualificator. Based on that and other simmilar data you can assign the Person to a group (say child, teen and adult, but also single young mother, if you combine with civil status and have childs, and things like that). Would be the best approach to add a lot of boolean and integer fields to the table in order to preserve such status and boolean qualifications (IsSingle, HasChilds, IsTeen, etc)? The answer to this questions will help me to understand a lot about how LightSpeed can help in a real application. Of course, I know some of this is about best practices, but it also will help me to understand a bit more about LightSpeed power on this kind of issues. |
|
|
The answer is always "It depends." If you want to be able to query on the age (e.g. "where p.Age > 18"), then it has to be represented in the database, because we're going to translate that Age reference to SQL. (There are ways around this with a bit of LINQ expression ninja magic, but they're a bit crude.) So in this case you would use a computed column in the database and a read-only field in LightSpeed. If, however, you only want to use the age client-side, for example for display in a data grid, then it's probably better to make it a calculated property in the partial class. This saves database resources and ensures that the value is always "live" (e.g. as the time changes, a computed column wouldn't get recalculated until you reloaded from the database, but a computed property would be recalculated on every get). I would not put something like Age in another class outside the model. It's absolutely appropriate to enrich your domain model with useful methods and properties -- much better than sprouting external helper classes. Where this gets difficult is when you veer from simple calculations into business logic. Consider an IsEligibleForBenefits attribute. You might want to query on this so you'd think, "oh, I'd better make that a computed column in the database." But you don't want to end up coding business rules about eligibity into your database structure. That belongs in the business layer where it can be configured and changed. So in this case a computed property -- or a separate BenefitsComputer service class -- would be a much better choice, with LINQ magic if you really needed to query efficiently on eligibility. |
|
|
May you ellaborate a little about the LINQ magic tricks or provide a reference to a source? Yes, the age data will be used in queries in the form you said ("where p.Age > 18"). AND it will be used to display, in a special datagrid control (XtraGrid) that is able to filter with such a base. But I'm hesitant to store it in the database. Anyway, I would like to know a little bit more about that magic, to see if becoming a magician is worthy of the effort involved. And when you say "Where this gets difficult is when you veer from simple calculations into business logic". Score!! You got the point at the fly. I may end up with tons of this boolean "is" and "has" and "belongsTo" fields, but I'm not sure that is the best way to approach this. In the case of IsEligibleForBenefits you're right, the rules for elegibility may change through time, then they must be in the business layer. My case is that this, say, Person entity changes. If this is a child, it has some specific attributes and actions involved. If it is an adult and belongs to a commitee, it has another set of attributes and treatment. If he is preparing to earn certain award, he must take certain course, pass certain interviews, etc. And those changes aren't always through a workflow. Some are pretty variable, or comes arbitrarily. Because that, I need to decide which booleans must be part of the table and which must be attached to the entity when an action course is needed. |
|
|
The magic is about using C# (or VB) lambda syntax to write your derived business attribute as a LINQ expression. Let's consider the IsChild attribute, with the business rule of "a Person is a child if their age is less than 18"). This is easy to express in C# as p.Age < 18. So the trick is to write that as a lambda expression instead of a normal CLR property: public static Expression<Func<Person, bool>> IsChildExpression = (p => p.Age < 18); Notice that this is a static field of Expression type, not an instance property of boolean type. We can now use this in a LINQ query as follows: var children = unitOfWork.People.Where(Person.IsChildExpression); If you examine the generated SQL you will see that the IsChildExpression criterion has been translated into "Age < 18" SQL. Of course, the trouble with this is that you can't use it in client-side processing, display it in a data grid, etc. because it's not a CLR instance property. But we can easily create a property that wraps the expression: public bool IsChild { IsChildExpression.Compile compiles the expression into a delegate, and we then invoke that delegate on the Person at hand. (Note that I am recompiling the expression every time the property is accessed. This is very inefficient. In reality you would want to cache the delegate.) So we have managed to keep the business rule in one place (the IsChildExpression) but we can now use it in both in a property (IsChild) and in a LINQ query. There are a couple of downsides/limitations to this: 1. We have to remember to use different syntax in the LINQ query. We can't write "where p.IsChild" because there's no mapping from the IsChild property to the IsChildExpression. I've considered making LightSpeed understand this pattern, which would unify the syntax again. 2. We've relied on IsChild being a boolean. What if we had a non-boolean property such as FullName: public static Expression<Func<Person, string>> We can use that fine in, say, OrderBy: unitOfWork.People.OrderBy(Person.FullNameExpression); but we can't use it in a Where clause because Where needs something that returns a boolean. You can work around this somewhat by creating a method that returns a comparison expression but it's a bit messy: public static Expression<Func<Person, bool>> FullNameIs(string name) { unitOfWork.People.Where(Person.FullNameIs("bob")); The nasty expression stuff could probably be made a lot more generic but I'll leave that for you to explore if you think this is a productive avenue for you! |
|