- Home
- LLBLGen Pro
- Architecture
Simple Exercise: Populate Drop-Down List from Authors in a Database
Joined: 28-Jan-2004
I think this architecture forum is a great idea, although my architectural questions are a bit more trivial than what I have seem from the rest of you.
I understand the importance of a layered approach to development (UI - Business - Data) and would love to turn out excellent code, but in the end the code fails to maintain a pure approach basically due to a lack of understanding.
I have just started a personal web project that I have decided to do "right" if it kills me, which it may, and I was hoping to get some feedback from others on a trivial exercise - filling a dropdown list on an ASPX page with a list of authors from an author table. I am using LLBLGen, of course, and for the first time I am using the Adapter Templates as opposed to the Self-Servicing templates.
Shown below are some code snippets that I would normally do that I am sure need much improvement. Notice I am passing EntityCollection and AuthorEntity, which is a LLBLGen Data Class, all the way up to the UI Layer. I also tend to create these public sealed classes with static functions for my "business classes" and quite frankly am starting to wonder how and why I chose this approach.
If anyone could point out the error of my ways and different code examples, I would be very appreciative. My code seems anything but layered and that it should be using some interfaces, perhaps a separate Author class different from the one created by LLBLGen, etc.
Thanks,
Dave
Code Behind of ASPX Page. Note AdminPage derives from System.Web.UI.Page class.
using DavidHayden.WebsiteCMS.Data.EntityClasses; using DavidHayden.WebsiteCMS.Data.HelperClasses; using DavidHayden.WebsiteCMS.Business;
using SD.LLBLGen.Pro.ORMSupportClasses;
namespace DavidHayden.WebsiteCMS.Web.Admin.UI { /// <summary> /// Summary description for AddPost. /// </summary> public class AddPost : AdminPage { protected System.Web.UI.WebControls.DropDownList ddlAuthors;
private void Page_Load(object sender, System.EventArgs e)
{
if (!IsPostBack)
LoadData();
}
override protected void OnInit(EventArgs e)
{
base.OnInit(e);
this.Load += new System.EventHandler(this.Page_Load);
}
private void LoadData()
{
EntityCollection _authors = SiteManager.GetAuthors(PortalSite.Id);
for (int i=0; i < _authors.Count; i++)
ddlAuthors.Items.Add(new ListItem(((AuthorEntity)_authors[i]).Name, ((AuthorEntity)_authors[i]).Id.ToString()));
}
}
}
SiteManager "Business Class"
using DavidHayden.WebsiteCMS.Data; using DavidHayden.WebsiteCMS.Data.EntityClasses; using DavidHayden.WebsiteCMS.Data.HelperClasses; using DavidHayden.WebsiteCMS.Data.FactoryClasses; using DavidHayden.WebsiteCMS.Data.RelationClasses; using DavidHayden.WebsiteCMS.Data.ValidatorClasses;
using DavidHayden.WebsiteCMS.Data.DatabaseSpecific;
using SD.LLBLGen.Pro.ORMSupportClasses;
namespace DavidHayden.WebsiteCMS.Business { /// <summary> /// Summary description for SiteManager. /// </summary> public sealed class SiteManager { private SiteManager() { // No Instances... }
public static EntityCollection GetAuthors(int siteId)
{
SiteEntity site = new SiteEntity(siteId);
DataAccessAdapter adapter = new DataAccessAdapter();
EntityCollection authors = site.Authors;
adapter.FetchEntityCollection(authors, site.GetRelationInfoAuthors(), 0, new SortExpression(SortClauseFactory.Create(AuthorFieldIndex.Name, SortOperator.Ascending)));
return authors;
}
}
}
PortalSite "Business Class"
namespace DavidHayden.WebsiteCMS.Business { /// <summary> /// Summary description for PortalSite. /// </summary> public class PortalSite { private PortalSite() { // No Instances... }
public static int Id { get { return GetCurrentSiteId(); } }
private static int GetCurrentSiteId()
{
// Populated from Global.ascx.cs which grabs it from web.config
return (int)HttpContext.Current.Application["SiteId"];
}
}
}
Joined: 08-Apr-2004
Hi There.
First - a disclaimer: I am in no way a .NET expert, and have only been using LLBLGen for a while, so my comments might not be 100% (hopefully someone will correct me if I make a mistake). I have however worked in tiered development for a good few years, so what I would say is:
-
Simple thing - you are not binding the dropdown. You are populating it programatically, when really you should just bind to the collection. This has nothing to do with the "architecture" though.
-
You have passed an EntityCollection back to the PL. The reason this is a bad thing is because it forces the PL to know about LLBLGen - it "tightly couples" PL with the data layer. The "goals" of these tiers/layers is to produce abstracted blocks of code that utilise the layers below but know nothing about the layers above. Although your code does achive this, it binds everything to LLBL, meaning that it effectively binds the PL to the DLL. If you change your DLL from LLBLGen to your own ADO.NEt code for example, you would have to change loads of code. IMHO, this is the point at which you either say "what the hell" and continue with this approach safe in the knowledge that you will prb not chnage LLBL, or you do what I do and make sure the PL only relies on the BL.
What I am doing is passing back "custom entities" to the PL (which are not LLBL relient), or for collections I am either passing DataTables or custom collections (see the the thread I started about this http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=834)
Your BL should consume the LLBL code (as it does) and pass back to the PL something that is not so relient on the PL.
Still, theres nothing "wrong" with your code - the architecture has to fit what you want it to do...
Joined: 01-Dec-2003
Hey guys I will add my .02
I actually disagree with Matt on this topic
If using selfserviceing than I would say hey you need to populate your own objects to pass around..as selfserviceing knows about that database...but with adapter the work has been done for you. Two projects are created one that targets that database and the other that contains all of the helper classes and entity classes. If you need to change your DAL you could just get rid of the DBSpecfic project and then populate the Entity objects using your new DAL.
for example 1. create LLBLGen entity object 2. use LLBLGen adapter object to populate entity object
opps now you boss says hey you are not alloyed to use dynamic SQL anymore! You must use my new method! Ok no problem
- create LLBLGen entity object
- Use your bosses new method to get data form stored PROCs. and populate LLBLGen entity objects
Creating your own objects seems like extra work 1. create LLBLGen entityobject 2. create custom entityobject 3. fetch LLBLGen entityobject using adapter 4. map fetched LLBLGen entity object to custom object
I say use the entityobjects
Thoughts?
Bert
Joined: 28-Jan-2004
MattWoberts wrote:
- Simple thing - you are not binding the dropdown. You are populating it programatically, when really you should just bind to the collection. This has nothing to do with the "architecture" though.
The only reason why I normally loop through the collection is because sometimes I need to add the word "Choose..." to the drop-down list in case the FK can be null. If this is the case, I can simply add "Choose..." by adding a statement before the loop to add it as a list item.
The statement before would be-
ddlAuthors.Items.Add(new ListItem("Choose....", "0"));
Before saving, I then do something as follows:
if (ddlAuthors.SelectedValue != "0") object.AuthorId = Int32.Parse(ddlAuthors.SelectedValue);
Is there a performance hit or other drawback for looping? If I databind, how do I add "Choose..." if need be?
Joined: 28-Jan-2004
I see both Matt's point and Bert's point with respect to using LLBLGen's entities and collections.
I would prefer not to use these "using" statements anywhere in my presentation layer:
using DavidHayden.WebsiteCMS.Data.EntityClasses; using DavidHayden.WebsiteCMS.Data.HelperClasses; using SD.LLBLGen.Pro.ORMSupportClasses;
This would seem to go against every layered development practice I have ever read... I think
However, do we really need to wrap every LLBLGen Entity/Collection in a business class? That seems way overkill.
Wouldn't a good in-between solution be create interfaces and pass those back and forth between presentation and business layers?
I changed my UI code as follows and everything works great:
interface IAuthor { int Id { get; set; } string Name { get; set; } }
private void LoadData() { // Changed from EntityCollection to ICollection ICollection _authors = SiteManager.GetAuthors(PortalSite.Id);
// Changed from AuthorEntity to IAuthor for (int i=0; i < _authors.Count; i++) ddlAuthors.Items.Add(new ListItem(((IAuthor)_authors).Name, ((IAuthor)_authors).Id.ToString())); }
I also was able to remove the following 3 using statements from the ASPX Code-Behind:
using DavidHayden.WebsiteCMS.Data.EntityClasses; using DavidHayden.WebsiteCMS.Data.HelperClasses; using SD.LLBLGen.Pro.ORMSupportClasses;
So where is the gotcha? This seems to easy...
Joined: 28-Jan-2004
Another change to the code above.
ICollection _authors = SiteManager.GetAuthors(PortalSite.Id);
is probably not ideal.
If you were returning a DataTable instead of a collection, DataTable returns IList via IListSource. Therefore by doing:
IList _authors = SiteManager.GetAuthors(PortalSite.Id);
I have the option of returning a collection or a DataTable.
What do you think?
Joined: 28-Jan-2004
I realize now I have overly complicated the issue by iterating through the collection when databinding provides the abstraction...
private void LoadData()
{
ddlAuthors.DataSource = SiteManager.GetAuthors(PortalSite.Id);
ddlAuthors.DataTextField = "Name";
ddlAuthors.DataValueField = "Id";
ddlAuthors.DataBind();
ddlAuthors.Items.Insert(0, "Choose...");
}
My main thought is to make sure the UI Layer does not dare use EntityCollection or DataTable, which assumes an understanding of how data is being stored/represented, so I just expose IList from GetAuthors
public static IList GetAuthors(int siteId)
{
SiteEntity site = new SiteEntity(siteId);
DataAccessAdapter adapter = new DataAccessAdapter();
EntityCollection authors = site.Authors;
adapter.FetchEntityCollection(authors, site.GetRelationInfoAuthors(), 0, new SortExpression(SortClauseFactory.Create(AuthorFieldIndex.Name, SortOperator.Ascending)));
return authors;
}
However, I now also realize there is no sense in putting PortalSite.Id in the UI layer when I can really just grab it while in the Business Layer. Hence the code now becomes
private void LoadData()
{
ddlAuthors.DataSource = SiteManager.GetAuthors();
ddlAuthors.DataTextField = "Name";
ddlAuthors.DataValueField = "Id";
ddlAuthors.DataBind();
ddlAuthors.Items.Insert(0, "Choose...");
}
public static IList GetAuthors()
{
SiteEntity site = new SiteEntity(PortalSite.Id);
DataAccessAdapter adapter = new DataAccessAdapter();
EntityCollection authors = site.Authors;
adapter.FetchEntityCollection(authors, site.GetRelationInfoAuthors(), 0, new SortExpression(SortClauseFactory.Create(AuthorFieldIndex.Name, SortOperator.Ascending)));
return authors;
}
The IAuthor Interface only comes into play when working with actual Author Entities, which I will try tomorrow.
Joined: 08-Apr-2004
Thats a fair point about working with interfaces, and passing those back. I'd be interested to hear other thoughts on that too.
My own reaction is that yes it does accomplish a looser coupling, since you no longer depend in the LLBL generated code in the PL.
But... (and I'm happy for people to disagree with me) the work that you have done to define the interface is only marginally more that the work to define the "custom entities". Infact, my templates are modified so that the "custom entities" are automatically created for me. I then use some common code to copy an LLBL entity into a "custom entity", which works as long as the field names are the same. The advantage I see of custom entities is that should I need to add an extra column in there, I can modify my enetiy really easilly. I can't modify the LLBL entity, without having to modify the factory etc...
Joined: 07-Apr-2004
Interesting...bringing Interfaces into play.
This will work ofcourse... as long as you use LLBLGen.
But.....
The Interface reveals the DL's structure via methods and properties. So although your Interfaces seems abstract and works now - change your DL to another technology that does not have the same structure, properties, methods and you will be doing some serious recoding as you will have to change the PL and Interfaces.
This would seem to go against every layered development practice I have ever read... I think
Yep! Pure N-Tier development is the best way to go - spend the time now - not later
Interfaces defines a common structure to use - They are abstract in this sence only. By defining your own interfaces i assume that you end up with 1 Interface = 1 LLBLGen Enitity(1:1)? This does not really seem worth it. Where is your Business rules located?
The method that MattWoberts and myself use is almost simular - not identical. He has custom entities i construct Manager classes that contain logic for retreiving datatables with different filters and the man class contains 1 custom entity object for 1:1 retreivals and changes. Another difference is: He generates his - I code mine .
A change to the DL will only affect our BL's implementation and not the BL's representation to the PL. As i stated in some of my previouse postings - a BL acts as an interpreter.
Joined: 01-Dec-2003
MattWoberts wrote:
The advantage I see of custom entities is that should I need to add an extra column in there, I can modify my enetiy really easilly. I can't modify the LLBL entity, without having to modify the factory etc...
.. didnt think of that... I really hadnt need ot do that yet..
As long as you have templates that are generating you r code all is good
now...how about nulls? how do you handle null values in your custom entities? for example an integer datatype that is nullable in you database. How do you retian the null value in your custom entites.
Bert
Joined: 28-Jan-2004
Right or wrong, I am convincing myself that exposing Interfaces to the UI layer is ideal. This makes my "application" more of a black box and allows me to change the business layer without effecting the UI layer as long as I continue to support the existing interface contracts.
If I decide to add another field in the database or additional functionality to the business layer, I can create a new interface. The UI can choose to use the existing "old" Interface or use the new interface with the additional fields and functionality.
I now want to work with actually adding an entity to the database. I am not very confident with what I am writing here, so any input is appreciated.
Here are my thoughts on adding an author, minus exception handling, transaction support, etc.
ASPX Code Behind:
protected System.Web.UI.WebControls.TextBox txtAuthorName;
private void AddAuthor()
{
// Interface does not instantiate the object directly.
// Here we use a Factory Method and only expose IAuthor
IAuthor author = SiteManager.CreateNewAuthor();
author.Name = txtAuthorName.Text.Trim();
int authorId = SiteManager.SaveNewAuthor(author);
}
Public Interface Specification in its own assembly:
public interface IAuthor
{
int Id { get; }
string Name { get; set; }
}
A Custom Author Entity I wanted to avoid this, but I see no other way around it.
I will stick this class in the same assembly as SiteManager. By making it protected internal, it cannot be created in the UI layer, which should be in its own assembly.
By deriving from LLBLGen's AuthorEntity, I keep its functionality, which saves me a bunch of work. Is my Business Layer tied into LLBLGen? Yep. This may be bad design. It's my first cut.
The main idea here is to support the IAuthor Interface on this entity so I can use it in the UI.
protected internal class Author : AuthorEntity, IAuthor
{
public override int Id
{
get
{
return base.Id;
}
}
public override string Name
{
get
{
return base.Name;
}
set
{
base.Name = value;
}
}
}
SiteManager Class Additions Don't want to expose anything about my objects here. Simply returning IAuthor and int for this scenario.
As mentioned above, I need a factory method to create the Author, since the UI can't instantiate it directly.
public sealed class SiteManager
{
...
public static IAuthor CreateNewAuthor()
{
return new Author();
}
public static int SaveNewAuthor(IAuthor author)
{
Author myAuthor = (Author)author;
myAuthor.SiteId = PortalSite.Id;
DataAccessAdapter adapter = new DataAccessAdapter();
adapter.SaveEntity(myAuthor, true);
return myAuthor.Id;
}
...
}
I certainly haven't saved any work here, but I have avoided the mapping of custom entities to LLBLGen entities (somewhat) by inheriting from LLBLGen entities. I have also kept my UI layer from knowing anything about the business layer.
Any ideas to make this better? I may be totally wrong here.
Joined: 08-Apr-2004
bertcord wrote:
now...how about nulls? how do you handle null values in your custom entities? for example an integer datatype that is nullable in you database. How do you retian the null value in your custom entites.
Hmmm. In our app, we're only just starting with the LLBL code, so to be honest I haven't yet had to tackle this. Call me stupid (or naive) but I wasn't expecting this to be a problem, my custom entity integer field contains null, and I can either change this to a number or not - am I going to experience problems with nulls? As long as the entity doesn't try to add "null" to the db when one isn't allowed, whats the problem?
Joined: 08-Apr-2004
Hello again Dave!
Erm, maybe I have missed something, but why did you bother with a custom entity dervied from LLBL's entity?
You got an IAuthor, and changed its fields, and passed it to the Manager to save it - all good. Your manager then saves it, all good. So whats all this about a custom entity?
The thing about your tiers is that they should not know anything about the layer above, and should only consume the layer beneath them. Your BL consumes your DAL, which happens to be LLBL code. Thats a good thing! Of course your BL has to use LLBL code, but thats just one layer consuming it, not your BL and PL - its all good!!
If you were worried about the business layer using llblgen code (predicates, data adaptors, entities etc..) and wanted to abstract this so that your BL only deals with business process logic, then you could create a further layer - your own "data layer". In there are all your data methods, for example "LoadAuthor" which does all the LLBL-stuff and returns an IAuthor, so your BL could do further BL code. I thought about this (and posted questions here), and came to the conclusion that this was overkill, and was over-complicating the architecture. In some situations though this might be appropriate.
Joined: 09-Jun-2004
Just a quick comment (unrelated to the direction this thread has taken) -- if you are going to use this drop down in multiple places, consider making it a custom control that inherits from dropdownlist. We've used this approach successfully -- if you ever need an author drop down, you just drag it over from your toolbox. If you need slightly different behaviors in different situations, you can pass in parameters...
Hope that helps,
J
Joined: 01-Dec-2003
MattWoberts wrote:
my custom entity integer field contains null, and I can either change this to a number or not - am I going to experience problems with nulls?
Are you storing your custom entity fields like LLBLGen does? Interanlly LLBLGen stores an entites values in an IEntityFields2 object, internally this data is stored using the object datatyope. So you can set a value as null like so.
Dim customer As New CustomerEntity("CHOPS")
customer.SetNewFieldValue(CType(CustomerFieldIndex.ContactTitle, Integer), Nothing)
I Was under the impression that your custom objects where just classes with properties that mapped to your databse for exaple the following class has two properties DateTimeTest and IntTest
Public Class Test
Private _datetimetest As DateTime
Public Property DateTimeTest()
Get
Return _datetimetest
End Get
Set(ByVal Value)
_datetimetest = Value
End Set
End Property
Private _inttest As Integer
Public Property IntTest()
Get
Return _inttest
End Get
Set(ByVal Value)
_inttest = Value
End Set
End Property
End Class
If you create an instance of this class and do not set the values of the properties you get the following
dim mytest as new test
Console.WriteLine(mytest.IntTest)
Console.WriteLine(mytest.DateTimeTest)
output:
0
12:00:00 AM
If the value is null in your databse how do you persist the value form the DB to your PL and back to the DB?
Bert
Joined: 28-Jan-2004
MattWoberts wrote:
Erm, maybe I have missed something, but why did you bother with a custom entity dervied from LLBL's entity?
You got an IAuthor, and changed its fields, and passed it to the Manager to save it - all good. Your manager then saves it, all good. So whats all this about a custom entity?
I can't instantiate IAuthor. I need an entity that implements IAuthor so I can pass it back to the presentation layer. Rather than building an entity from scratch, I derive from AuthorEntity. This may not be ideal. However, it does work
Joined: 07-Apr-2004
I think this post is going to receive alot of fire...
Dave wrote:
I can't instantiate IAuthor. I need an entity that implements IAuthor so I can pass it back to the presentation layer. Rather than building an entity from scratch, I derive from AuthorEntity. This may not be ideal. However, it does work
If you are deriving your own entities just to add the implement interface clause - Why don't you just change the existing entity template to add "implement I + 'EntityName'"? Because your classes are not based on your Interfaces - it's the other way around.
MattWoberts wrote:
Your BL consumes your DAL, which happens to be LLBL code.
What BL? There is only PL and DL in Dave's implementation. BL = Business Layer - that means rules & validation. Ever heard of a BL without business rules?
This is not BL but some type of AL (Abstraction Layer) net even - it is something like AAL (Almost Abstraction Layer). Because this BL(AAL) is making the PL dependant on the AAL and the AAL is dependant on the DL - so why have it? So you can't replace any of the layers below the PL without affecting the other layers. And where will the Business rules be? In the PL or DL ofcourse - Cant add it to the interaces.
This seems like a whole lot of work wich is not acheiving much. Are these interfaces stored in a seperate class library or is it part of your DL Library?
My opinion only.... Post me to death if you want to.
Dave wrote:
Right or wrong, I am convincing myself that exposing Interfaces to the UI layer is ideal
It doesn't seem like Dave wants our help on tier design.
Now on a friendlier note... Matt what are you using adapter or self serv...? And when are you going to get a picture? - Need to put a face to the name.
Joined: 07-Apr-2004
Hi Matt
Call me stupid (or naive) but I wasn't expecting this to be a problem
Will only be a problem if you have an string or integer field that has no value assigned and you save it over an existing field value. Don't think .Net Types can be null....not sure. The problem is usualy on the DB side when retreiving DBNull values into .Net Types - but LLBLGen takes care of this for us.
So you should not have a problem if you load from the actual entity first.
Joined: 08-Apr-2004
Dave,
WHat I would do at this stage is take a step back, look at the code, and work out what it is achieveing. You are defnining custom entites, and interfaces also. Its a lot of code as Wayne says. If its not obvious to you what gains there are then you should re-visit!
Some thoughts:
Can't you make the LLBL entities implement the IAuthors interface? Thus removing the need for the custom entities?
Or...
If you are using custom entities, then theres your abstraction. Ditch the interfaces, as they don't achieve anything further for you.
Wayne - I was assuming that the "SIteManager" was the BL. Yeah theres no BL logic in there at the moment, but its a placehodler for the BL code, non?
As for the picture, I'll think about it....as long as its not a star wars character!!!
Joined: 08-Apr-2004
Bert (and Wayne)
I haven't yet got to the stage you are discussing of passing back custom entities which have nulls. I see what you mean though. My thinking is that in most cases the entities can come from the db in teh first place, or I can just set something that indicates that they are null (not sure how though)
Bert, how did you handle this?
Joined: 07-Apr-2004
Yeah theres no BL logic in there at the moment, but its a placehodler for the BL code, non?
I might be wrong but as far as i know is it impossible to put code in a Interface. Interfaces are purely a representation of methods, events and proeprties that is suppose to be implemented by classes with non common anchestors or classes with common ancestors but with non common functionality wanting to have common functionality implementation.
Like the sqlconnection and fbconnection - their common anchestor, 'component' does not implement the logic or framework for a connection object - there for they have to implement IDBConnection.
Joined: 15-Apr-2004
Hi All,
I'm coming in very late on this topic, but I thought I would throw in my .02.
First, [quotenick="Dave"]
My main thought is to make sure the UI Layer does not dare use EntityCollection or DataTable, which assumes an understanding of how data is being stored/represented, so I just expose IList from GetAuthors
EntityCollections (Adapter) and DataTables only represent a snapshot of the data, just like any other container you want to stick data in.
Second, if you decide to use EntityCollections with your PL, you should wrap them up in your BL. My BLs will typically expose arraylists, datatables, and entitycollections. Even though the EntityCollections and associated Entity class is referenced in the PL, ALL data manipulation and validation is done in the BL.
Take care,
Fishy (puting the BL back into LLBL)