Answered by:

N-Tier WinForms Application - What is the RIGHT way?

Question

I'm building an application that currently has three 'layers', each of which is in its own separate project.

- Presentation (WinForms)
- DAL (Class Library)
- BEL (Class Library)

In BEL, I have classes such as 'Customer'; in 'DAL' I have classes such as 'Database', but I am confused.

When it comes to inserting data rather than simply receiving datatables based on one simple parameter (e.g. int id) in a DAL's class method, how can I do this in a strongly-typed way? I can't add references to both BEL and DEL because I'll get a 'circular dependency'
error. If I just reference DAL from BEL, I can add strongly-typed parameters in any methods, but then how do I fire the methods in DAL classes from BEL classes given this circular dependency issue? Am I meant to simply have object parameters in DAL methods
that are then cast to my type - I assume not!

I've seen options of having two middle layers (Business Logic, Business Entity), with the compromise that this setup allows for a "double loop system", but I can't discern whether that makes it something to avoid or not.

Reading other people's posts and articles has added to the confusion due to the references of using XML etc. and, be it through my ignorance or otherwise, that seems a little over complicated for what I'm trying to do (CRUD operations using my types).

All replies

Sorry I wasn't able to get here sooner. Work gets in the way, you know. ;0)

Anyway, I think you would benefit from having a separate Project (call it DataClasses if you'd like) that contains your Typed DataSets. Then your DataClasses Project could be referenced by all 3 of your other Projects without having to worry about circular
references.

What a coincidence seeing you here, Bonnie! I'm more than willing to wait for helpful assistance. :)

I know I'm forming the wrong idea in my head (the penny hasn't dropped on n-tier yet!), but:

DAL, BEL, Presentation projects reference DataClasses project.

DataClasses acts as the intermediary between BEL.

DataClasses references DAL? I'm assuming I'm wrong here as wouldn't this put me in the same situation - DataClasses and DAL can't reference each other, so I either sacrifice being able to add typed parameters (for a Customer, or other entity-type class)
within DAL classes (if DataClasses is referencing DAL), or I miss out on being able to create objects of DAL classes because I chose to reference DataClasses from DAL?

I'm obviously missing the point, and likely the connection to typed datasets here which probably solves the problem (as I'm referring to general classes).

Two days into my N-Tier life and my head has taken the form of guacamole!

The DataClasses Project doesn't need to reference any of your other Projects. The DAL, BEL and Presentation (UI) Projects all reference the DataClasses Project.

Remember in the other thread I pointed you to a link (
https://msdn.microsoft.com/en-us/library/bb384586%28v=vs.140%29.aspx?f=255&MSPPError=-2147217396 ) about how to separate your Typed DataSets from your TableAdapters into different Projects? This is one reason why ... the Typed DataSets *don't*
need to know anything about the TableAdapters, but the TableAdapters *do* need to know about your DataSets. So, the TableAdapters would be in your DAL Project, which would reference the DataClasses Project where the Typed DataSets live.

Now, as you also may remember from the other thread, I haven't actually tried the steps in that link (because I don't use TableAdapters), but I certainly hope that Microsoft got it right with that process such that the Typed DataSet does not need a reference
to the TableAdapter ... otherwise, what would be the point? Maybe I should try it out myself before I recommend it. ;0)

"The DataClasses Project doesn't need to reference any of your other Projects. The DAL, BEL and Presentation (UI) Projects all reference the DataClasses Project."

Ha, sorry Bonnie, I'm making this real hard for you, I'm sure!

Is that to say then that you end up purely having a setup of:

UI references BEL references DAL

UI, BEL, DAL reference DataClasses - my impression has been that the UI should be strictly out of anything other than the BEL/BLL layers(?)

