Table of Content
- Introduction
- An exercise in Data-modelling
- Develope the Model
- Model-Guidelines
- Walkthrough all Properties
- Datamodel-Summary - and About AllowDbNull
- Layout-Preparation
- Fetch the Datasource-Window
- Build the "Raw-View"
- BindingSources (please remove the BindingNavigator)
- BindingSource-Naming
- The Four Views
- 1 - ParentChild-View
- 2 - JoiningView
- 3 - m:n - View
- 4 - Detail-View
- Custom DetailControls
- Custom ListControl as "Selector"
- Custom ListControl as "Joiner"
- DataExpression - calculated DataColumns
- The Sample-Application
- Concluision
- Still Topics are missing
This article is the continuation of my previous article Relational Datamodel for Beginners, where I introduced the basic Idea of relational Datamodelling, and how to implement a model with VisualStudios typed-Dataset-Designer.
That article ended with a small Sample-Application, which demonstrates two main-benefits of a strong typed relational Datamodel (namely a typed Dataset):
- You can code against it in a objectoriented, typed manner, loop through data or collect it by Linq-Expressions or whatever. There is no need for any cast or any untyped data-request by "Property Name String Code Smell". On a later article I will go in there deeper.
- You can present data by configuring Databindings in the Form-Designer. "Presentation" means: Not only look at it, but support full CRUD (add, edit, delete - as far as you don't restrict that)
The Presentation works reliable, consistent and with an absoutely surprising minimum of Code-Lines - you will be really amazed, if you haven't seen such yet.
Although thats articles Sample-Application demonstrates Databinding, the article did not explain how it was configured, to do its job.
And that is the subject of this article: Explain that, and explain it within a more general context - I call it the "four-Views" - Concept.
But before start design a databinding-driven Gui, there are several preparations to complete.
If you're not very familiar with several technical terms (Entity, Model, Relation,...), I recomend to refer the previous article again, especially my definition-tryals.
You can also go there to learn the basic usage of the Dataset-Designer - I don't repeat that here.
Meanwhile I published a third article in order to cover some Topics, which here still are missing: Programming against typed Dataset
An exercise in Data-modelling
Every Data-Application-Development should start with designing a Datamodel. This may be a hard change of thinking to programmers, who used to think of their program as about of Textboxes, Datagridviews, Comboboxes etc.. - all this stuff is irrelevant - you must think about Data, and moreover about the reality.
Data-modelling is really what the word sais: you create a model of the reality. When I tried to explain the term "entity" in the previous Article, I had some troubles, because I could not distinguish exactly, whether "entity" means a peace of Model or means a peace of reality.
But exceptionally in this case I love that double-meaning, because - while designing a model - when I make a statement about reality, at same time that is a statement about the model and vice versa.
Develope the Model
Assume a mail-trading-company. It offers Articles, and customers order them - which entities occur? In fact, three of them are already defined, by the simple statement: "Customers order Articles":
1) Customer
2) Order
3) Article
To make it a little more complex lets structurate the articles in categories: 4) Category
You can already now open the Dataset-Designer and put the entities on it:
And a good habit is to configure the PrimaryKeys too - since that is the first Modeling-Guideline: "No Entity without a PrimKey!"
Now think about the relations: a Category contains many articles - right?
And a customer may order several times - means: a customer has many orders.
But what is an Order, what contains it? Think simple, and of concrete reality - a customer may order: "I want 5 bottles of beer and 16 saussages!"
You see, his order mentions 2 different articles (and could be more). Means: An Order contains several Entries. And each Entry refers to an article - and associates a Count with that.
So here we have the 5th Entity, named: 5) OrderEntry
.
As explained in the previous article, when an Entity can contain several others, then there is a 1:n - Relation, one-to-many, or Parent-Child-Relation. We already found three of them:
1) Category=>Article
2) Customer=>Order
3) Order=>OrderEntry
And there is a 4th Relation, we already stated: "an OrderEntry refers to an Article" - is at same time a definition in our model: 4) Article=>OrderEntry
In Fact, the Sentence: "A is referenced by many Bs" is the "Relation-Validation-Statement". If it makes sense, then a one-to-many-Relation is present, otherwise you have to think twice.
For Practice, lets validate, whether our Relation-Concept makes sense:
- A category is referenced by many articles - ok
- A customer is referenced by many orders - ok
- An order is referenced by many order-entries - ok
- An article is referenced by many order-entries (too) - ok
So our Datamodel comes out as:
You see, I already addes the most-trivial attributes: Categories and Articles have names, an article has a price, customers are seen as companies (in this model), an order has a Date, and - as seen from our customers concret call: an OrderEntry has a Count.
And of course I added the necessary ForeignKeys to subordered Tables - why, that is explained in detail in my previous article.
In repetition of that I show the recommended configuration of an usual 1:n - Relation:
In Words: enable ForeignKey-Constraint, Update-Rule.Cascade, Delete-Rule.Cascade, Accept_Reject-Rule.None
The completed Datamodel, with all Entities Attributes:
I recommend, to build that yourself, and if you unshure how to do, refer to the previous article.
I presented the Model intentionally step by step, because the Dataset-Designer can accompany you while thinking, and helps, while the designing-process unfolds.
Model-Guidelines
- No entity without PrimaryKey
- Short, meaningful Naming
The designer generates lots of classes, Properties, Events, Methods - all of their Names are derived from the Name-Base, you set in the Datamodel. So a small suboptimalism will multiplicates a hundred times, and you have to deal with it as long as the program exists - Name PrimaryKeys simply
ID
- always - (shorter and meaningfuller is impossible ;) ) - Name ForeignKeys by
ParentTableName + "ID"
Eg Order.CustomerID
, or Article.CategoryID
. If done so, everybody immediately recognizes Primkeys, ForeignKeys, and to what Parent-Table the latter refer. - Don't frighten name-conflicts
in a typed Model the same name, used in different tables do not cause name-conflicts, not even indirectly.
It's impossible to mix up an Category.Name with an Article.Name, because Category and Article are completly different and independend classes. - In cases feel free to use your own language.
Shortness and Meaningfullness to you and your collegues may be more important than satisfying more abstract demands of an international standard. A Model can contain about hundreds of terms, some of them difficult to explain - it's very hard, to find always the exact matching english expression. - Make the model strict - avoid AllowNull
By default the designer configures properties which are allowed to be un-set. Don't do so! Try always to enforce, that properties are to set, and only allow Null, if you have good reasons for that.
Instead of AllowNull mostly one can configure a DefaultValue
, eg String=""
, Bool=False
, Int=0
, and stuff like that. That prevents NotNullAllowed-Exceptions as well, since a NullValue can't occur - in doubt always the DefaultValue is present.
Trust me: That trick eases the later coding against the Datamodel significantly - Make the model strict - try use ForeignkeyConstraint and cascaded Deletion
Usually the childrows become invalid, when a parentrow is deleted. For that, simply configure cascaded deletion, and you don't need to think about that further. - UpdateRule.Cascade
This one becomes important, when accessing to real Databases. But better get used to do so from the beginning - it doesn't hurt. - Respect Datatypes
I'm heartbroken, that I must include this point, but I've seen so often Numbers, stored as Strings, Dates, stored as Strings, Booleans, stored as Strings, such hurts :-( - No Prefixes
Prefixes, if they make sense at all, their purpose is to distinguish several types. In a relational Datamodel there are only Tables. So what's the use, to prefix each Table with "tbl"? Everybody knows by himself, that OrderDataset.Article
is a Table - what else such should be? - Name singular
As said, "Entity" means both: the concept of a record, and the particular concrete record. Yes, a Table also defines the concept of the contained records, and a Table must be seen as plural.
But in Code you will deal mostly with the particular DataRow. And when the Table is named plural, then the Dataset-Designer generates the particular DataRow also as plural - and that is definitely wrong.
Eg the Method OrderDts.Articles.AddArticlesRow(...)
expresses by its naming, that it adds several Articles - which is not correct - the Method adds only exact one Article.
For that name Article singular, and the Dataset-Designer will generate: OrderDts.Article.AddArticleRow(...)
, which does what it says.
Some of "my" Naming-Convention you may recognize, when you refer to other naming-guidelines, for example compare Naming-Guidelines published by the Illinois Longitudinal Data System Project.
Walk through all Sample-Model-Properties
Sorry, that it takes so long, before we can start with Drag & Drop, but as said: The model is the foundation of the application, and although each Property can be done in seconds, each details of each property is cruicial.
So here a List of configuration-details, which may be worthwile to note:
ID
-Property of all Tables is configurated as follows:
Primkey, Integer, AutoIncrement, AutoIncrementSeed=-1, AutoIncrementStep=-1
One Exception of that: Microsoft, the autor of the Northwind-Database, from where I derived my Datamodel, preferred to design the Customer-Primkey as String, for some Reasons :wtf:
Effect is, there will come up Problems, when you want to create a new Customer, because the Dataset can't automatically generate a valid unique Primkey of type String :-( - all Properties of all Tables by Default:
AllowDBNull=False
If in contrary a Property is NullAllowed, there is a reason for, I'll explain - Properties
Customer.ContactPerson, .Position
and .Telefax:
The term "<unset>"
is set as DefaultValue. This prevents NotNullAllowed-Exceptions on unset Values, since there are no unset Values, since per default "<unset>"
is the value, which informs quite clearly about an unset value - without exception - knowWhatImean? ;)
All other Customer-
Properties are required - a customer without complete contact-data is inacceptable - Properties
Order.DeliveryDate, ShipDate, ShipCosts
Theese are AllowNull, because it has a specific meaning, eg when the ShipDate is unset (an unsettled order) Category.Image
AllowNull, since such an Image is an optional gimmick Article.Level, .OrderedUnits, .MinimumLevel
Theese Amounts have DefaultValue=0 to prevent NotNullAllowed-Exceptions. 0 is a reasonable, valid value for theese Properties - especially on new Articles.
Datamodel-Summary, and about AllowDbNull
You see: Each Property of each Table was handled with thoughtful care, especial about the Question, wether to allow Null or not. Three different Decision are possible:
- AllowDbNull=False
the standard - prevents entering records with irregular NullValues by ConstraintExceptions - AllowDbNull=False + DefaultValue
this causes no exceptions on missing values, since a NullValue simply can't occur - in doubt there always will be the DefaultValue - AllowDbNull=True
sounds generous, but in contrary this Configuration causes the most problems:
Whenever you want to retrieve such values by Code, before that you must make use of another method, to check, whether the Value is set or not.
Otherwise you get a StrongTypingException
, when the Value was not set.
Layout-Preparation
First we need a form, I recomend to put a Menustrip on it, and a TabControl.
Then remember the Datamodel, we have 5 Entities/Tables, related as Follows:
Customer => Order => OrderEntry <= Article <= Category
So for the first View, called the "Raw-View" prepare a Layout, where each Table can find its Place, and every table is clearly labeled to avoid confusion:
Can you do so?
The TabControl is docked with Dock.Fill
, actually all the (Container-)Controls are docked like that: On the "RawView"-Tab there is basically a SplitContainer. On its left SplitterPanel is the Groupbox "Customer+Orders". Within that there is another SplitContainer, with Orientation.Vertical
. Its top-SplitterPanel contains the "Customer"-Groupbox, its bottom-SplitterPanel contains one more SplitContainer, now horizontal again. Its SplitterPanels contain the Groupboxes "Orders" and "OrderEntries".
So in general, the relations Customer => Order => OrderEntry
is prepared on the left of the Tabpage, and Category => Article
is on the right.
Everything lays on SplitContainers, so when you work with it later, you always can drag the current workspace to optimal size.
I always start with a "Raw-View", to get my Datamodel presented, even quick & dirty, but complete. So I can input Test-Data and can test basic behavior, and I can save the test-Data to be able to re-access it on further test-runs.
Fetch the Datasource-Window
Open the Datasource-Window: "Menu - View - Other_Windows - Data_Sources":
The DataSource-Window looks like this:
It is our Datamodel - "NorthwindDts", and all its 5 Tables - presented as a treeview.
To start bind the Customer->Order->OrderEntry
- Tables, just expand Customer
, and in Customer expand Order
- you will find OrderEntry
in it.
That is how the DataSource-Window represents the Customer->Order->OrderEntry
-(Relation-)Line:
Build the "Raw-View"
Now drag the Customer from DataSources onto the Form, into the "Customer"-Groupbox.
This will generate a full configurated DatagridView, with all Columns. At first Dock it Dock.Fill - the easiest way to do so is to use the "Dock_in_Parent_Container" - Link of the Datagridviews SmartTag:
Do the same with Order
and OrderEntry
, and then propable you know for yourself, whats to do with Category
and Article
. ;)
Again - note: From the DataSources-Window-Treeview choose the nested Table, if you want a configuration, that shows the Data as subordered, according to the DataRelations.
Your Drag and Dock-Result should look like this:
Now improve all Datagridviews a bit, by setting their AutoSizeColumnMode
on AllCells
. For that you can select all the DGVs together (Ctrl.-LeftClick), and then set the AutoSizeColumnMode in PropertyGrid:
Believe it or not - we still need a little code to make that work:
Imports System.IO
Public Class Form1
Private _DataFile As String = Path.GetFullPath("..\..\NorthWindDts.xml")
Private Sub MenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles btReload.Click, btSave.Click
Select Case True
Case sender Is btReload
NorthWindDts.Clear()
NorthWindDts.ReadXml(_DataFile)
Case sender Is btSave
NorthWindDts.WriteXml(_DataFile)
End Select
End Sub
End Class
But then you're done - press F5 and see the result:
Of course In the Image it appears not very representable. On a wider screen that will be better.
Please note: The application now already is a runable prototype, and gives full CRUD-Support to every Entity, and to every single Record.
Moreover you have built your first "Parent-Child-View", which does some Databinding-Magic: When you select a customer, the Order-Grid only shows orders of that Customer, and when you select an Order, the OrderEntry-Grid only shows OrderEntries of that order (Customer => Order => OrderEntry
).
Same on the right: Select a Category, and you will get only the articles of that category. The mechanism can be recognized very well - note: every's shown article's CategoryID
matches to the ID
of the selected Category.
(As well as OrderEntrys' OrderIDs matches to the selected Order-ID)
BindingSources
As seen when you drag Entities on Form, the Designer generates configurated DatagridViews, including all columns. Another Part of that Configuration are the Bindingsources, which appear in the Component-Tray of the Form-Designer:
Sidenote - please remove the BindingNavigator
Moreover there is generated a NorthwindDts
, and a CategoryBindingNavigator
. The latter Component I recommend to remove. I'm always ranting against its stupid click-next/-previous - Buttons - can you imagine a more useless Control?
When you want present Data, present them, with Datagridview, Listbox, Combobox - what you like. But don't provide click-next/-previous - buttons - no-one will use such rubbish, when he just can scroll to the DataRecord he wants to select. And can there be a more irrelevant Information about a Record, as its Index in the Table?
So remove it.
Back to BindingSources: a BindingSource is a kind of "Data-Query" from the Dataset, and is to use as Datasource either for databindable Controls, or for other BindingSources.
It has two cruicial Properties: DataSource
and DataMember
. You can set theese in the FormDesigners PropertyGrid - eg our CategoryBindingSource
is configured as follows:
Its DataSource is the Dataset, and its Datamember is the CategoryTable. Thats why our databound CategoryDatagridView displays Categories - since its DataSource it the CategoryBindingSource
. You can see CategoryBindingSource
as "querying" Category-Records from the Dataset.
Now look to ArticleBindingSource
's Configuration:
Its Datasource is not the Dataset, but its the CategoryBindingSource
! And its DataMember is not a Table, but is the DataRelation Category=>Article
. Thats why our bound ArticleDatagridView displays Articles, and it does not display all Articles, but only Child-Rows of the CategoryRow, which is currently Selected in the CategoryGrid. You can see ArticleBindingSource
as "querying" Article-Records from the Dataset, pseudocode-query like:
"Select * From Article Where Article.CategoryID = <CategoryBindingSource.Current.ID>"
BindingSource.Current
- that is the second Main-Concern of a BindingSource: It is a CurrencyManager, which communicates with the bound Control, and the BindingSource always "knows" the current SelectedIndex of the Control - no matter whether it is a DatagridView, a Combobox or a Listbox.
And same thing the other way round: When a BindingSource changes its .Position
-Property, than the Control will change its Selection too - they synchronisize - that is meant by the term "Binding".
BindingSource-Naming
While going on we will get much more BindingSources, which will provide the Data in several Views. For that I recommend a consequent Naming-Convention - otherwise you will confuse yourself.
- Prefix
bs
I recommend to all BindingSources - in general - The BindingSource-Name shall say the Table or the Relations, whiches Data it provides - in our sample theese are (upto now):
"bsCategory"
, "bsCategoryArticle"
, "bsCustomer"
, "bsCustomerOrder"
, "bsCustomerOrderEntry"
- some particluar BindingSources later will provide Data to Comboboxes and Combobox-Columns.
For that use a second Prefix Cmb
, eg "bsCmbCategory"
could be Name for a BS, which provide Category-Data to a DGV-ComboColumn - i will come to that later.
The Four Views
In general I state four basic-ways to present Data. All presented Data ever can be seen as one of them or a specific composed combination - The basic-views I call:
- ParentChild-View
- Joining-View
- m:n - View
- Detail-View
These Views are not Dataset-specific - not even dependent on a relational model.
Every complex Datamodel can be presented in Variants and Combinations of theese four basic Presentation-Types (and nearly every actually is).
1 - ParentChild-View
You already know it - the "Raw-View" above presents all of our DataRelations in ParentChild-Manner: A Selector-Control selects a ParentRow, and another Multi-Item-Control (Listbox, Combobox or DatagridView) presents the ChildRows of the selected ParentRow.
Here a simpler Example, presenting Category => Article
:
2 - JoiningView
A "JoiningView" is present, when a Datagridviews contains Combobox-Columns, selecting ForeignKeys, which reference to superordered ParentRows.
Look at this Article-DatagridView:
It presents ArtcleName and DeliverUnit as well as CategoryName and CategoryDescription.
In Sql one would query such View as follows:
SELECT Category.Name, Category.Description, Article.Name, Article.DeliverUnit
FROM Category INNER JOIN Article ON Category.ID = Article.CategoryID
Therefore I name that kind of View "JoiningView", although it is done without Sql.
Note, that a JoiningView can do, what Joining-Sql never could do: namely change ForeignKeys, and save Changes back - see a ComboboxColumn-Cell in Action:
I change "Chai"s Category from "Getränketest" to "Gewürze".
In the Background thats articles CategoryID gets a new Value, now refering to the "Gewürze"-Category-ParentRow - it is simply an Integer, which changes.
To achieve that Behavior, at first drag the Article-Entity on Form, to generate a DGV. Then open via SmartTag "Edit Columns", and change the CategoryID-ColumnType from DatagridviewTextboxColumn
to DatagridviewComboboxColumn
Then choose the Datasource of that ComboboxColumn:
Choose with care, namely select OtherDataSource-ProjectDataSources-NorhtWindDts-Category. Choosing that way will generate a new Bindingsource.
Then choose the DisplayMember - which Property the ComboboxCell shall display?
Of course the Name of the Category is most important to be displayed.
Next choose the ValueMember - Which Property is the "meaning", when a User selects a Category?
The "Meaning" of that is the ID
: the PrimaryKey of the selected CategoryRow. This Value will be written to the (Article.)CategoryID
- (compare with what the DataPropertyName sais).
That is difficult, and you must have a good understanding of what theese 4 Elements are for:
- DataPropertyName -
CategoryID
: The Name of the to this DGV-Column bound DataColumn.
Here the DGV is bound to the ArticleDataTable, and CategoryID
is the ForeignKey, referring to the CategoryDataTable. - DataSource -
Category
: the ParentTable, whiches Rows are ParentRows of the ArticleRows.
Theese Rows are shown, when the Combo-DropDown drops down - DisplayMember -
Name
: means Category.Name
the visible Column of the ParentRows, which are shown, when the DropDown drops (since a Combo can only show one Column) - ValueMember -
ID
: means Category.ID
When the User selects a Category then this Value - the ID
- gets written to this' DatagridviewColumns DataProperty, defined by DataPropertyName
.
In concret: The selected Categories ID
gets written to the current Articles CategoryID
(means: that Foreignkey gets changed).
And again Binding works bidirectional: The ComboBoxCell can write another Category.ID to Article.CategoryID as well as it displays the Category.Name of the Articles Category, as referred by that ForeignKey.
You see: Compared to Sql it's a complete different concept and thinking, which achieves to join in Properties of other (Parent-)Tables.
3 - m:n - View
The m:n - View simply combines ParentChild- and Joining-View.
See the Order-ParentTable, and the OrderEntries of the selected Order:
You see, in OrderEntry is a ComboboxColumn, which refers to the other ParentTable: to Article - remember the Datamodel:
Order => OrderEntry <= Article
Sql would express the OrderEntries as follows:
SELECT Article.Name, OrderEntry.Count
FROM Article INNER JOIN OrderEntry ON Article.ID = OrderEntry.ArticleID
WHERE OrderEntry.OrderID = <bsOrder.Current.ID>
4 - Detail-View
(The term "DetailView" often is used unspecific, sometimes ParentChild-Views, sometimes JoiningViews are meant. So no-one seems to distinguish, whether the details come from Parents, from Children, or from the current Table itself.
In my term-usage "DetailView" means to present one Datarecord detailed, means: each of its Property is presented in its own Single-Item-Control.)
The Selector-Control (DGV, Combo, Listbox) selects a Record, and a set of Single-Item-Controls (Textbox, Label, NumericUpdown, DatetimePicker) presents the Values of the selected Record - The ItemControls do not present other, related Tables, neither Parent- nor Child.
Building a Detail-View is basically as easy as the ParentChild-View: First drag the entity on Form, to get the Datagridview generated, as Selector.
Then change the Generation-Mode of the DataSource-Window to "Details":
Then drag the same entity once again on Form, on an empty Panel:
Result:
For each Article-Property we have got a Label and a Textbox - in case of a boolean Property it is a Checkbox.
On DetailViews I recomend to reduce the displayed DatagridviewColumns to only the most significant Properties.
Otherwise the Gui would be overcrowded with Data-Presentation.
Use the DGV-SmartTag, to "edit Columns":
Then remove all columns, until only Name
is left. In same Dialog you also can set Name
s AutoSizemode to Fill
:
Result:
Now I recommend to remove useles Detail-Controls as well, and give a meaningful Text to the Checkbox:
At Runtime:
Often you want to bind other Controls as that what was generated by the Datasources-Window. No Problem, put on Form what you want, and bind it.
Eg Category.Name
I bound to a Groupbox's Text:
Did you see, that I expanded the (DataBindings)
-Node in the Groupbox-PropertyGrid?
And Category.Image
of course I bind to a Picturebox-Image
Picturebox's Binding is a bit buggy - whenever a DataRecord changes, the Picturebox marks the previous DataRecord as "Changed" - although a Picturebox never can change a Picture.
The Workaround to avoid that is to configure that Binding more detailed, for that open the Advanced Bindings - Dialogue:
Set Datasource-Update to Never, and the Bug is workarounded.
Custom ListControl as "Selector"
As said: Bindable ListControls are: 1) DatagridView, 2) Listbox, 3) Combobox. So you don't need necessarily to bind Tables to DatagridViews - for instance I bound additional a Combobox (to do so I used Comboboxes SmartTag):
Note: DisplayMember=Name
- The Combo will display Category.Name
- Values.
Also note, that ValueMember and SelectdeValue left unset (this combo works as "Selector", not as "Joinier" - I will come to that later)
That Combobox can select a Category as well as the DatagridView can - actually Combo and DGV are synchronisized, since they are bound to the same BindingSource.
See databound DGV, Combobox, Groupbox, Picturebox and (Description-)Label at runtime:
| Side-note: The Dgv looks like a Listbox, because I set
-
CellBorderStyle.None Row- and Column-HeadersVisible=False ColumnHeadersHeight=23 AutoSizeColumnsMode.Fill .
You see: Datagridview is enormous customizable.
|
Custom ListControl as "Joiner"
Like a ComboboxColumn joins Values of a ParentTable into a JoiningView, so a Combobox can join ParentValues into a DetailView.
Go back to our Article-DetailView, and add a Combobox, as shown below:
Like at the DGV-ComboboxColumn choose OtherDatasources.NorthwindDts.Category
as Datasource, to ensure, that a new BindingSource is generated, especially for this Combobox.
Otherwise Side-Effects can occur, when several Controls are bound to the same BindingSource.
The complete "joining"-Combobox-configuration:
It is very similar to the joining DGV-ComboColumn - again 4 Elements are cruicial:
- The Datasource -
CategoryBindingSource
- provides DataRows, shown in the DropDown - The DisplayMember - (Category.)
Name
defines the Column of the shown DataRows to be displayed - The ValueMember - ID - defines the "Meaning", when a User selects an Entry.
- Selected Value -
bsArticle-CategoryID
- this is a bit different to DGV-Combos. In a Combobox the Selected Value can be any Column of any BindingSource. And the Selected Value will be persisted there.
That means: From a selected Category the ValueMember - ID
will be written to bsArticle.CategoryID
.
Its the same as in the DGVComboColumn: The selected superordered Primkey gets written on the Foreignkey of the current ArticleRow.
At Runtime:
And is an Instrument to change the Category of a given Article.
DataExpression - calculated DataColumns
Let me shortly mention a further DataColumn-Feature, namely the DataColumn.Expression-Property (I strongly recommend to follow the link):
You can assign small math-formulars, and the DataTable will always have the Values updated - similar to Excel-Cell-Formulars.
Moreover calculated Columns need not to be persisted to the Database, since the Values are always re-calculated.
For instance I added a DataColumn Price
to OrderEntry, with the Expression:
Parent(ArticleOrderEntry).Price * Count
Trivial, isn't it? The OrderEntry-Price is Article-Price * Count.
Then I added a Column OrderPrice
to Order, with the Expression:
Sum(child(OrderOrderEntry).Price)
Even no rocket-science too - the Order-PriceSum is the sum of all OrderEntry-ChildRow-Prices - what else?
See my m:n-View now:
Also note the new ArticlePrice
- ComboColumn of OrderEntry, whiches DisplayMember is (Article.)Price
. In the Configuration I set the Combobox-DisplayStyle to Nothing, because I don't want the User to be able to change the ordered Article with that DGV-ComboColumn.
For that the ArticlePrice-Column looks like a Textbox, but actually is a joining ComboboxColumn.
And I made some Entries in the DefaultCellstyle:
Namely I choosed the non-proportional Font Courier New
, set Format Currency, and aligned MiddleRight - this seems to be not the worsest way to present Currency-Values ;)
It is not a wonder of Design, but it works, and shows a lot of basic Databinding-Behavior.
Conclusion
Huh - that was a lot of stuff, wasn't it?
None of it is really hard to understand, but some points cause us to change our View.
- First - in the previous article - is to understand the difference between relational and hierarchical Model. And that in relational a ParentRow does not contain ChildRows - instead of that each Child refers to its ParentRow. And how that is to achieve by Primary-/Foreign-Keys - which define the DataRelation.
- Then there is to understand, that a relational model is not a Database - in contrary: Without Database-Backend a typed Dataset is much easier to design, to build and to change. An underlying Database always is a second, redundant Model, and it is always a challenge, to map the Database-model correctly to the ClientApplication-Model, which is in use in the client-application. A Database often can be renounced, but the ClientApplication-Model never is dispensable.
- Learn that Sql mostly is not needed, since it belongs to the Database-Model, not to the Client-Model.
- Next is to forget about Code and Controls and stuff. Instead of that thinking must become very simple, and must think about Reality. The Datamodel must not satisfy any need of the application. It only has to meet Reality: Statements about the model are at same time statements about Reality
- Then learn use the Dataset-Designer, and learn use it well, with care to every single Property of every Entity: Naming, Datatype, whether Null is allowed, DefaultValue, evtl. an Expression
- Learn and follow Datamodel-Guidelines (you can choose other Conventions than I provided - one of the main things is to follow a particular Convention consequently)
- Then finally start with Databinding, using the DataSource-Window
- Learn what BindingSources are, and what for. And name them with care, to beware of confusion
- Learn to know a lot of SmartTags, Properties and Configurating-Dialogs to configure BindingSources, DatagridViews, DatagridViewColumns, Comboboxes, Textboxes, Labels, DatetimePicker,... and even learn to use Tabcontrol, TableLayoutPanel, Groupbox and SplitContainer to layout the Area, where Data is presented.
Still Topics are missing
- I didn't mention the Problem of form-transcending Databinding, especially how to build Data-Input-Dialogs (Input-Masks)
The Winforms-Designer is a wonderful tool to design rich and reliable Data-Presentation, but unfortionally it generates on each Form/Dialog its own typed Dataset. In Result the Dialog has no Data, because it was the MainForm, which filled its Dataset, and it would be a bad idea, to fill the Dialogs Dataset too, since that ends up in Data-Redundancy-Confusion. - a deeper walk-through, of the stuff, which was generated by the DatasetDesigner
- Clean coding against typed Dataset, without Code-smells
Most programmers, who luckily managed to build a typed Dataset, immediately fall back into untyped programming - without more reason, than that they don't know the new capabilities and powerful Features, a typed Dataset provides. - Partial Classes: One can enhance each typed DataClass with Partial Classes, and place arbitrary BusinessLogic there. Very powerful capabilities!
IMHO 1) - 3) are also necessary, to develop (small) databinding-driven Applications
For that these topics meanwhile are covert in the continuation-article of this, and can be seen as a preliminary completion of the first Entry into a specific "world of development".