- Home
- LLBLGen Pro
- Architecture
Making a Reusable Framework of Core Entities
Joined: 09-Aug-2006
First off I'm using LLBLGen 2.0 on the .NET 2.0 Framework, TwoClasses Selfservicing.
A little background: I'm working on a custom CMS that I've inherited from many past developers. I have a lot of issues with the design, its inefficiencies, and smells, but unfortunately, moving forward we can't just scrap the whole thing because we have too many custom applications VERY tightly coupled (gah!) to the original data model (i.e. piles and piles of random SQL throughout the application code). So over the past months I took it upon myself to start doing things "good" ( ). (I won't say great, or even 85% right, but "good" is a long way from where things were. ) In this time I've developed a new web application built on an enhanced version of our old CMSs data model and LLBLGen. "Enhanced" means lots of triggers (for backwards/forwards compatibility), added FK relationships, indices and, yes, even Primary Keys to some tables -- if you can imagine. Anyway, I've been in the Zone of Pain for quite some time, but things are looking up.
Sorry for the boring intro, I just thought it might be useful to know, especially if this thread ends up exposing some of the ridiculous design flaws -- it wasn't me, and yeah I know its bad, so I won't be offended if you need to point and laugh.
The actual question:
With our CMS system we have built many custom applications for various clients. Without exception, these custom applications will use some of the core content types and some newly defined (custom) content types.
Examples:
Core/Generic content types: Subpage, Event, NewsItem, File, Link, etc.
Custom Content Types: Tradeshow Exhibitors, Customized Journal Articles etc.
All of these entities are defined in one database, and deployed with all applications. In all current deployments, we configure the CMS application to only show the core and custom content types that truly belong to that application, however, empty tables for the other types are always kicking around (yes, sloppy).
In the future, whether or not we still push all of the tables or not, is not my concern for now. I only care about the generated code.
What I want to be able to do is, generate a different project for each custom application, so that they all have their own customized DAL/BLL. Meaning each custom app needs to be independent of other custom apps. Because I don't want to have to redeploy and retest every app when we change a portion of some customized CMS implementation, obviously.
Here's my idea: As I see it I need to do two pretty major things. First I need to generate a project (using the object subet feature) for all Core Object Entities (i.e. base object types, and other cms entities such as those that handle permissions, logging, and other cms plumbing.). This is the project where I'd define any framework extension code or business logic for all of these entities.
Second, for each custom CMS implementation I'll need to generate code for the subset of custom content types, but it seems that I'll still need to include all the code for the Core ObjectEntities I built in the first project. I assume I need to do this in order for the OR Mapper to build all the right relationships etc.. But the thing of it is, I need a way to make each custom implementation point to the First project for all of the Core Object Entities. Is there anyway to do this with the code generator??
In case the dependency between the Custom Content Entities and the Core Entities wasn't clear enough, I figured I should mention that all content types in our CMS have one unifying base object called "Object". For example, Subpage,Event, or Tradeshow Exhibitor are a sub-type of Object. (sigh, I know this is bad, but I don't think I can change it for a while). Obviously, ObjectEntity would exist in the Core Objects component, and its subtypes may or may not live in the same component.
I hope I've been clear enough here. I'd appreciate any advice or opinions.
Thanks,
Shawn
Joined: 21-Aug-2005
I think the following thread was discussing a similar issue: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7714
Joined: 09-Aug-2006
This is kind of similar, but I think I'm a bit confused about some things. It seems that his problem is a little more advanced in some ways, but not quite a solution to my problem. His problem refers to many modules that may be isolated or dependent on others, but in all cases, every module has its own LLBLGen project (I think, if I understand what was written). In reality my problem is simpler than that, but not quite the same.
In my case, well, I may be more confused than ever about this.
Option 1 (the simple case I thought of a long time ago, which I hate): One LLBLGen Pro project and just forget about all these lofty concepts of dependency and just push everything to all implementations. (sucks!)
Option 2 (I don't like too much either) Create a single LLBLGen Pro Project for all CMS Entities (Core and Custom).
Task 1: Generate only the Core CMS Components into a Project called Cms.Core.Dal add in business logic I need for the core components here.
Task 2: For each custom implementation, generate all core components (or at least the "Object" super type needed for all custom content types) and the subset of custom content types. i.e. I'll call this project Cms.ClientX.Dal.
Task 3: Add a reference to Cms.ClientX.Dal for Cms.Core.Dal .
Task 4: (upon each code re-generation) Delete all the classes related to Core Components, then write a script or something to insert the appropriate using statements to refer to Cms.Core.Dal, cross-fingers, and recompile. I'm probably missing something else I'd need here, but that's all I have for now. (Perhaps there's some way to use LLBLGen to simplify this step??? )
I apologize if the thread you sent me to was supposed to make it clear what I should do, I think I'm getting caught up his details, and not seeing a real solution there.
I'd appreciate it if you could spell it out for me.
Thanks
Joined: 21-Aug-2005
How would you manage if an entity in the Cms.ClientX.Dal should have a relation with an entity in the Cms.Core.Dal.
This will not be generated into Cms.ClientX.Dal unless you map the other entity from the Cms.Core.Dal into the llblgen project of the Cms.ClientX.Dal.
And I'm afraid you would end up adding everything again to that project.
Taking Northwind as an example: you would have mapped everything into the Core.Dal. Then for a ClientX you need another Table "OrderExtraDetails". But this table/entity should have builtin relation with Core Order Entity.
Joined: 09-Aug-2006
How would you manage if an entity in the Cms.ClientX.Dal should have a relation with an entity in the Cms.Core.Dal.
This will not be generated into Cms.ClientX.Dal unless you map the other entity from the Cms.Core.Dal into the llblgen project of the Cms.ClientX.Dal.
Yes, this is the exact problem. There will most definitely be this relationship in every implementation, because the super type "Object" will be defined in the Core.
So, the plan is when I generate the project for ClientX I would need to include all the Core entities, but afterwards I'd need to manually: 1. Add a Reference to Core DLLs in the ClientX project (only the first time the project is generated)
-
Remove the Core classes that I had to generate in order to get all the mappings from ClientX classes to Core classes.
-
Then run some kind of automated script to add appropriate using statements, referencing the Core Project, throughout all the remaining ClientX-specific classes. I guess I could do this with a custom Template. However, I just realized that there is probably more to this than using statements. There's the Enums and Constants I'd have to change as well (and likely a lot more), yuck!
Anyway, as you can see, I haven't figured out all the details, but this approach is the best I can think of for now. Does this sound practical at all?
I fear I've still not explained myself that clearly. Let me know.
Thanks.
Joined: 17-Aug-2003
If I understand it correctly, you will have a central base entity 'Object' which a lot of entities inherit from? Ok, don't do that. It's not that it's not possible, but you'll regret the choice later on. the reason is that to be able to implement inheritance with a relational model, a lot of joins will be done for every query you run. It's better to have an associated Object entity with all the entities and load it when you have to, but only then. Unfortunately, relational models aren't great ways to store inheritance hierarchies in. Only use inheritance if you want to have a relation between a subtype and another entity which shouldn't be present in another subtype of the same supertype.
About the custom entity set per customer: The relation definitions are a problem. You could try to make a single project, and select the entities you need for the customer (that is: all entities, thus also the core entities, they're all in 1 project) in a group, and generate the code with that group. (you can define subsets or groups in the generator configuration dialog).
The commandline code generator tool (sourcecode is in the SDK), doesn't support selecting these groups by default, but you can add this code without a lot of effort to the sourcecode of the code generator. All you have to do is read the entities in a group and place them in the Entities list of the executing generator instance. The code generator will then pick the entities in that list.
You can then add a new command line parameter to the cli code generator and make the whole thing batchfile driven.
Joined: 09-Aug-2006
Thanks for the feedback.
If I understand it correctly, you will have a central base entity 'Object' which a lot of entities inherit from? Ok, don't do that. It's not that it's not possible, but you'll regret the choice later on...
Actually, you've given me that advice before , but, unfortunately, I've still got the problem where I need to refer to things as [ObjectEntity]s about 90% of the time, and being able to polymorphically fetch all (or large subsets) of Object subtypes seems pretty indispensible. There are some major efficiency problems with this design, however, the cost of fixing them and migrating all of our existing systems is extremely high (literally millions), and I don't have the power to make those changes. Oh, how I wish I did.
I believe last time you suggested getting rid of the ObjectEntity's table and adding its fields to all the existing subtypes. Then, I think you recommended a 3rd party template which uses a unifying base class, which defines the common properties of "ObjectEntities". I've been reconsidering this approach recently, and it might work, but I've got to figure out how that will effect the CMS's content hierarchy (by "content hierarchy" I'm referring to how the user creates and organizes their content. I'm not referring to hierarchy of mapped entities, types, sub-types etc.). The Content Hierarchy is created by defining Parent-Child relationships between 'Objects'. A given Object can have multiple parents and children, this functionality is supported by another table "ObjectRelation" (ObjectRelationEntity). So, I guess to be able to get rid of the Object table I'd still need to have some kind of unified key creation facility, but that should be pretty simple.
As far as the custom entity set goes: I'm pretty clear about how to use the subset tool, but as I'm sure you can tell there are other problems as well (the relations and all the associated properties (prefetch paths, enums, etc.)). I honestly haven't tried apply my custom template idea (to add the proper using statements to the Core framework into the Specific project), but I'm pretty sure it's going to take more than that.
For now I'm just going to push the whole framework to all clients. When I'm given more time, I'm going to come back and figure out exactly how to do it right. I'll probably have a few more questions about the generator then.
Thanks.
Joined: 15-Jun-2005
Unfortunately, relational models aren't great ways to store inheritance hierarchies in. Only use inheritance if you want to have a relation between a subtype and another entity which shouldn't be present in another subtype of the same supertype.
I wanted to ask for clarification on this point. Another valid reason for inheritance in addition to subtype relations being different is to be able to differentiate interfaces and behavior. I have two subtypes that have different interfaces and business rules (Event and Task). In some cases (adding, editing) I need to look at these as specific, strong types. But when I am simply rendering a calendar view, I want to look at these generically as "CalendarItems" without regard to specific subtype. There is no difference in the relations these subtypes have to other types.
In object terms, this is simple to accomplish, but in data model terms, what is the proper approach? Using a single table / discriminator column eliminates extra joins, but at the expense of strong typing in the business layer. Using multiple entity tables provides strong typing, but at the expense of increased joins / decreased performance. Using a single table / Target-per-entity-hierarchy inheritance approach seems like it would provide both advantages and seems like the best approach, but I can't tell if you're saying not to use inheritance at all in this case (relations are the same) based on your comment.
Joined: 09-Aug-2006
I'm not quite sure, and I know you weren't asking my opinion, but it sounds like you don't need to make Event and Task subtypes of the same super-type, you probably just need to define a common interface to both of them. And then you could simply refer to the items as "CalendarItem"s via the CalendarItem interface. This interface wouldn't likely need to define any new members but could possibly include some extra properties in the BL; I guess this depends on what kind of project you're using, and where you decide to add the interface.
Does that make sense, or did I miss your question completely?
Shawn
Joined: 15-Jun-2005
That is exactly what I want to do, and if I was coding everything manually, it's exactly what I would do. I just don't know how that would be done in LLBL. AFAIK, you can't just define a class interface -- everything is entities based on tables. But in order for the entities to actually implement the CalendarItem interface, that would have to be built into the code generation process. Maybe it is possible and I'm just missing it?
Joined: 17-Aug-2003
bmoeskau wrote:
That is exactly what I want to do, and if I was coding everything manually, it's exactly what I would do. I just don't know how that would be done in LLBL. AFAIK, you can't just define a class interface -- everything is entities based on tables. But in order for the entities to actually implement the CalendarItem interface, that would have to be built into the code generation process. Maybe it is possible and I'm just missing it?
Of course that's doable
In v2.0 you can specify an interface definition which has to be added to the entity definition in the generated code. You can bulk-add this definition if you want using a plugin provided.
Then, in code, you write a simple include template which implements the actual interface and bind that include template using a new templatebindings file (see Templatestudio docs and llblgen pro sdk for details, it's very straightforward) and let that include template do the interface implementation for you at generation time
You can also decide to generate a partial class with the interface implementation, if you want to.
Joined: 09-Aug-2006
Yeah. Actually, when I first did this, I didn't realize you could add it in with the LLBLGen project, so I just modified the standard code-generator Entity template to add a User Code section where you'd normally define interfaces. Then I just add in whatever interfaces I want in the generated project. This is not quite as slick as using the "add interface" feature of the designer, but I'm too lazy to change it right now. I'll probably regret it when someone else works on the project without my custom template, but hey, its not too hard to add in a few interface declarations here and there.
Joined: 15-Jun-2005
OK, you learn something new every day . I'll take a look into adding the interface, as I believe that could solve my problem in this case.
I'm still unclear as to when you would favor inheritance over this approach though. To me, this seems similar to the example in the doco of specialising a CompanyCar into a SportsCar / FamilyCar. In both cases, you are differentiating the subtypes based on attributes (Task has attributes like "due date" and "percent complete" that are not on Event). In the doco example, TPEH was used to illustrate the approach.
I guess what confuses me in understanding this is that the documentation example actually illustrates mixing TPE and TPEH, even though it's not valid to do so. Maybe what's missing for me is an example that takes the same exact data model and shows it implemented in both TPE and TPEH all the way through, pointing out how you would decide which approach is better and why. When I look at my own data model, I see advantages to using both methods, but since they cannot be mixed, I am not sure what my criteria should be for committing to one over the other for the entire project.
Joined: 17-Aug-2003
bmoeskau wrote:
OK, you learn something new every day . I'll take a look into adding the interface, as I believe that could solve my problem in this case.
I'm still unclear as to when you would favor inheritance over this approach though. To me, this seems similar to the example in the doco of specialising a CompanyCar into a SportsCar / FamilyCar. In both cases, you are differentiating the subtypes based on attributes (Task has attributes like "due date" and "percent complete" that are not on Event). In the doco example, TPEH was used to illustrate the approach.
the thing is that an entity in a hierarchy of target per entity (TPE) suddenly contains by itself its own hierarchy, of type TargetPerEntityHierarchy (TPEH)
Say there were three types of BoardMember entities and this could be solved by using TPEH. When I'm then fetching all boardmembers, I've to fetch using 2 different fetching techniques, namely TPE (for boardmember's hierarchy) and TPEH for the 3 derived types of boardmember. And perhaps you'll define another TPE hierarchy below one of the subtypes.
It makes it really complicated, and not really efficient. The thing then is: is this really necessary? In practise, having a lot of inheritance doesn't help you forward in the way you want to: inheritance with relational models is more of a trick than it is build into the relational model itself: it's semantical interpretation of the data inside the table rows to make it work and retrieving data isn't as efficient as it is in non-inheritance scenario's (especially with TPE, where you need joins with supertypes or with subtypes, depending on the scenario)
The mix of hierarchies is also rare, it only pops up in scenario's where a common base entity is used using TPE and for the rest TPEH hierarchies. Using a common base entity isn't efficient anyway, and I hate to say that but it's true and I think it's better to be honest than to deny it. I've to add that 'inefficient' doesn't mean it brings down the system to a crawl, but it does need extra operations with joins you otherwise don't have to do. If every cycle counts, these joins might hurt the performance, that's all I'm saying.
The thing with interfaces is this: In .NET you have single-implementation inheritance. There's no support for multiple-implementation inheritance. However there's support for multiple-type inheritance: you can inherit an interface from multiple interfaces, effectively making it a subtype of the 2 or more supertypes.
Interfaces are a tool to make it possible to have a type inherit from multiple types, however as said, implementation wise, you can inherit from just 1 supertype (class). With entities, you have a problem: you can't have multiple inheritance using entities in LLBLGen Pro. You can, in theory, have this in ObjectRoleModelling for example, but you can't have it in code.
I guess what confuses me in understanding this is that the documentation example actually illustrates mixing TPE and TPEH, even though it's not valid to do so.
No it's not mixing the two. If you look closer, you'll see TWO inheritance hierarchies: the one BoardMember is in (TPE with manager and employee) and the hierarchy RELATED to that via a relation of CompanyCar which has a TPEH hierarchy. CompanyCar isn't part of the inheritance hierarchy of boardmember, so they inheritance hierarchies aren't mixed.
You thus can have related entities which are in a different TYPE of hierarchy, you can't mix 2 different hierarchy types in a SINGLE inheritance hierarchy.
Joined: 15-Jun-2005
Otis wrote:
The mix of hierarchies is also rare, it only pops up in scenario's where a common base entity is used using TPE and for the rest TPEH hierarchies. Using a common base entity isn't efficient anyway, and I hate to say that but it's true and I think it's better to be honest than to deny it.
And of course, this is our scenario (and I think that of the original poster too) . I hear what you're saying -- again, my only concern is the amount of column duplication you end up with including the Base table columns in every entity table, but it sounds like that's your recommended approach.
Otis wrote:
I guess what confuses me in understanding this is that the documentation example actually illustrates mixing TPE and TPEH, even though it's not valid to do so.
No it's not mixing the two. If you look closer, you'll see TWO inheritance hierarchies: the one BoardMember is in (TPE with manager and employee) and the hierarchy RELATED to that via a relation of CompanyCar which has a TPEH hierarchy. CompanyCar isn't part of the inheritance hierarchy of boardmember, so they inheritance hierarchies aren't mixed.
You thus can have related entities which are in a different TYPE of hierarchy, you can't mix 2 different hierarchy types in a SINGLE inheritance hierarchy.
Ahh. I did not get the distinction that they cannot be mixed only within the same chain. In my project, everything is related to BaseItem, so I was seeing it only as all or nothing either way. Thanks for clarifying that!