I do remember and have looked at that link, and maybe this is where the problem exists in my understanding; I have in my head: objCustomer gets created in BEL layer - how does it get to DAL?!?!?! I assume, my reluctance in considering the DataSet is
what cures this pseudo-problem (given that the DataSet exposes the fields of the db, which (I again assume) I would somehow bind to an object (type 'Customer' (class file)) to allow data to return through the BEL and up to the UI?

N-tier sucks at the moment - I see the value, and I'm sure n-tier and I are just in the 'dating' phase, before we can truly appreciate one another and live happily ever after!

Think of a DataSet as purely a DTO class (Data Transfer Object). The DataSet itself knows nothing about where its data comes from. It is used like a simple class to pass your data around between the tiers/layers.

So, the UI calls a method in the BEL and the BEL calls a method in the DAL. The DAL gets data from the database filling a DataSet and returns that DataSet to the BEL, which may or may not do something with/to it, and then the BEL returns the DataSet
to the UI. The UI uses that DataSet for databinding its controls and whatever else you might need to do with that data.

Right, yes, your attributing DataSet as a DTO has tied to things up (previously I seem only to have seen Entity Framework mentioned in connection to DTO), so that makes sense. I still have one bit I'm confused about:

"So, the UI calls a method in the BEL and the BEL calls a method in the DAL."

That's a long way for a penny to fall ... I bet it would ruin your day if it landed on your head!! ;0)

You're not using EF (Entity Framework), so you don't use a Database class ... which is what you seem to be doing in the InsertCustomer() method in the BEL. Even if you *were* using EF, you wouldn't be doing this in the BEL ... this is something done in the
DAL. All access to the database is done in the DAL, period.

This is what your methods would look like:

UI calls this method, passing it a CustomerDataSet (that the UI has placed data into):

// Method within some logic class in 'BEL'
public void InsertCustomer(CustomerDataSet dsCustomer)
{
// Operate on the DataSet, dsCustomer, if necessary
using (MyDAL da = new MyDAL())
{
da.InsertCustomer(dsCustomer)
}
// Operate on the DataSet, dsCustomer, if necessary
}

The DAL would have this method:

public void InsertCustomer(CustomerDataSet dsCustomer)
{
// Use your TableAdapter to insert the dsCustomer data into the database
// You don't need to return it, because objects such as DataSets are
// passed by reference as long as you don't create a new object
//(such as dsCustomer = new CustomerDataSet()), which you wouldn't do in this case anyway.
}

I see the correction to my BEL method, though my intention from that would not have been to perform the insert into the DB itself - I saw it as the 'Customer' object gatherer, so to speak!

I see your point now, and the point of the 'DataAccess' project, which based on being referenced, would have allowed instances of the Typed DataSet to have been created in the other layers.

I read something earlier about DataSets not being mentioned in the UI, and instead to generate your object (Customer, for example) there instead, which then gets sent to the BEL project, which in this case I see would then be turned into a DataSet. Wouldn't
that then remove the necessity to reference the DataClass project? Or, as has just this moment popped into my head, that would presumably mean that the UI can't then populate controls based upon the
typed DataSet... Though would a cast to a typed DataSet solve that, too?

I think the penny's in Earth's stratosphere now - if you see a shooting star with a copper colour, you'll know I'm close to fully grasping the concept!

Probably whatever you read about not having DataSets in the UI might have been based on things a while back when TableAdapters could not be separated from Typed DataSets. I don't know, but I still wouldn't have agreed with that anyway. In my opinion DataSets
(either Typed or not, but Typed is *definitely* way better) are great for databinding UI controls. You definitely don't want to turn them into a Customer object ... what would be the point of having both (DataSets and objects)? In your example, the CustomerDataSet
is the only DTO you would use.

I don't even do UI stuff anymore (server-side services for the last 8 years or so) and I still use Typed DataSets. I know a lot of people think that DataSets are too "heavy", and EF is "all the rage" ... but I disagree. I guess I'm just
a contrarian! ;0)

I think I somehow read this message without having any recollection of it! It was only yesterday when I thought "I don't think I ever heard back" that I realised there was something waiting for me. And, I've been completely distracted from the
project this relates to, but now it's back.

One question: how come Microsoft, in their articles, suggest using WCF services instead of a 'DataClasses' project. And, just to see if I even still remember:

>>One question: how come Microsoft, in their articles, suggest using WCF services instead of a 'DataClasses' project.<<

Do you have any links to articles that suggest that? I use WCF simply for accessing all my back-end server-side stuff from the UI. My UI sends and receives data to my WCF service methods. It sends/receives that data as Typed DataSets serialized to XML (using
the DataSet.GetXml() method). Basically, the WCF services acts as your BEL and it references and calls the DAL methods.

>>If that's wrong, I'm starting again! :-)<<

You don't have to start again, that's right! Unless, of course, you're wanting to use WCF (which is a good thing to do, depending on your application and how it's used), but then it's still pretty much the same: you just substitute WCF for BEL in your list
of acronyms. ;0)

Ah, that's reassuring - my vision is like Neo's form the Matrix, except I see 'BEL', 'BLL', DAL' etc.!

I imagined the WCF service to be more of a standalone project, like the 'DataClasses' one as it seems to blur the line of responsibility and ease of maintaining just a single project should something, i.e. logic, change later...

Your job sounds very interesting. Did you learn a lot of what you know by doing it (and self-teaching)?

Maybe you misunderstood that article. In the WCF service that they create (they call the project DataService), they are referencing what we have called the DataClasses project (where the NorthwindDataSet lives, they called the project DataEntityTier). The
DataService also references the DataAccessTier (which is where they put the TableAdapters ... which, BTW, I don't use ... I roll my own DataAccess with SqlDataAdapter).

>>I imagined the WCF service to be more of a standalone project, like the 'DataClasses' one as it seems to blur the line of responsibility and ease of maintaining just a single project should something, i.e.
logic, change later...<<

Yes, the WCF service has to be a separate project ... since it gets deployed as a Windows service. But it's not totally standalone, because you have to have references to your other projects too, like the DataClasses and BEL. If you're interested, I have
a blog post about creating easy Windows services. Basically, you self-host your Service in a Console project. Not sure if you're ready for that, but take a peek if you're curious:
https://geek-goddess-bonnie.blogspot.com/2013/10/easy-windows-services.html

>>Your job sounds very interesting. Did you learn a lot of what you know by doing it (and self-teaching)?<<

Well, part of it has been self-taught, but not the basic concepts. I took Computer Science as a minor in college (long story as to why I majored in Math instead of Computers). But, that was a *long* time ago ... before PCs came along, that's
for sure! (Cobol, main-frame IBM computers ... yeah, I'm ancient). Working for many, many years as a programmer, you tend to pick things up as you go. Experience is a great teacher. Mainly the self-taught part of it is simply learning new programming
languages. I used to program in Visual FoxPro until .NET came out in 2002, which is when I switched to C# and never looked back ... except with the occasional twinges of nostalgia ;0)

Maybe you are right, but I don't quite see the point of the WCF service if a BLL were then going to be added that communicates with the DataAccess layer once all validation/business logic code had been run, unless the logic was placed in the WCF service
(but then I'd wonder if that's really abiding by the principles as it blurs separation of responsibilities). If that BLL didn't exist, I can see why the service would then be required (if communication between the UI and DAL was frowned upon) - I'm digressing,
and probably making no sense, but things have to make sense to be reconciled in my mind!

I'll take a look at that link for sure... I watched quite a bit on WCF services earlier on and from what I know so far, I don't think they quite fit into what I'm trying to achieve (whereas the solution we've talked about in this thread does, for the sake
of getting used to n-tier solutions).

It sounds like you have lots of experience Bonnie, which can only be a good thing! Any time anyone uses the term 'main-frame' immediately conjures up images of covert hackers somehow changing the world - I won't tell anyone though, your secret is safe!

Think of a WCF service this way ... it's simply a back-end, server-side "application". After all, in a scalable multi-tier system, your database would actually be on a server somewhere accessible to the rest of your network. And the front-end (UI)
would need to call methods to the back-end to get at that data. Might as well have the WCF service running on a separate server, also available to the rest of your network (or at least to your application via HTTP and/or TCP/IP communication), wrapping up
all that data getting/processing functionality.

There's nothing wrong with having a BLL available to both the front-end (UI) and back-end (WCF or a DataAccess assembly). The same BLL project can be used for both front-end and back-end. Again, there's nothing wrong with that.

Now, technically, there's also nothing wrong with having both the UI and the WCF service running on the same machine. It doesn't *have* to be on a separate server. That all depends on your application and it's intended usage.

>> Any time anyone uses the term 'main-frame' immediately conjures up images of covert hackers somehow changing the world <<

For me at the moment, I think WCF is unnecessary - I'll return my attention to that subject at a later time!

You know what's interesting? 99.9% of what you have helped me with turned out to relate to a digression (N-tier)! Thankfully, I've returned to the point where I was weeks ago, albeit with a proud face and n-tier solution (some sarcasm there, but only some!).

You mentioned binding before, and that the UI controls should be bound. If that's the case, I understand how I can bind a textbox etc., but the original question I (think I?) had was: how do I display multiple childrows against a parent row of data?

Example, based within a button_click event handler for ease, and testing with two gridviews (you'll see why!):

I create an instance of my DataSet and fill the two tables (tblCustomers, tblTreatments) within it.

I set the datasource property of my tblCustomersBindingSource to ds.tblCustomers.

If I run it, my first gridview loads my customer data.

Problem:

I have multiple treatments against any one customer. It could be 1, it might be 8 etc.

Whilst I can replicate my success above but with tblTreatments, I can't figure out how when the customer record is changed, I make use of my existing relation to alter the records displayed in my second gridview.

Note: I checked the count property of my ds.tblCustomers.ChildRelations.Count, which gives me '1', which suggests my relation's there. I seem to remember seeing a 'Merge' property used by somebody, but the bindingsource of a parent table used for the datasource
of a child table or something. Either way, I've mixed it up and tried and can't figure it out!

If I can offer any reassurance that I won't bug you soon, the fact that I'm returning to my initial query is hopefully it! :-)

I ended up researching it thinking you hadn't responded and came up working with the same data.

As it happens, I cannot find a solution anywhere relating to managing bound DateTimePicker controls when the field value is null (I've been trying to do this on a project different to the one in which you helped me on multiple DataSets).

So far, I've read countless SO articles of hearing the same response that because of the underlying implementation of the row, within the table of the typed DataSet, it doesn't support a null value. That's fine, as I'd quite happily show an obviously wrong
date (1,1,1900, for example), but I can't see a way of intercepting the DBNull value before it is presented to the control. The only thing I can find relates to people trying to add/edit/update tables based on the property. But, my problem occurs prior to
that!

I did miss it, and I thought it had solved my problem, but I must be missing something else.

As of now, when I click a button that binds the controls to the necessary binding sources, the date in the DateTimePicker shows the value within the DataSet, as desired. However, once I navigate forward and then back (MoveNext(), MovePrevious() within the
binding source), the DateTimePicker simply displays the 'MinValue'. This is despite me setting the 'Text' property of the DateTimePicker within the binding.

Also, when I click the 'Next' button that has the 'MoveNext()' method, it executes the DateTimePicker_ValueChanged event 6 times (start to end). Why?!

Still though, when I call 'MoveNext()', the [datecontrol]_ValueChanged event handler fires 6 times when it moves from the initial record (with a non-null value) to one with a null value. Moving next to subsequent records executes it twice whilst moving back
does the same until it gets back to a non-null date where it only executes once.

You didn't mention about the DBNull the first time you talked about this. I'm not seeing that behavior. I see the ValueChanged fire only twice when it "hits" a Date that's null. That's because the normal behavior of the DateTimePicker is to default
to DateTime.Now if there's a DBNull.Value and set the .Checked = false (even though you don't see the CheckBox unless you turn it on, which you don't have to do for this to work).Apparently the .Checked false indicates that we don't have a valid date.

That's when we set it to .MinDate, which turns the .Checked to true (and fires the ValueChanged event again), but we need to set the .Checked back to false after that, because we have what we consider to be a NULL date. I suppose maybe it doesn't matter,
because we'll probably pick a new date anyway. And, as you showed by changing it, it doesn't seem to matter.

Anyway, I have no idea why you see it firing the ValueChanged a million times. I've moved back and forth through the rows and have only ever seen it fire twice, as I've explained why above. How are you determining how many times the ValueChanged fires? I
simply put a Console.WriteLine() as the first line of code in the ValueChanged event handler.

I see the logic behind the 'Checked' property, and the code you supplied. Incidentally, I tried a DateTimePicker in a test project and test database with the following results:

Using the code as supplied (setting '.Checked' to false), the DateTimePicker Text remains at 'MinDate'.

Altering '.Checked = false' to '.Checked = true' sort of works. As, in this case, the second record of 3 within the database contains an actual date (records 1 and 3 are DBNull). When controls are loaded with the first item, and 'MoveNext()' is called on
the BindingSource, the second record's date is 'MinDate'. Going to record 3 and then back to 2 then shows the correct date. This is as good as I can get it, which is still not right!

As for the amount of time the code executes, I have it as 2 in my test project as well, using a debug.Print statement in the same place as my main project. Clearly there's something more going on in my main project that needs some attention.

Ha Ha ... I won't abandon you, Jon! But, I just don't think I can figure out anymore where the problem is, since it seems to work fine for me! Good luck with this, I hope someone else comes along with some ideas! (Although, I think you should
post a new question, since this one has taken a *big* thread drift!!!)

When you do, post a link to your new question here, so I can follow along with other people's replies/ideas (I won't jump in though, since I'm obviously out of ideas myself!!)

Now I'm on the case of updating typed DataSets where there are joined queries (and, in this particular case, using the table adapters provided - mostly for the challenge, though I know it may offend you Bonnie!) - an interesting topic!

Thanks for all of your help. I'll mark the code you put up above as the answer as it did clearly lead me to success!