Introduction

 Top Next

These eNotes have been specifically written for the DevShare 'Olympic' developer event and conference.

As such they are not for general circulation to all Vulcan colleagues at the moment.

Some of this material may be released in some Pearls articles, and much is likely to be used for VODC 2012

in November of this year.

Please keep this to yourself, as others have not contributed financially to making the event a success.

I hope the contents of the next sections and chapters help you in your quest to move into .NET and WPF, as

well as supporting technologies like SQL Server, and XAMl scripting.

I am glad I made the effort to move over, and now I am enjoying the journey a Great Deal ;-0)

I hope this happens to you !!

PHH - Friday 13th. April 2012.


Learning support material

 Top Previous Next

As well as the learning material in these eNotes, I will supply a few working development VS solutions

which are working apps but also 'work in progress' projects.

However, you also need to know that I have made a HUGE amount of Vulcan.NET code available to the

community over the past four or more years.

You need to visit the sites of :-

http://www.clickstartvulcan.net/

for the original / older eVolumes of eNotes in the ClickStart series, as well as the newer site of :-

http://www.winclickstartvulcan2.net/ClickStartExec2.html?introduction.htm

where I am building up the newer eVolumes of the ClickStart series.

The very latest eVolume is not yet on the main option list in the Windows based site of mine, where the domain name

'WinClickStartVulcan2.net' represents that fact that the site is a Windows based one, and one from which I can also run

Silverlight projects.

The direct URL is as follows, and it will be added to the list sometime soon :-

http://www.winclickstartvulcan2.net/WebNotes/ClickStartVN2/ClickStartVN2.html

Here is what the new eVolume looks like - contents is gradually filling out ;-0)

==============================================================================================================================================================

before you move on, don't forget that there is much good stuff to be had from published text books on IT related

topics. I get a lot of inspiration from buying quite a spread of such texts.

There is a limit on what can be achieved by 'Googling' alone, and I also find that guys who are in a rush to publish

at a Google level are often NOT the guys to be leading our learning ;-0)

I have found an awful lot of poor and misleading 'stuff' out there just ready for Google searches and finds.

Yes, we can often find what we are after - eventually - but we may need to throw away a lot of 'junk' first.

Try to use text books more often, and quite frankly, look at my ClickStart eNotes.

Very often I see guys posting in the NGs and in the VPS section, and they clearly have NOT made the effort to see

what has been offered to them in ClickStart (FREE) before they post a plea for help.

It is good to show others that at least we are trying to help ourselves.

PHH - Friday 13th. April 2012.


What is on offer in these eNotes

Please be aware that I have prepared far more material than I will be able to show during DSO itself.

Being the organiser I needed to have suitable and interesting material just in case of problems with

guest speakers not showing up for any reason.

So some of these helpful suggestions and ideas may never get to you unless you read through the

following chapters, sections, and appendices.

I hope you don't miss any Pearls ;-0))

Hope you have a nive conference / event.

PHH - 6th. May 2012.


Chapter 1 - STATIC classes, facts and figures

I used to think that STATIC methods and properties were something complex and confusing. And so, as a

consequence, I only used those in the system, when I had to, and left things at that for quite a while.

However, after using a few common and standard examples of both of these in my general coding, over time,

I started to change my mind. I could see how easy they were to use and apply, and how useful some of them

were to me in my application development.

Very recently, I decided it was time for me to explore the depths of the STATIC class in general, and now

I consider the whole STATIC 'thing' a great aid to the developer's "toolkit", or armoury. We can quite often use

a STATIC approach to improve our code, and general development 'solution style'.

In this section on STATIC 'everything', I will demonstrate what is going on when we use the individual parts of

a static class.

First of all however, let us look at some 'System' static methods and properties we may well have met by now.

A Good Start :

Basically, it is a good idea to think of STATIC methods as Functions. The functions I used frequently in VO

can be made into STATIC methods. These can be used directly in our coding.

In fact some STATIC properties may also be thought of as functions in the old world, but also we could think

of them as GLOBAL variables. It all depends where you declare your class containing the STATIC methods.

When we use the 'System' static methods and properties, these are Global, it is our own that we need to

carefully scope, depending on where we wish them to be used.

Of course when we say that system static classes are global - that is provided that we code the namespace

correctly at the head of the appropriate PRG files, and that the system assembly is included in the References

section of the Solution Explorer (SE) pane.

A Big Difference :

Quite simply, another major difference when using Static methods is that we do not need to instantiate.

Well, actually it goes further than that - we must not instantiate, as this will cause an error at compile time.

[ *** Note *** so the consequence is we can't use SELF within the code syntax for STATIC stuff. ]

Study the following image :-

====================================================================================

And just to confirm the point, here is another good example using the 'System.Math' class.

Study the image below :-

============================================================================

Returning to the File class.

We will see in the next subsection many details about the File class in the 'System.IO' namespace.

For now it is enough to realise that it is a static class and so can't be used in instantiation. So to use

the static method 'File.Exists()' we must just use it 'as is' - no instantiation, allowed or required.

Similarly, when we have a class which may be used with instantiation, but within it there exists a method

which is static, we can't instantiate either, see below for an example :-

==================================================================================

We can't use the 'Now()' method with an instance of the structure 'DateTime'. The above image

shows what happens.

We can improve this as shown below :-

=============================================================================================================

As you can see from the output messages, the above syntax is acceptable to the compiler.

Both the instantiated oDT1 variable, and the oDT2 assigned variable obey the syntax rules.

Notice that when we use code for static classes, methods and properties we use a point ' . ' and not a colon

as in the rest of dotNet Vulcan syntax. Take care with this until you become familiar. It does get quite easy

after a short while.

My Tip - Points or Colons :

Let us now discuss another 'thorny' issue which hits any guy coming to dotNet and Vulcan - not just those

moving from Visual Objects.

When do we use a colon between our Namespace/Class/Method, and when do we use a point ?

My own rule of thumb is that if I am using something already existing in a namespace / assembly and I am

NOT instantiating, then I use a point - just as I would in C# for example.

However, if I am making a instance in my VN2 code, and then using this object, the colon is the thing to use.

Following this rule of thumb of mine, if I use either a STATIC class from the system or one of my own making,

then it is a point which must be used - below are two examples of this :-

=======================================================================================

The class 'SQL_my_funcs' is one of my own making - we will see a lot of this later in the following chapter.

In the code above I am using both a static method and a static property - lines 143 and 150.

In both cases I use the 'point' even though these are my 'stuff'. The colon is NOT used because I

have not instantiated. I like to think that once instantiation has taken place then the 'thing' belongs

to Vulcan.net and as such needs its colon syntax.

The above explanation may not be technically based, but it does serve me well while coding in VN2 - I don't

then need to think too much about it.

Let us move on to the next section and we can deal with some of the above issues in more detail as

things unfurl, and as we start to understand more.

In the next subsection we will look at some system STATICs in action. In the subsection after that we

will start to make our own.

See you in the next section .......

PHH - Friday 13th. April 2012.


STATIC methods, properties and fields

In this subsection we will start to look at STATIC classes, methods, and properties which are available

to us in the system namespaces.

Let us start with a simple, but commonly used method, that called 'File.Exists'. This is a method

called 'Exists()' in the class called 'File' which is in the 'System.IO' namespace.

Study below the relevant line (411) and the syntax used :-

=========================================================================================

Notice that we use the 'point' and not the colon, and that no instantiation takes place. The method is just

used directly.

If we look at the next image we see that the intellisense provides quite a lot of information for us :-

================================================================================================================

The context message says that the static method will return a Boolean, or logic.

Notice also that we can see a lot of other useful stuff for file management, like Copy and Delete.

Let us stand back a few paces, and see what the SDK docs have to say about the Namespace and the Class itself :-

[ note the File entry is the last one, right at the bottom ... ]

==============================================================================================================================================

Having clicked on the File hot link we get more detailed information on the methods - and other things too :-

=========================================================================================================================================

*** NOTE *** The red 'S' symbol / icon indicates that the method is a STATIC one.

This is commonly used across all the SDK docs.

Here are the rest of these :-

========================================================================================================================================

*** Note *** All the methods in the File class are STATIC methods. There is nothing else to this class.

[ Just 37 static methods to aid our development and coding - no properties, no constructor, or anything else. ]

Moving on to another commonly used coding area, that of DateTime.

'DateTime' is a structure, but it has a Constructor. Since we as coders can use the Class itself it is not a STATIC class.

However, do realise that static classes can have Constructors which are used internally by the static class itself.

Although the 'DateTime' structure can be instantiated, it does have some methods and properties which are STATIC.

Below is shown one of 11 overloads of the Constructor - we are instantiating with Year, Month and Day :-

=====================================================================================================

Here is a VERY commonly used STATIC property :-

=========================================================================================================================

*** Note *** I think this is an error on part of Microsoft, It is not a Class but a Structure !?

So the above is what we do if we want a 'DateTime' (DT) of this instance in time, both the date and the time parts

of the DT.

In the image shown below there is also shown a 'Today' static property which returns zeros for hour, minute, second etc.

after the date values of Year, Month, Day :-

==============================================================================================================================================================

You may have noticed that there are two STATIC fields which return maximum and minimum values.

I think the difference in Fields and Properties is that the value of fields is fixed and does not change during

the lifetime of the app, but Now and Today are variable and do change.

If this rule of thumb of mine is true then we would expect the values of Pi and 'e' to be fields also.

Have a look at what we see in the Math class to confirm our ideas :-

============================================================================================================================

Pi and 'e' are fixed values, constants of mathematics.

Back to the 'DateTime' structure :

Notice in the image shown below that we have some STATIC methods too for the 'DateTime' structure :-

========================================================================================================================================

If we look at the above image, we can see that there are a couple of methods which may well be useful to most guys, that of 'Compare',

and that of 'IsLeapYear', as well as 'Parse' too.

As well as STATIC methods and properties, it is possible to have static Fields and Operators as shown below :-

[ Remember that this DT structure is not a STATIC one, and can be instantiated. ]

=======================================================================================================================================

We have already made some basic observations about Fields, so let us now concentrate on the Operators.

Quite simply, the eight defined operators above mean that we can use symbols like +, -, >, <, >=, <=, <>, and = as

long at the types on either side of the operators are correct.

So for example, for + and - we must involve a DateTime and a 'TimeSpan', and the result has to be another DateTime.

So if we look at the SDK sample C# code we see :-

======================================================================================================

And in Vulcan syntax we have the following :-

============================================================================================================

WARNING - Take Care !!! :

On line 168, if we neglect to add the 'as TimeSpan' then the Vulcan compiler seems to make it a USUAL.

This does not fulfill the Operator requirements and so we see the following at runtime, as the syntax is NOT syntactically

incorrect :-

============================================================================================================================================================

In my tests I also noticed that the Try/Catch did not trap this, presumably because it is happening outside of the dotNet

'sphere' of the underlying system.

Message to me is clear, keep away from USUALs and strongly type everything.

Scope and Typing Update :

You may well be pleased to know that version 210 of Vulcan.NET has improved the above situation a 'LOT'.

We can now use and apply 'IMPLIED' in order to get our strong typing correct. Check out the following image details :-

However, if you leave out the 'implied' command word in line 188, we get back our runtime error and 'crash' situation.

My personal 'feel' for this is that 'IMPLIED' will make life a lot easier, and my code will be more correct as a result.

Moving on ..........

Further Care :

When we use the operators + and - we normally expect the order of the values to NOT matter. However this

is not true for how they are defined for 'DateTime'.

Checkout the following image, of what happens when we reverse our above order :-

==============================================================================================

It would appear that for the operators to apply they need to follow a DT value, for which they are defined.

The other values involved depend on the type of operator.

The other seven operators are pretty straightforward, as long as you get the correct data types in your statements,

and also in the correct logical order.

Summary :

As developer / coders, we use STATIC methods and properties as we would functions in other languages, and as

most of us did in VO.

We can use static methods, properties and fields from both System defined classes and structures, as well as those

of our own making.

A non-static class can have static methods and properties, and may be instantiated too.

Static classes and structures may have Constructors - which are used internally.

Where Now ? :

It would seem that we are in a position to try and make our own first examples of STATIC 'stuff', and to get a

feel for what can be done to make our coding life more easy, and possibly better structured too.

Eventually, we will see how to make class libraries of our STATIC classes, so that they may be used in many

of our different solutions - 'off the peg' so to speak.

See you in the next subsection where we get our hands dirty .................

PHH - Friday 13th. April 2012.


Defining our own STATIC classes

In this subsection we will create our own STATIC class, Methods, Properties, and a few other bits

as well.

We will get a good 'feel' for what is going on.

Possible Problem ;-))

Just a slight warning ! Like all things we try to demonstrate in a simple fashion, the quality and relevance of

the example is often 'suspect' and a bit meaningless.

Our first demo example here is probably such a case. Unfortunately, if we make it more relevant it becomes too

complex to early.

So if we wish to have a clear sample to study, we need to be aware that it is a bit 'Micky Mouse' as they say ;-0))

Where to Start ? :

Instead of introducing to a standard class, parts which are STATIC, and building up to a complete STATIC class,

I think it makes more sense to start with a full STATIC class, and work the other way.

To my mind we see quicker and easier what causes what, when we do a full class first.

Let us call our class 'LogDetails'.

In order to do our design, creation and testing we need a simple solution and project - as seen below :-

==============================================================================================================================================================================

To our newly made WPF app I have added a new folder and beneath that a file (PRG) to take the code for

our new STATIC class.

Study the following image :-

==============================================================================================================================

Well, obviously we need to make a few changes to this 'stub' code from the VS integration.

So after some thought and typing I have made a start to our little example, and here is how it runs - see below :-

=======================================================================================================

In the above image take special note of line 30, as this is the instruction which accesses the newly made class.

I will now show you the code of what we have, and which produced the above result.

Study the following code very thoroughly, as a few new things will be found in the syntax :-

=========================================================================

The above class was placed in the "VODCeNotesDemos" namespace.

Before we move on, let us explore the possibilities of removing the class name from before the method name

of any static method used throughout the other static methods and properties, and the constructor itself.

Take a look at the following image :-

=================================================================================================================

Although we have to use the class name in code outside the STATIC class, if we have the class defined as STATIC we

can drop the need to do so in code within the class methods and properties.

This topic will have to be revisited by us when we come to having static methods in an 'open' or non-static class. One

that may be instantiated. For now lets move on to examine the role of the Constructor.

What Part does the Constructor Play ? :

Let us look again at the calling code in the Initialize method of our new WPF form applet.

Study this image below :-

=================================================================

As we said previously, line 30 is the only place from which the static class is referred to.

If we have not done any instantiation, then how does the Constructor of the STATIC class get involved ?

It would appear that the answer is quite simple - for a STATIC class the first time any element of it is accessed by

code, the constructor is called, and any values setup within it are therefore 'set' for future use.

Yes, that means all static fields keep their properties 'as is' for the lifetime of the application, or until they are changed

by code accessing related properties and/or methods.

*** Note *** I think it 'taken as read' that the constructor has only one overload, and that takes no parameters !!

Thus it may be called by the system whenever it needs to do so.

Persistence of Values within the Class :

Let us explore this persistence by doing another simple test - see details in the image below :-

==============================================================================================

And now exploring using the VAT increasing method from outside of the STATIC class itself we see that the

value of the 'nVAT' field must have persisted from one use of the class to another. See the following image :-

=================================================================================================

So at this point I hear a number of you shouting - "Global Variables" - and this is true, we do have a tidy

way to afford ourselves the possible luxury of variables which are available across the application, or at

least across a number of WPF form classes.

We can see a good example of this later in the subsection on 'SQL_my_funcs' where I wish to have methods

and properties (and fields) available for ADO.NET use with my calls to the SQL database. Using a static

approach to this makes my life as a developer a LOT easier ;-) And I like that.

Existing Issues :

At the moment I have an issue which may be put to rights when I consult colleagues. At the moment even Alwyn's

book does not give me the answers :((

I see in the DateTime structure that there are two fields which are both read-only, and I think there are a couple more

in the Math class.

I do not know how to make my Fields publicly available (outside of the class) and yet not allow them to be changed

from outside.

I have tried the modifier term 'PROTECTED' and this is not what we require - see below :-

===========================================================================================

And the code used for this :-

========================================================================

"Watch this space !"

I will now chew Alwyn's ear ;-0))

Answers and Reasons :

It was there in 'the Book' after all, and Alwyn kindly pointed me to it.

In the following image we have public access to a fixed value field, and one that can't be overwritten

from outside of the class by any other - user or developer.

Study what we get at runtime :-

=========================================================================================

In the image above the code is using a field called 'RootTwo'.

Let us now view the code to see how I set up the class and the value for the 'Read Only' field :-

=========================================================================

Okay then, two steps forward and one step back ;-)

Firstly, we seem to have a fixed value field (here defined as CONST) which returns the correct value from outside of

the class itself.

If we do a test to see if we can change this value we get something like the following :-

=====================================================================================

This above output would suggest that we have done the right thing, and that we have a Read Only field.

However, the backwards step is that we have to have a value which exists at compile time. See the image

shown below for an example of the problem / issue.

=================================================================================

And at compile time the STATIC dotNet class cant' be used to provide the fixed value.

Unless we can find some way around this then we will be limited in at least the flexibility of such a way of providing

a Read Only fields - STATIC or otherwise.

Once again I will take advice. I don't think "the Book" can help ou this time as I have read all the related pages.

Keep watching this space.

STATIC Locals :

This actually may have more relevance to the next subsection, where we study a class which is not static but

which has some static methods and/or properties.

Make sure you visit the next section to see how field data may be persisted from one instance of a non-static class to

another, since we can already do this through STATIC fields in our STATIC classes, the relevance seems to be more

with non-static classes, and non-static methods.

A more Complex Example of a STATIC class :

Before we leave this section let me say that soon in this section we will see a full and 'proper' example of a STATIC class.

The class will be completely our own making, and will do a really useful job. It will allow us to use ADO.NET to access

our data in SQL tables, in an easy to code manner. We can then make sure this class is available for use anywhere in

our application.

It took me some years to realise that a Static Class was the way to go, but now I am there, it does feel right ;-0))

Probably time to move on to the next subsection, so see you there ...................

PHH - Friday 13th. April 2012.


Classes with mixed static/non-static members

Remember from discussions in earlier subsections, there are a few System classes which are of

a 'mixed' type. By a 'mixed' type I mean that the class itself is non-static, and that it has a few static

methods as well as non-static ones, and the same probably goes for the mix of properties, and possibly

fields too.

It is really the methods and properties which we will usually refer to when we talk of 'mixed' type.

Once again the example we will create and test is not a very clever, or useful one, but we can keep it

simple and short in lines of code, so that we can see clearly what is going on.

Before we look at the class and all of the supporting code, let us look at the result at runtime :-

=================================================================================================

So we see above we can use the un-instantiated form of the syntax, and all works OK.

After seeing the code, we can try to instantiate as well.

First study the code as see below :-

================================================================================

OK then guys, time for the big test - instantiation - see below for the image of the results :-

=============================================================================================================================

Well, the intellisense looks promising, so what do we get when we complete the code and run the test app ?

Study the full details carefully in the image below :-

=============================================================================================

Yes, we seem to be able to use both modes - instantiation and direct STATIC use.

You may notice when studying the class code VERY carefully, that I have tried to mix all the various ways of

defining methods and properties, as well as fields.

It is impossible to get a meaningful example, so you will just have to live with the best 'silly' idea I could come up with ;-)

You will see some use of the 'self' qualifier in the code, as well as the static syntax of 'Class.Member' too.

I have to say that the mixed form class is by far the most difficult to follow and even create. We are mixing two separate

approaches and although they work when coded correctly, they can be hard to understand for new guys coming to

STATIC 'stuff' for the first time.

STATIC locals :

Reading AJB's book I see that there is yet another type of variable which persists for the lifetime of the

application, and this is a local qualified / modified with a 'STATIC'.

The following tests will show that if within any method we use such a 'static local' the value of this will remain for the lifetime

of the app itself. This is even for non-static classes with non-static methods.

In the sample of code shown in the image below, we instantiate three MixedDetails objects, one after another. However,

the value of the count seems to be remembered from previous instantiations as the value keeps on increasing at each

and every new instantiation.

Study the code and other details very carefully :-

=======================================================================================================

[ The above image was generated with a bit of screen capture 'overlay' but is quite genuine otherwise! ]

Now then, what does the code look like which causes this behaviour ?

Notice in the following few lines of code, that if no 'record' of a previous value was stored and remembered, then

each time we instantiate a new object of the class, we ought to get a ' 1 ' as the output :-

=====================================================================================================

Access to Members (Properties, Methods, etc..) :

As I have stated previously, the mixing of STATIC and NON-STATIC features in one class may be very useful,

BUT, it is more difficult to understand initially, certainly for new guys.

Let us get straight one aspect first, and that is how to code our solutions so that we use both types from one of

these 'mixed' classes - either system or our own.

Any static member is accessed without instantiation and by using the "Namespace.Class.Member" notation as

shown below :-

=========================================================================================================================================

In the lines numbered 58, 61, 64, 68/69 we are using the syntax of Class.Member and are dropping the namespace

as we are within that namespace, so it is superfluous.

The above example shows all three types of access to static members, Method, Property and Field.

However, we do have a slight complication which is shown in lines 68 & 69. Here we have two acceptable ways of

finding the character value of the data item - new guys may finf that on line 69 hardest to get to grips with.

I like the braces around the field data item, as I know this is an object and also one made in our Vulcan code - therefore

it may be used by us and the colon used for any further stuff we require.

Note that trying to use the 'point' notation for the 'ToString()' does not work, as shown below :-

====================================================================================================

I suggest you don't worry about this, but make a few small samples and test them for yourself. It will

all start to get a bit easier then, and you will soon get the hang of it - this is what happened to me !

Non-Static Access :

This is simpler, as it is really just the same as all over classes which can be instantiated.

Any confusion may arise when we mix the 'modes' of use - however, don't let the fact that instantiation has occurred

affect your judgement. Even if you have an instantiated object, to get to the STATIC members of that class you

keep to the 'Namespace.Class.Member' notation as seen in the working example in the image below :-

========================================================================

In the above code we have an instantiated object 'oMD', of class 'MixedDetails', but this does not change the way

we have to code line 42. This would work even if no 'oMD' existed at all.

A system example of this type of mixed mode class/structure is 'DateTime'. We should be familiar by now with

the very useful static property of 'DateTime.Now', and yet we instantiate other DateTime objects quite happily elsewhere

in our own code.

Just remember that we are effectively dealing with Functions (of old) and as such a function related to the class in focus

is simply placed there for our convenience. Obviously a 'function' to give us the date of 'Today' needs to be with other

'DateTime' stuff.

On the other hand, the System.Math class is completely just a bunch of single and separate 'functions', and nothing else.

Actually, after a short while, all of this 'stuff' on STATIC 'things' becomes quite straightforward and also VERY useful to us

developers and coder guys.

*** Note *** I still need to find out if we can make a STATIC filed which is read only - like 'PI' in Math, and 'MinValue' in DateTime.

>>> correction to above, see the relevant part of the previous subsection - now filled out with an example <<<

PHH - Friday 13th. April 2012.


Globally available data objects

my personal comments - lazy way to handle objects and scoping

when don't know how to pass one form to a child - whole object etc.

old programming style - declare at start all variables to be used - or not - as the case maybe.

however, we do have need for some widely available data - stuff to do with user, company and logons

as well as maybe printing, and certainly data source access.

Global use of anything needs to be well thought out and carefully used - simple rule ? Keep to a minimum

Often in the past programmers grab for a global when they were in trouble, and to make a quick fix to scoping problems.

This is NOT the way to use STATIC elements / members.

selecting a printer ?

PHH - Friday 13th. April 2012.


Chapter 2 - Example, Visual Tree WPF

============================================================================================================================================

=============================================================================================

==========================================================================================================

PHH - Friday 13th. April 2012.


The VT class

======================================================================================================================

PHH - Friday 13th. April 2012.


Using the VT class

PHH - Friday 13th. April 2012.


Possible Enhancements

PHH - Friday 13th. April 2012.


Chapter 3 - ADO.NET use of STATIC classes

PHH - Friday 13th. April 2012.


Example 1, the 'SQL_my_funcs' class

Background to making the Class :

What I am about to say is all to do with my personal experience of coming to SQL for the first time, and at that

same moment starting to use ADO.NET in my Vulcan dotNet code.

I had not used any other way to access data in an SQL database (DB). So my experience of querying the DB was

solely based on my experiences with the Management Studio 'shell'.

The early advice I was given by colleagues was to conform to the 'disconnected model' or whatever the phrase is,

meaning that when existing data was required, or storage of new data needed, a connection to the server be made, and when

the transaction was complete the connection be removed, broken, or we say we disconnect from the server.

So I started to write methods in my business solutions which looked like the following.

Study the code carefully, and try and find the 'parts' to the code 'chunk' :-

============================================================================================================

Yes, the code in the above method is a bit longwinded, even verbose, but it does do the job quite nicely.

Basically we have part one which tries to connect to the server with the 'set' connection string, and also create

a command object.

The second 'part' or section sets the query script, and the third sends this to the SQL Server with the method of

the command class entitled 'ExecuteReader'.

Obviously the return dataset is associated with the waiting DataReader object.

The collapsed section simply takes the first row in the returned set and builds a Customer object from it. I will show

this section expanded in the image below :-

====================================================================================

Finally, after the above code, we then close the connection to the SQL server.

This may be a bit of an eyeful to take in if you are not used to SQL stuff. And I agree with the sentiment

whole heartedly.

This is one of the reasons I was motivated to try and come up with a better solution, and one which was not complicated,

or difficult to use in my new apps.

Let us now look at possibly better way to code a method in which I am trying to query the server and get an returned

data set.

Study the following code thoroughly :-

====================================================================================

This time, as illustrated in the above image, we form a query and then give it to a STATIC method to be sent to the

server. From this method is returned a DataSet, either filled with data or 'empty'.

So the process of forming a query and then processing the results from that, are covered in just a few lines, and the

number of the lines will depend on the complexity of the query.

This seems to be an improvement to me, and so we need to study the STATIC method which was used to do the work.

In the 'chunk' of code below we see that the opening of a connection (and its close) are handled along with the attempted

querying of the database.

I tend to think of this stuff as the 'messy' but necessary side of using SQL. Study the code used below :-

===================================================================================================================

By removing all the 'dirty' and necessary bits from the more business logic type of code, it is easier to see what it is

the we the developer is trying to do.

Hopefully, you will see a lot more of this sort of improvement in the later part of this subsection.

Where are we headed ? :

Well, ideally, I would like to make a ClassLibrary assembly ( a DLL to you and me).

This library I would like to be able to add to my Reference section in the SE (solution explorer).

Then with the suitable '#USING' at the head of my PRG's I will be able to use my STATIC methods, Properties, and

any Fields too.

With a fixed Class in my library, I can then get very familiar with using the members in the same way each time I code a

new solution.

Thinking Further ahead, I would like to make a separate STATIC class which is specific to the business objects I use with

the SQL tables - so for example 'Customer' will have a method or two to access a row, or fuller dataset, as well as writing

a new row, and updating a targeted row.

The plan would be to have a short set of members (methods and properties) for each table to business object relationship

or association.

So we may expect to see something like the following :-

==========================================================================================================================

Unfortunately, at the moment I have this mixed in with the 'SQL_my_funcs' class, so I need to change that ASAP.

More on the new Class later. Since it is related to a whole business solution, the Class ought to be named after the app

itself. So the above methods ought to appear in a Class called 'SmallTrader', since this is what has the Invoice, Customer,

Configuration, and Service Job objects. These are custom made business objects.

At the moment the 'Job' pair (or small set) of methods has not been made. You will see more of all this later, for now, lets

move on to our next sub-topic.

Size of the Class :

At the moment, the 'SQL_my_funcs' class has 22 methods, 7 properties, and 16 fields. However since this is very much

a 'work in progress' class, I expect these numbers to increase.

The size of the class, although not huge by any stretch of the imagination, does make it difficult to present it in these eNotes

and for readers to easily focus on what is being discussed. This is just due to the complexity of dotNet and we will have to both

live with it as well as do our best ;-)

Before we move on, let us see a collapsed section of the class code - that from middle to the end. check this out :-

==============================================================================================================================

Let us now move on to see more on what the class is and how to use it.

About the Class and its Use :

Lets do a quick summary of where we have got to - we need a STATIC class which can be used from anywhere in our

applications, and which is easy to use, as well as being VERY like using SQL scripting with management Studio.

The ADO.NET stuff will be hidden from view after being thoroughly tested.

The resulting class members will then allow us to right the business logic methods in a more succinct and easy to

read manner.

The consequence of this set of requirements is that we must ensure we cover all the possible ADO.NET 'stuff'.

We must not create a class that just solves those issues and problems for the 'Small Trader' app.

Okay then, let us first tackle the issue of a 'config' file on disk, which when read will provide us with the fixed

details for the MS SQL server and the connection, as well as the name of the database too.

My usual style for this situation is to have a 'config' file residing in the same directory (or folder) that the app's

EXE file is in.

Below is a real example of this :-

==============================================================================================

And we can open the file in Notepad to see the contents - the data which makes connection possible.

Look out for the necessary details - see below :-

In the image shown above the XML tags help to indicate just what is what.

So the connection string itself is :-

Data Source=BEST-V64QUAD\SQL_DEV_08_R2;AttachDbFilename=C:\Users\BEST\SmallTrader.mdf;;Integrated Security=True;Connect Timeout=0;

The server is :-

BEST-V64QUAD\SQL_DEV_08_R2

and the database is named :-

C:\Users\BEST\SmallTrader.mdf

We now need to see how the relevant method in our STATIC class reads this file and sets the class static fields and properties.

Here is the code for reading the config file - study carefully :-

===========================================================================================================================

What is happening in the above code ?

It is quite simply really, even if you are unfamiliar with serializing objects to disk / file, you can at least appreciate what

is being done.

The folder (or directory) is found which holds the application file, and since I always place the config file there, we try to

see if it is available.

All being well (usual behaviour) the file is found and the contents read in by a 'reader' then deserialized and cast into a

'ConnectionDetails' object. This is an object of a class I made way back to go along with an interactive 'Connection' screen

which I first made with WinForms.

To get your head into this, lets look together at the coding of the class - study carefully the following image :-

============================================================

For those that are not yet aware, serializing an existing object in memory, to a file on disk, is very easy and takes few lines

of code. Paul Piko first pushed me in this direction years back - thanks again Paul, yet another good tip from down under.

Since I like working with objects, all the time in my modern way of coding (I was not always a convert to "objects everywhere"),

doing things this way has a lot to offer. Not least being able to bind the object to a WPF 'connection' form if I so choose.

When designing and making a class which is serializable we need to make sure that it as a Constructor which takes no

parameters - and 'empty' constructor in simple mans terms.

I will put more on Serializable Classes in the relevant section of these eNotes. See "Section H", Where you also see the

In order to help new guys to Serialization, and using 'config' files on disk, it seems sensible to show the basis of a new STATIC

method I will develop and test more thoroughly, but as it is it serves well to demonstrate the process involved.

See below the image of the code to serialize a 'ConnectioDetails' object to disk file :-

==================================================================================

Now that we have a deeper interest in static classes, we may like to notice that in the above code we have

used two 'system' static methods - these are in line 442 and 445.

Also notice that my old code, which is being re-used from the "BEST company" app, has some lines commented

(457 & 458) because we have been advised to let the garbage collector for the CLR have its head, and NOT to mess

with things for tidying up memory. So lines like this need to be a 'thing of the past'.

Setting up Our STATIC Class :

In passing, you may notice that the value of the application directory could be made a class variable, and its value

determined once, in the Constructor so that the first time any member of the class is accessed it will be set for

future use.

Remember that my new class 'SQL_my_funcs' is still in the making, and I am improving it as I write these notes, so

why don't we take this opportunity to improve things ? Let us change the Constructor to be as shown below :-

========================================================================================

The above code changes mean that as soon as one member of the STATIC class is accessed, the Constructor will

be run that time only (as it usually does) and then the fields with directories and file names will be populated, ready

for use by the other members.

Let us now see how the two methods have been cleaned up and tidied by these changes - see images below :-

=========================================================================================

In the newly cleaned code above you may also notice that I have changed the old code from VIDE days (years ago)

so that the upper case in command words is now all lower case. Quite often I leave stuff just as it is so I know

which lines of code are old and been reused, and which are new - a history trail if you like ;-0))

And now the Object to Disk Method :-

=================================================================================

I think you will agree that it is cleaner and more tidy !

The only other thing to notice and discuss is the 'MyMessage' STATIC member. This is shown

below and allows the messages shown in this fashion to be toggled on or off. This applies to the whole lot

in the application done like this. It is VERY helpful when developing and testing - here it is :-

================================================================================

Taking this Class Further :

Now that we have seen the basic idea of our real and larger STATIC class, I think we need to split the rest of

the eNotes and explanation into subsections, so things are easier to manage for both reader and writer ;-)

I will try and use section heading which reflect the function of the STATIC members.

See you in the first of these subsections, I will call it "Connecting" ...........

PHH - 22nd. August 2011.

PHH - 13th. April 2012


Full working code

//----- SQL_my_funcs.prg -----

//----- created August 2nd. 2011 for VODC in November -----

//----- for "Small Trader" app. - by PHH -----

#using System

#USING System.IO

#using System.Data

#using System.Xml.Serialization

#using System.Data.Common

#using System.Collections.Generic

#using System.Collections.ObjectModel

#using System.Collections

#using System.Windows

//------------------------------------

BEGIN NAMESPACE SmallTrader

STATIC CLASS SQL_my_funcs

#region {.. class variables ..}

static public lConnectionTestedOK as Boolean

static private cFileXML as string

static private cAppDir as string

static private cWork as string

#endregion

#region {.. other variables of the class ..}

//----------------------------------------------

static public oConnDetails AS ConnectionDetails

static private oDFactory AS DbProviderFactory

static private oDConn AS DbConnection

static private cStrConn as string

//----------------------------------------------

#endregion

#region {.. more class variables ..}

static private cSource as string

static private cAttach as string

static private cSecurity as string

static private cTime as string

static private cInst as string

static private cDBfilePath as string

static private lConnected as Boolean

static private lShowMessages as Boolean

#endregion

#region {.. and more ..}

//----------------------------------------------

static private nErrorCode as int

static private cErrorMessage as string

static private cErrorLanguage as string

#endregion

STATIC CONSTRUCTOR()

nErrorCode := 0

cErrorMessage := "No Error"

cErrorLanguage := "1033"

//-----------------------

lConnected := false

lShowMessages := false

//lShowMessages := true

//-------------------------------------------------------

cAppDir := System.IO.Directory.GetCurrentDirectory() +"\"

cWork := System.IO.Directory.GetCurrentDirectory()

cFileXML := cAppDir +"ConnectionDetails.xml"

SQL_my_funcs.ReadSerialXmlFile()

RETURN

//========================= [ Backup Database ] ===========================

static method DoBasicBackup() as Boolean

local lResult := true as Boolean

local oF as DirectoryInfo

oF := Directory.CreateDirectory("C:\BackupSQL")

local cName2Use as string

cName2Use := oF:FullName+"\StaticBasic.bak"

MessageBox.Show(cName2Use,"Will be Used :")

local cScript as string

cScript := "BACKUP DATABASE [" + SQL_my_funcs.DBfilePath + "] "

cScript += "TO DISK = '"+cName2Use+"' "

cScript += "WITH FORMAT, "

cScript += "NAME = 'Full Backup of SmallTrader' "

MessageBox.Show(cScript, "Back-Up :")

try

SQL_my_funcs.KeepOpenSendNonQueryScript(cScript)

MessageBox.Show("Query has been sent .... ","After BU :")

catch oEx as SystemException

MessageBox.Show(oEx:Message, "ERROR :")

end try

local nError as int

nError := SQL_my_funcs.GetErrorCodeFromServer()

if nError = 0

MessageBox.Show("No apparent error","Error ?")

else

MessageBox.Show("Error number :- "+nError:ToString(),"Error ?")

endif

//MessageBox.Show("Just before force disconnect ....","TEST :")

SQL_my_funcs.DoForceDisconnect()

RETURN lResult

//========================= [ End Backup] ===========================

//========================= [ Error Object ] ==============================

static method SaveErrorToSQLtable(oE as MyError) as void

SQL_my_funcs.ChangeErrorRecord(oE, RowChangeModeSQL.Insert)

return

static method Error_FillTypedList(oE as MyError) as List<SQLfieldSpec>

local aSFS as List<SQLfieldSpec>

aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[UnRef]", "@ur", DbType.Int32, oE:UnRef})

aSFS:Add(SQLfieldSpec{"[Occurred]", "@occ", DbType.DateTime, oE:Occurred})

aSFS:Add(SQLfieldSpec{"[Method_Name]", "@mn", DbType.String, oE:MethodName})

aSFS:Add(SQLfieldSpec{"[Object_Class]", "@obj", DbType.String, oE:ObjectClass})

aSFS:Add(SQLfieldSpec{"[Namespace]", "@nsp", DbType.String, oE:Namespace})

aSFS:Add(SQLfieldSpec{"[Operation]", "@op", DbType.String, oE:Operation})

aSFS:Add(SQLfieldSpec{"[Main_Message]", "@mm", DbType.String, oE:MainMessage})

aSFS:Add(SQLfieldSpec{"[Inner_Message]", "@im", DbType.String, oE:InnerMessage})

aSFS:Add(SQLfieldSpec{"[SQL_Error_Text]", "@set", DbType.String, oE:SQLerrorText})

aSFS:Add(SQLfieldSpec{"[SQL_Error_Code]", "@sec", DbType.Int32, oE:SQLerrorCode})

return aSFS

static method ChangeErrorRecord(oE as MyError, eMode as RowChangeModeSQL) as Boolean

local lResult := false as Boolean

if eMode = RowChangeModeSQL.Delete

MessageBox.Show("Wrong mode requested >> DELETE !?","Change MODE :")

return lResult

endif

local cTable := "[MyErrors]" as string //----- change for other Tables -----

//==========================================================================

local aSFS as List<SQLfieldSpec>

aSFS := SQL_my_funcs.Error_FillTypedList(oE)

//--------------------------------------------

//MessageBox.Show("Just about to try the STATIC func!","Testing :")

lResult := SQL_my_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

//MessageBox.Show("Just after MBO - try the STATIC func!"+lResult:ToString(),"Testing :")

return lResult

//========================= [ End Error Object ] ===========================================

//========================= [ Message for Exceptions ] =============================

static method MyInnerMessage(oEX as Exception) as string

local cText := "" as String

if oEX:InnerException <> NULL

cText := oEX:InnerException:Message+CRLF

cText += "Exception Type : "+oEX:InnerException:GetType():Fullname+CRLF

cText += "Source : "+oEX:InnerException:Source+CRLF

cText += oEX:InnerException:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oEX:InnerException:TargetSite:ToString()+CRLF

endif

//MessageBox.Show(cText)

return cText

static method MyMainMessage(oEX as Exception) as string

local cText as String

cText := oEX:Message+CRLF

cText += "Exception Type : "+oEX:GetType():Fullname+CRLF

cText += "Source : "+oEX:Source+CRLF

cText += oEX:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oEX:TargetSite:ToString()+CRLF

//MessageBox.Show(cText)

return cText

//------------------- And Another Overload --------------------

static method MyInnerMessage(oDBX as DbException) as string

local cText := "" as String

if oDBX:InnerException <> NULL

cText := oDBX:InnerException:Message+CRLF

cText += "Exception Type : "+oDBX:InnerException:GetType():Fullname+CRLF

cText += "Source : "+oDBX:InnerException:Source+CRLF

cText += oDBX:InnerException:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oDBX:InnerException:TargetSite:ToString()+CRLF

endif

//MessageBox.Show(cText)

return cText

static method MyMainMessage(oDBX as DbException) as string

local cText as String

cText := oDBX:Message+CRLF

cText += "Exception Type : "+oDBX:GetType():Fullname+CRLF

cText += "Source : "+oDBX:Source+CRLF

cText += oDBX:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oDBX:TargetSite:ToString()+CRLF

//MessageBox.Show(cText)

return cText

//--------------------------------------------------------------

//========================= [ End Exception Messages ] =============================

static method MakeBusinessObjectDetail(aSFS as List<SQLfieldSpec>, cTable as string, eMode as RowChangeModeSQL) as Boolean

local lResult := false as Boolean

local cText, cHeading as string

if eMode = RowChangeModeSQL.Update

local cFieldSymbolPairs := "" as string

SQL_my_funcs.CreateFieldSymbolPairs(cFieldSymbolPairs,aSFS)

cText := "UPDATE "+cTable+" SET "+cFieldSymbolPairs+" WHERE [UnRef] = @ur ;"

cHeading := "Updating :"

endif

if eMode = RowChangeModeSQL.Insert

local cFields := "" as string

local cSymbols := "" as string

SQL_my_funcs.CreateFieldsSymbols(cFields,cSymbols,aSFS)

cText := "INSERT INTO "+cTable+" ("+cFields+") VALUES ("+cSymbols+");"

cHeading := "Inserting :"

endif

//MessageBox.Show(cText,"Script :")

lResult := SQL_my_funcs.MakeBusinessObjectChange(aSFS, cText, cHeading)

return lResult

static method MakeBusinessObjectChange(aSFS as List<SQLfieldSpec>, cText as string, cHeading as string) as Boolean

local lResult := false as Boolean

//====================================================

SQL_my_funcs.DoSetConnectionString()

IF !SQL_my_funcs.DoTryToConnect()

RETURN lResult

ENDIF

//==========================

LOCAL oCommand AS DbCommand

oCommand := SQL_my_funcs.DFactory:CreateCommand()

oCommand:CommandText := cText

oCommand:CommandType := CommandType.Text

oCommand:Connection := SQL_my_funcs.DConn

//----------------------------------------------------

SQL_my_funcs.AllParamsSetAdd(oCommand, aSFS)

TRY //----- execute the insert statement -----

oCommand:ExecuteNonQuery()

lResult := true

CATCH DbEx AS DbException

MessageBox.Show(DbEx:Message, cHeading)

END TRY

SQL_my_funcs.DoForceDisconnect()

return lResult

static method CreateFieldSymbolPairs(cFieldSymbolPairs ref string, aSFS as List<SQLfieldSpec>) as void

local oLTE as IEnumerator

oLTE := aSFS:GetEnumerator()

local oSFS as SQLfieldSpec

local i := 0 as int

do while oLTE:MoveNext()

i +=1

if i > 1

cFieldSymbolPairs += ","

endif

oSFS := (SQLfieldSpec)oLTE:Current

cFieldSymbolPairs += oSFS:FieldName+"="+oSFS:@@Symbol

enddo

return

static method CreateFieldsSymbols(cFields ref string, cSymbols ref string, aSFS as List<SQLfieldSpec>) as void

local i := 0 as int

local oLTE as IEnumerator

oLTE := aSFS:GetEnumerator()

local oSFS as SQLfieldSpec

do while oLTE:MoveNext()

i +=1

if i > 1

cFields += ","

cSymbols += ","

endif

oSFS := (SQLfieldSpec)oLTE:Current

cFields += oSFS:FieldName

cSymbols += oSFS:@@Symbol

enddo

return

static method SetAddParam(oCommand AS DbCommand , oDbT as DbType, cSym as string, oV as object) as void

LOCAL oParam AS DbParameter

oParam := SQL_my_funcs.DFactory:CreateParameter()

oParam:ParameterName := cSym

oParam:DbType := oDbT

oParam:Value := oV

oCommand:Parameters:Add(oParam)

return

static method AllParamsSetAdd(oCommand AS DbCommand, aSFS as List<SQLfieldSpec>) as void

local oSFS := SQLfieldSpec{} as SQLfieldSpec

local oLTE := aSFS:GetEnumerator() as IEnumerator

oLTE:Reset()

do while oLTE:MoveNext()

oSFS := (SQLfieldSpec)oLTE:Current

SQL_my_funcs.SetAddParam(oCommand, oSFS:DbT, oSFS:@@Symbol, oSFS:Val)

enddo

return

static method ReadSerialXmlFile() as logic

local serial as XmlSerializer

local oSR as StreamReader

local cText, cHead as string

local lResult as Boolean

lResult := false

lConnectionTestedOK := false

if File.Exists(cFileXML)

cText := "Existing file will be read!"

cHead := "XML file :"

//----- use my STATIC toggled message system -----

MyMessages(cText,cHead)

//----- read object from XML file -----

serial := XmlSerializer{typeof(ConnectionDetails)}

oSR := StreamReader{cFileXML}

oConnDetails := (ConnectionDetails)serial:Deserialize(oSR)

oSR:Close()

//-------------------------------------

lResult := true

lConnectionTestedOK := true

//----- use my STATIC toggled message system -----

MyMessages(oConnDetails:ToString(),cHead)

else

cHead := "XML file :"

cText := "File can't be found !?"+CRLF

cText += "Go to Connection and create a string."+CRLF

MessageBox.Show(cText,cHead)

endif

return lResult

static method SerializationToFile() as void

local cText, cHead as string

if oConnDetails = NULL_object

cText := "NULL object ! - ConnectionDetails ..."

cHead := "My Error :"

//----- use my STATIC toggled message system -----

MyMessages(cText,cHead)

return

endif

local serial as XmlSerializer

local oSW as StreamWriter

if File.Exists(cFileXML)

cText := "File exists already!"

cHead := "XML file :"

MessageBox.Show(cText,cHead)

endif

//----- save object to XML file -----

serial := XmlSerializer{typeof(ConnectionDetails)}

oSW := StreamWriter{cFileXML}

serial:Serialize(oSW, oConnDetails)

oSW:Close()

return

static method SerializeOthersToDisk() as void

/*local cFile as string

cFile := "C:\Temp\customers.xml"

local oSW as StreamWriter

oSW := StreamWriter{cFile}

//----- Save object to XML file -----

local serial as XmlSerializer

local aTypes as Type[]

aTypes := Type[]{1}

aTypes[1] := typeof(Customer)

serial := XmlSerializer{typeof(ArrayList),aTypes}

serial:Serialize(oSW,self:alCustomers)

oSW:Close()*/

return

static method MyMessages(cB as string, cH as string) as void

if ShowMessages

MessageBox.Show(cB,cH)

endif

return

//======================== [ basic ADO.net & settings ] ==============================================

static METHOD DoSetConnectionString() AS VOID

IF String.IsNullOrEmpty(oConnDetails:cProvider)

MessageBox.Show("Provider string NOT available !","WARNING")

RETURN

ENDIF

//====================================================================

IF oConnDetails:cUserName:IndexOf("<") > -1 .OR. oConnDetails:cUserName:IndexOf(">") > -1

oConnDetails:cUserName := ""

ENDIF

IF oConnDetails:cPassWord:IndexOf("<") > -1 .OR. oConnDetails:cPassWord:IndexOf(">") > -1

oConnDetails:cPassWord := ""

ENDIF

//----- get the factory for the provider we require -----

oDFactory := DbProviderFactories.GetFactory("System.Data."+oConnDetails:cProvider)

//----- create a connection STRING ------

oDConn := oDFactory:CreateConnection()

//====================================================

cStrConn := ""

cSource := "Data Source="+oConnDetails:cServerName+";"

cAttach := "AttachDbFilename="+oConnDetails:cFilePath+";;"

//--------------------------------------------------------------

cDBfilePath := oConnDetails:cFilePath //----- for backup ! -----

//--------------------------------------------------------------

cSecurity := "Integrated Security="+oConnDetails:cSecurity+";"

cTime := "Connect Timeout="+oConnDetails:nTimeOut:ToString()+";"

//----- Willie said this would leave it always open if ZERO is supplied! -----

cInst := ""

cStrConn := cSource + cAttach + cSecurity + cTime +cInst

oDConn:ConnectionString := cStrConn

oConnDetails:cConnectionString := cStrConn

MyMessages(cStrConn,"My Connecting String :")

RETURN

static METHOD DoTryToConnect() AS LOGIC

LOCAL lResult AS Boolean

lResult := FALSE

lConnected := false

local cText := "" as string

TRY

oDConn:Open()

cText := "Vulcan.NET : Small Trader : << Connected! >>"

MyMessages(cText,"Try to Connect :")

lResult := TRUE

lConnected := true

CATCH DbEx AS DbException

MessageBox.Show(DbEx:Message)

END TRY

RETURN lResult

static METHOD DoForceDisconnect() AS VOID

LOCAL cText AS STRING

IF ! oDConn = NULL_OBJECT

oDConn:Close()

cText := oDConn:ConnectionString+" - State = "+ oDConn:State:ToString()

MyMessages(cText,"Disconnecting? :")

lConnected := false

else

cText := "Vulcan.NET : Small Trader : << Disconnected! >>"

MyMessages(cText,"Disconnecting? :")

ENDIF

RETURN

//========================= [ query ADO.net members ] ================================================

static method KeepOpenSendScalarQueryScript(cScript as string) as Object

local oResult as object

oResult := object{}

try

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch oEx as SystemException

MessageBox.Show(oEx:message,"Start of Scalar Query !")

end try

//----- getting a scalar quanity returned -----

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oResult := oDComm:ExecuteScalar()

//----- need to cast in calling code section! -----

CATCH DbEx AS DbException

//SaveAndDisplayErrorDetails(DbEx,"Keep, Returned Data Object :")

MessageBox.Show(DbEx:Message,"Keep, Returned Data Object :")

END TRY

return oResult

static method KeepOpenSendNonQueryScript(cScript as string) as Boolean

local lResult as Boolean

lResult := false

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch DbEx AS DbException

MessageBox.Show(DbEx:message,"Start of Non Query !")

end try

//----- getting no data returned -----

oDComm:CommandType := CommandType.Text

TRY

LOCAL cStatement AS STRING

//----- use a terminator ; as recommended by Paul Nielsen -----

cStatement := cScript

oDComm:CommandText := cStatement

oDComm:CommandType := CommandType.Text

//----- execute the supplied statement -----

oDComm:ExecuteNonQuery()

lResult := true

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Keep-Non Query :")

/* local cText := "" as string

cText += "Error Code :- "+DbEx:ErrorCode:ToString()+CRLF

if !DbEx:InnerException = NULL_object

cText += "Inner Exception :- "+DbEx:InnerException:ToString()+CRLF

endif

cText += "Source :- "+DbEx:Source:ToString()+CRLF

cText += "Message :- "+DbEx:Message+CRLF

//cText += "Stack Trace :- "+DbEx:StackTrace+CRLF

MessageBox.Show(cText,"DbEx Details :")*/

//MessageBox.Show(DbEx:Message,"Non Query :")

END TRY

return lResult

//------------------------------------------------------------------------------------------------

static method SendNonQueryScript(cScript as string) as Boolean

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

local lResult as Boolean

lResult := false

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch DbEx AS DbException

MessageBox.Show(DbEx:message,"Start of Non Query !")

end try

//----- getting no data returned -----

//oDComm:CommandType := CommandType.Text

TRY

LOCAL cStatement := cScript AS STRING

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cStatement

oDComm:CommandType := CommandType.Text

//----- execute the supplied statement -----

oDComm:ExecuteNonQuery()

nError := 0

lResult := true

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Non Query :")

/* nError := GetErrorCodeFromServer()

nErrorCode := nError

//FindErrorMessage()

cErrorMessage := DbEx:Message

MessageBox.Show(nError:ToString(),"Code ?! :")

MessageBox.Show(DbEx:Message,"Non Query :") */

END TRY

SQL_my_funcs.DoForceDisconnect()

return lResult

static method SendDataSetQueryScript(cScript as string, cTableTitle as string) as DataSet

nErrorCode:= -999

cErrorMessage := ".. reset for use .."

//------------------------------------

local oDS_Result as DataSet

oDS_Result := DataSet{}

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

//----------------------------------

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"Start of DataSet Query !")

end try

//----- getting a data set returned! -----

oDComm:CommandType := CommandType.Text

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

LOCAL oDA AS DbDataAdapter

oDA := oDFactory:CreateDataAdapter()

oDA:SelectCommand := oDComm

oDS_Result := DataSet{}

//----- execute the select statement -----

oDA:Fill(oDS_Result, cTableTitle)

nErrorCode := 0

cErrorMessage := "No error thrown"

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Error DataSet :")

END TRY

SQL_my_funcs.DoForceDisconnect()

return oDS_Result

static method SaveAndDisplayErrorDetails(DbEx AS DbException, cHead as string) as void

cErrorMessage := DbEx:Message //----- CARE with order, must be before next line -----

nErrorCode := GetErrorCodeFromServer()

if !lShowMessages

local cText := "" as string

cText += ErrorMessage+CRLF

cText += ErrorCode:ToString()+CRLF

MessageBox.Show(cText, cHead)

endif

#region {.. test code ..}

//---------------------------------------

//----- make oE from properties and fields -----

//as MyError, eMode as RowChangeModeSQL

/*local eMode := RowChangeModeSQL.Insert as RowChangeModeSQL

local oE := MyError{} as MyError*/

//ChangeErrorRecord(oE, eMode)

#endregion

return

static method SendReaderQueryScript(cScript as string) as DbDataReader

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

//----- using the DataReader approach as an alternative -----

//----- to the 'DataTable' one I like particularly -----

local oDr as DbDataReader

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

//----------------------------------

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"Error DataReader :")

return NULL_OBJECT

end try

//----- getting a data set returned -----

oDComm:CommandType := CommandType.Text

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oDr := oDComm:ExecuteReader()

//----- execute the select statement -----

nError := 0

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Error Datareader :")

/*MessageBox.Show(DbEx:Message,"Error DataReader :")

nError := GetErrorCodeFromServer()

MessageBox.Show(nError:ToString(),"Code ?! :")

nErrorCode := nError*/

//FindErrorMessage()

END TRY

//----- do NOT close the connection - needed Open by DataReader ! -----

return oDr

static method SendScalarQueryScript(cScript as string) as Object

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

local oResult as object

oResult := object{}

try //----- to Open a connection & make a command object -----

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch oEx as SystemException

MessageBox.Show(oEx:message,"Start of Scalar Query !")

end try

//----- now try getting a scalar quanity returned -----

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oResult := oDComm:ExecuteScalar()

//----- need to cast in calling code section! -----

nError := 0

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Returned Data Object :")

END TRY

SQL_my_funcs.DoForceDisconnect()

return oResult

//------------------------------------------------------------------------

static method GetErrorCodeFromServer() as int

local nResult := -999 as int

local cScript as string

cScript := "SELECT @@Error;"

nResult := (int)SQL_my_funcs.KeepOpenSendScalarQueryScript(cScript)

if lShowMessages

if nResult = 0

MessageBox.Show("No apparent error ! ","Error Code :")

else

MessageBox.Show("Returned code is :- "+nResult:ToString(),"Error Code :")

endif

endif

return nResult

//================================================================================================

/*static method FindErrorMessage() as void

local cScript as string

cScript := "SELECT text FROM [master].[sys].[messages] where [message_id] = '"

cScript += ErrorCode:ToString() + "' and [language_id] = '"+cErrorLanguage+"' "

cErrorMessage := (string)SQL_my_funcs.KeepOpenSendScalarQueryScript(cScript)

return*/

//----------------------------------

static access Source() as string

return cSource

//----------------------------------

static access Attach() as string

return cAttach

//----------------------------------

static access StrConn() as string

return cStrConn

//----------------------------------

static access DBfilePath() as string

return cDBfilePath

//----------------------------------

static access DFactory() AS DbProviderFactory

return oDFactory

//----------------------------------

static access DConn AS DbConnection

return oDConn

//----------------------------------

static access ShowMessages() as Boolean

return lShowMessages

static assign ShowMessages(lM as Boolean) as void

lShowMessages := lM

return

//----------------------------------

static access ErrorCode() as int

return nErrorCode

//----------------------------------

static access ErrorMessage() as string

return cErrorMessage

//----------------------------------

END CLASS

END NAMESPACE // SmallTrader

======================================================================================

PHH - 13th. April 2012


Connecting

This is probably the most important part of any SQL related work.

If you can't connect to your data server, and the correct database too, then the rest of the 'stuff' is no use at all.

Obviously, if we connect, we also need to disconnect satisfactorily as well.

The Model :

I think I mentioned someplace earlier in these eNotes that the 'model' I have been using to date, in all my SQL solutions,

is the one where a connection is made with the server ONLY when there is a requirement. By this I mean the application

requires some existing data, or indeed it may wish to change existing data, or add new data.

Once the 'transaction' is completed the connection to the server is closed.

*** TIP *** It is always a good idea, at application launch time, to do a test connection, just to see that things are as

they should be ;0)) So I usually try to connect, show a message, and then force a close - i.e. drop the connection, or

disconnect.

Problem with the Disconnected Approach :

There is one possible 'problem' or issue we may face when applying the 'disconnected model', and that is when it comes to

errors taking place within the server itself.

If for any reason the server throws and error, the error number is available to us (from the server) as long as we send the

correct query immediately after getting an error, and ALSO while the current connection is still intact.

We will deal in greater detail with error codes from the server in the relevant subsection, two or three from here. For now we

will continue assuming that between connecting and disconnecting, we send one query to the server, and get either no reply or

a data object which is a dataset or a single value.

Details of the String Required for Connection :

Before we can open a connection to the server, we need to put together a 'string' value which contains all the necessary

details about which server and which database file.

Usually, this information is know only to the PC user, administrator, and developer. It could be hard coded into the application

code but this is not recommended and is a little naive in my opinion.

Since moving an application to a different PC platform will undoubtedly mean that the connection string details will change, it is

more usual to have a 'config' type file of some description or another, and this is likely to exist outside of the application itself.

In the example shown below we have already read the XML configuration file mentioned, and shown, earlier in the previous subsection.

If you recall the static method 'ReadSerialXmlFile()' is used to populate the class object named 'oConnDetails'. At the point when

we call the 'DoSetConnectionString()' method all the connection details are help in the properties of the 'ConnectionDetails' object.

In the following image, study what is being done, and how the details are accessed from the relevant object :-

=======================================================================================================================

In the above code, please notice that we are just simply getting string data together in the correct order and making it

available for subsequent static method to try to use it for achieving a connection to the server.

Let us now look at the static method to try and gain a connection.

Study the details in the following image, and see how we check to see if we already have an open connection before

we attempt to make a new one :-

===============================================================================================================

Notice that the above code does work, and the message box was generated live - from this very code. The static method

is not very long or complex. And the one to close the connection is even simpler.

In the next image we see the code in the method to force a disconnection - or drop the connection.

Unfortunately, in both of these short methods I have not as yet replaced the three lines to produce the MessageBox

with the one line to use the 'MyMessages' member. I will do this now in my source code so as not to forget ;-)

Study the code below :-

==========================================================================================================

Once again you are seeing live output from the very code we are viewing.

Note that the name of property of the connection object, which is relevant to these operations, is entitled 'State'.

Okay then guys, before we close this subsection, and move on to the next one, we need to see the original form

written to work interactively with the ConnectioDetails class of object - note this was a WinForms creation and done

with VIDE.

Viewing the above image, it should be obvious which controls serialize to file and which deserialize from file. There is

another which just reads the XML file as text for display in the scroll box.

It is not necessary in all apps to have a way to change and test all the connection details - BUT - it is a great help

when setting up apps initially. Although most users should never go near the 'connection' part of SQL the facility

this form / screen offers is excellent for administrators and developers.

I would recommend having such a form - these days a WPF one - and password the access to this form to keep

away "those that know not what they do".

*** TIP *** The easiest way to find the server name is to use Management Studio (MS), open this as shown in the following

image, and all the possible server instances are shown in a drop list. You can also search for more as well.

=================================================================================

I usually like to open MS and open the tables list in the Object Explorer pane - see below for what I did after

first selecting the server from the above drop list :-

=================================================================================

*** TIP *** For reasons I do not understand, I have found in the past that sometimes to get my Vulcan.NET and ADO.NET to

work for the first time (connect etc.) I have to detach the database in focus, close the MS shell, and try again. Don't worry,

it is perfectly possible to have the database attached in MS and also have your app running. I work like this all the time, it is

sometimes the first time that causes a problem.

If you first fail, or have problems, just keep on trying - that is the way to success with SQL 'stuff'.

And finally, before moving on, lets see the WPF version of the Connection form - the one which will be bound to our object.

Don't be fooled by the improved colours ;-0) it does the same basic job of interactively setting the connection details.

As yet this form does not have data binding applied in the usual WPF dotNet 4.0 manner. This copy shown above was

created about 20 months ago and was one of my first few attempts at using WPF for real apps.

I will attempt to improve things by doing them the 'right' way, although this particular screen has not that much to benefit

from data binding - not like many of the other screens in the Small Trader application.

Time to move on, see you in the next section ..........

PHH - 13th. April 2012


Returning data

Okay you guys, are you up for this subsection on getting back data from our SQL database ?

In many ways it is quite straightforward, some would even say easy.

One Thing or Many :

When we send a script to the SQL server with the intension of getting back data, we will have in mind

whether we expect to get a single piece of data, or a bunch of stuff.

An example of a 'scalar' quantity (single thing) is for example a date - say the date of the last order of a

specified customer. We can obviously have single or 'scalar' value returns for any data type which is

supported by the SQL table field types. So we can expect Boolean, Strings, Integer and Double, as well

as a few possibly less well used types as well.

If we expect as a result, of our query statement being sent to the server, that we will get a row or a number

of rows from our data table(s), then we are talking about having a DataReader or a DataSet returned.

What form of data we expect returned determines which STATIC method we use to send our query statement.

Let us start with a scalar example.

Getting One (Scalar) :

In the image shown below we are trying to find the unique reference number of the next customer. Checkout

the full details carefully :-

===============================================================================================

*** Note *** I realise that we can have the settings on the Customer table such that the action of supplying a new

customer automatically increments the primary key etc. - but we are using this application as a learning tool and

so we will do some simple things for ourselves for the moment.

Looking at the above code we see that it is quite short, as well as easy to read.

basically, we are forming a string which is the query and then in one line sending this to the server, getting a result,

which we then cast into the type of value we expect. What is returned from the static method is just an object.

So if that was quite easy, then what does the static method itself look like ? Study the following image :-

=============================================================================================

The above static method may be used to get and return any scalar value, of any type. It is very flexible in its use.

Now lets move on to getting a bunch of data.

Getting Many ('DataReader' and 'DataSet') :

Okay then, lets start with using a 'DataReader' to access the data returned by a query statement.

The first thing we need to realise about a data reader is that we need to keep the connection to the server open until we

have done all our reading of the data items. This is not the case with a DataSet, this seems to be returned 'intact', or as a whole.

So we need to remember to close the connection from our calling code - method.

Let us look at the Small Trader application and see the WPF form which finds and displays the customer objects.

In this form I have made the source for the customer drop list an 'ArrayList' - it works OK, but is not the way I would do it today,

more than 20 months down the line.

Study the image following :-

================================================================================

As you can see, the customers are certainly fetched back from the SQL table okay.

So now to see what the form method looks like which achieves this result - see below :-

=========================================================================================================================

In the code in the above image line 736 is the one accessing the STATIC member which uses the statement to return

the DataReader object. remember the data is still at the server until reading is completed, and then, and only then, can

we close the connection.

On line 771 we force a disconnect, as the 'DataReader' is no longer required.

So now that we see how to call our static member, what does the code look like which gets us the reader ?

Study carefully the image shown below :-

====================================================================================================

As you can see from the above code, it is not that different to the Scalar version, and we will find that it is

also not that different to the 'DataSet' version to come.

The one important thing which is different is line 753, where we MUST leave the connection open.

'DataSet' - my Preferred Approach :

[ in order to get a DataSet we use the DataAdaptoer in ADO.NET ]

Let us now look at the third of the alternatives for getting data returned from the SQL server after sending a

query statement, or script.

This time we will do things in such a way that the whole bunch of data will come back to our app as a 'DataSet'.

Once the data is returned we will have our static method close the connection.

Let us start out with a problem to solve - from our 'Small Trader' application. Let us ask for a whole bunch

of job sheets. Study the image shown below :-

=============================================================================================================================

As you can see in the above code, the task of getting our whole pile of data is quite easy - simple almost.

Okay, we also need to look at our method for creating the query string, which we will do a little later on, but first

we need to do an experiment to prove that the second parameter provided on line 169 is actually the name of the first

table and not that of the DataSet itself - study the following image :-

=============================================================================================================================

The point at which our Small Trader application applies this code, is when in the following WPF form, and when

the DataGrid needs filling.

At the moment the 'Refresh' button makes sure that the grid is up to date, and is there simply because I can't

get the appropriate event to fire / work on the 'DatePicker' control. I will work on this, it is something non-standard.

Note that it is the controls which complicate the logic of creating the statement string, or script.

We need to take into account which set of records we require, those with invoices or those without.

Study the following code carefully, particularly lines 149 and 158, note the Left Outer Join makes sure we

get all of the Job Sheets, even those with no match in the invoices :-

===========================================================================================================

So the above code simply makes a long statement string for either of the selected modes (or groups) and also applies

the restrictions from the start and end date controls too.

Okay then guys, it is time to look into the final part of all this, the STATIC method which actually communicates with

the server.

Look closely at the detail of the following image :-

====================================================================================================================

Yes, it does look familiar, as it is not that different from the others we have seen and studied.

The difference here is that we close the connection before returning the 'DataSet' object to the calling method.

Please also be aware that there are other overloads for the Fill method, and so other and slightly different ways of

using Fill to solve our problems.

For the straightforward SQL coding I have done so far, and for standard types of problem I have tried to solve, these

techniques shown above have been perfectly adequate, so I stick with them for now.

Okay then, time to move on and see how we edit data and store new stuff we collect at our WPF form inputs.

See you in the next subsection ..........

PHH - 13th. April 2012


Making and Changing Table Rows

This subsection is about storing new table row data and also changing existing rows as well.

For those of you who are familiar with SQL scripting, the command words related to these operations

are INSERT and UPDATE.

If we look at the five existing members of the STATIC class we are building - related to sending statements to the server - we

see that we have two which deal with data 'changes'. Note I have yet to deal with DELETE as my usual style of database use

does not involve me deleting records, I usually mark then as inactive so as not to lose the information.

These operations are generally know by the acronym 'CRUD' - for Create, Update and Delete. (These are general terms.)

For much of what I do below in this section I wish to use 'CRU' as I will be just referring to INSERT and UPDATE commands

in the T-SQL script language.

Have a look at the following image to see what we currently have in our STATIC class :-

===============================================================================================================================

In the above code lines please notice the ones marked 'NonQuery', these are on lines 615 and 651.

The 'Non-Query' refers to the fact that the SQL statement sent to the server is not querying for data to match an enquiry,

but more of a way of sending a command statement to the server, and one which does not expect any return at all.

We will more on this non-query aspect a little later on.

Going Further :

I am afraid to have to tell you that these (CRU) are the operations which are generally VERY verbose or longwinded. if

you as a developer / coder don't have a good and well thought out system it is VERY easy to make errors, and ones which are hard

to find later.

It is for these operations that we need most to construct a good 'SQL_my_Funcs' class.

What is the Problem :

Well, funnily enough, there is not a problem as such. If the table structure is small, meaning it has only a few fields, then there

is not too much of a problem of coding our Vulcan in a similar way to what we may write in the Management Studio editor.

Let us look at the following image, one which shows two simple tables and their structure :-

============================================================

In the above image the tables named 'Goods' and 'Services' are shown fully.

It would be quite easy to do 'CRU' on these without any special classes or members.

In fact looking through my code created early in this project I find the following, where the task has been split into

two parts, even though the tables for Goods and Services are simple - study the following carefully :-

===================================================================================================================

By studying the above code you will see that the task seems to be in two parts, the script or statement itself, and

the list of parameters which must be provided - these are 'tag and value' pairs.

Now then, the above code is complex enough for only four (yes 4) fields, but imagine what it gets like when there are many fields.

And I have to admit that most SQL tables can have VERY many fields.

This 'Small Trader' app is just a small demo one really, even though it does a useful task for my friends at the computer repair

store in Newport. And yet if you look at the image shown below you can see just how complex the Invoice table has become :-

===============================================

Yes, plenty of fields, 41 in total I make it, and I am not going to count those in other tables ;-0)

Don't think of tables with many fields as being a result of poor design, it is just quite necessary sometimes (or even quite often)

to have a whole bunch of fields.

So now you see the problem, one of longwinded code on a limited size screen, and parameter definition a long way from the script

line for checking purposes. All a bit of a pain I think.

And so I came up with my alternative approach

PHH - 13th. April 2012


How to do this in MS

In this short section we will see how to manually create an INSERT statement which when sent to the SQL server

will add a new row to the table in focus.

Obviously we must observe the mandatory fields, and provide values for these, otherwise we would get an error from

the server. However, all other fields can be supplied or not. This is NOT the way I approach things with my business

object approach, and I suspect that s true for the Entity Framework approach where MS too deals with business objects.

It makes much more sense to update a row, and create a new one, where all the fields of the row are provided, and these

match on a one-to-one basis with the business object properties. In this case the Customer table has exact matching

fields with the Customer class properties.

Let us see how to make a new row in MS - study the image detail carefully :-

===============================================================================================================================================================================================================================

Notice that I have missed out 3 or 4 of the non-mandatory fields - like Add5, Mobile, and Email.

If we write another query we can see the results of our first statement to do the INSERT.

Checkout the following image :-

=======================================================================================================================

As you can see we have at least a couple of NULL values, simply because we did not specify a value (or the field) in our

previous INSERT statement.

In the simple way I now do my 'Business Object' (BO) approach this would not be the case, I would set default values to all my

properties of a new BO before using it in the business logic. Changes and edit would simply change the values.

So for the moment in my new way of doing INSERT and UPDATE I deal with all fields and no NULL properties.

Changing rather than Creating :

While we are at it, and I have the software tools in front of me, we ought to try at least one direct query in the MS shell to do

an UPDATE to an existing table row.

Let us try and change the very row we have just made, and maybe provide some values for those fields previously let without

any specified value.

Check this out carefully :-

===================================================================================================================================================

And now we can run the same query as before to see if the row (or record) of customer 131313 has changed - see below :-

=======================================================================================================================================================================

In the above image you can see that I have collapsed the row width of unchanged fields / columns, so the results are easier to see.

Notice too that with an UPDATE the mandatory fields do NOT play a part - unless for some strange reason you were trying to make

them NULLs.

If the row has been made previously, all mandatory 'rules' or conditions must have been met, so that the server did not throw an

exception. So we simply update, or change, those fields we wish.

Lets move on to the next subsection ..........

PHH - 13th. April 2012


My Old Way !

I think it is worth showing an example of how I started out saving a business object of my own making

into an SQL table related to this object. Below we will see how I treated an object of the 'Customer' class

in my first real SQL applet called 'BESTcompany'.

Because I was copying the way this was done in the Management Studio 'shell' I have ended up with everything

in one method - not nice ! Below we will see an 'UPDATE' version of my old style coding.

So let us see if I can make this readable in the images shown below :-

=====================================================================================================================

====================================================================================================================================================================================================

As you can see in the images above we have quite a bunch of code lines, and this is just for EIGHT (yes 8) fields !

To my mind the biggest problem is have parameter code with important parameter names, separated by many lines

from the text string which is to be the statement, or query. The chosen parameter name has to appear in both parts,

be exactly correct, and not be a duplicate. This is therefore difficult to achieve with the code arranged as it is above.

Make sure you see the previous subsection which shows the origin of this 'Old' approach, making a direct query from

the Management Studio (MS) shell.

The extra problem to the MS shell is that we have to 'extract' from, or associate with, the properties of our business object,

in this case a 'Customer' object. In MS we supply a fixed value at query time.

See you in the next subsection ...................

PHH - 13th. April 2012


Possible Problems - New Way

Possible Issues or Problems :

errors in the text entries for the SQL statement - see below

========================================================================================================================

======================================================================================================================================================================================

This is one side of the coin for using new tools for SQL like the EF (Entity Framework) and others like LINQ, checks are all done at compile time.

It could be possible for a compiled and tested app having such an error like this somewhere, waiting to blow things

out of the water, and everyone thinks things are OK :((

and here is another example when I have made a first attempt at saving Goods (parts) in this new way :-

===============================================================================================

PHH - 13th. April 2012


Errors at the Server

Having errors take place within the SQL server after we have sent a query statement from our ADO.NET based

methods in our Vulcan code, is an issue we MUST address, and one which can be handled quite nicely - if we

go about it in the right way.

If we use dotNet code 'as is', and not LINQ and Entity Framework technologies, then the strings we send to the

server for it to process, can have errors within them, which only show up when the server tries to parse and then

execute the statement.

The Vulcan compiler can't possibly detect any error in a string value making up the statement script. This means

we can have code which compiles and runs, which may at some later stage throw an error because of a hitherto

untried string which has been constructed at runtime by the dotNet method code.

For this simple reason we MUST use a system which handles errors in the server, and respond to them in an

appropriate manor.

How to Detect Errors :

Well, this little subsection is just Good News !

If we send a short query statement to the server immediately after any previous statement, we can detect if the

previous statement failed, and if it did what the reason was.

The script requesting this MUST be the one immediately after the statement in focus, AND, has to be for the same

connection. So in layman's terms we must keep the connection open, and make sure that no other query statement

is sent after the one in question, and before the script to enquire of the error situation.

First of all let us see what can be got back from the server after an error - below is what we get in the next subsection

if we neglect to include a database name in the script to request a database backup :-

As you can see it is not as yet VERY informative, except that we know we definitely had an error, as zero ( ' 0 ' ) is

what comes back to us when no error at all has taken place previously.

So from this simple output we know we had an error, and that its code is 1038.

Interpreting the Error Code :

Well, this bit is also easy, as Microsoft have provided a 'View' in the master database for us to use.

Study the following image of the Object Explorer in Management Studio :-

================================================================================

In the above image see that the 'System Views' comes within the 'master' under 'System Databases'.

If we look at the following screen shot of what I found on Googling the topic, it suggests that there is a table

which belongs to the system - this maybe true BUT it is the 'view' which is accessible to us, and which we can

use to find the explanation, or error message.

=========================================================================================================================================================================

So from the above image we know we are looking for 'sysmessages', but instead of a table to use, we find

a 'view' - but with a name of 'sys.messages' instead.

In effect the 'view' is a Read Only form of the table!

The image shown below clearly illustrates that we can't find a table which is suitable :-

=============================================================

However, using the correct 'view' - called 'sys.messages' - we can access the messages associated with

the error codes which are returned to us from the server.

Study the following image carefully :-

=============================================================================================================================================================================================

From the above information we can now form a more specific query statement and send it to the server.

Study the short script below :-

======================================================================================================================================

As you can see in the above image, the filter condition in our script means that we now only get eleven rows (11) returned

to us - AND - that ten of these are not in my local language.

So if we wish to have all the field data for the error code 1038, and just in English, we can do the following :-

=======================================================================================================

Equally, Dieter and our friends in Germany code use '1031' as the filter and get the message in German.

Very nice to have it this simple, and I think using 1049 would do Chris OK, it looks like Greek to me ;-0)

Getting the Error Code :

This in itself is easy enough, study the following code :-

==============================================================================================================

But do remember that this MUST be done immediately after the query your are concerned about.

For an example to help you understand, if you sent this script immediately after the Backup error we saw above, we

would get returned to us '1038', and if we then ran the 'GetErrorCodeFromServer()' again we would get a different

result - you guessed it, ZERO, since there was no error when we sent the first statement of "SELECT @@Error;".

After all, this is a query statement like any other, so if it ran without error then we get zero back when we do it

a second time.

Problem - Issue ? :

Okay then, "what is the catch ?" I hear you say.

Up until now we have designed and used our static methods to query the server in a fully 'disconnected' fashion. So

before and after each query - within the static method - we Open and Close a connection.

The consequence of this is that we can't run the get error method after using any query methods, we do not have the

same connection. If we did we would simply get zero each time around.

Top Down Design of what is Required :

Before we get into all the necessary details, let us look at how we may wish to use the facility to automatically pick up

any error code (and message) which may happen during the course of a standard query.

Study the following code carefully :-

============================================================================================

What we have done above is make an incorrect statement string (on line 214) and then send it to the newly changed

static method (entitled 'SendScalarQueryScript') - then print out some stored details.

If we then run the above handful of code lines - from the end of the 'Initialize()' method of the main form - we see the results

of our efforts displayed in the message boxes. We can see some of the changed code in the background, but we will

study the full details soon.

Check this out :-

===========================================================================================================================

And now for the next message box, that which shows us the error code returned from the server.

Note the error code is stored in a static field called 'nError' within the SQL_my_funcs static class.

======================================================================================

And finally, the last of the three message boxes, that which displays the stored error message itself.

=============================================================================================

Okay then guys, we can see the idea - after any query, the error code and message are always

available if required by the developer / coder.

What Needs to be Done in the Static Query Methods ? :

So what do we have to do to change our existing static query methods to bring this about ?

We will use the 'SendScalarQueryScript' method to explore the possibilities, as well as doing

some testing too.

The New Fields :

Before we can do any processing we need a few more data fields - see below :-

=======================================================================

This simple method of storing an error code and related message, is not perfect, but will certainly do us for now.

Let us go on to study in detail how the 'SendScalarQueryScript' method has been changed to make all this work

for us :-

==============================================================================================

In the image above the first thing to notice is that lines 635 and 660 Open and Close a connection to the server.

This means that on other static member call must involve any other connection changing - making and breaking!

Quite simply, this means the 'GetErrorCodeFromServer' static member, as well as the 'FindErrorMessage' static

member must written so as to use an open connection and then leave it open when done - see below :-

==============================================================================================================

And the new error message method :-

==============================================================================================================

From the two above images of the code being successfully used we see that one special (and new) static method

is required - this is entitled 'KeepOpenSendScalarQueryScript' and it does not Open or Close a connection.

Checkout the code below :-

==============================================================================================

Yes, this is a simple or 'primitive' form of the other similar query of the name 'SendScalarQueryScript', and obviously it

must not have any error checking or anything fancy - other than the query itself, and passing back the result.

So Where Now ? :

Well, it would seem that now we have a working 'Auto Error Catcher' form of the Scalar query method, we now need to

make similar changes to the methods for 'Reader', 'DataSet' and 'Non Query'.

This I have attempted, and after making a slight improvement in the code to that I first tried in the 'Scalar' version, I have

also tested out each with an incorrect script to see if things generally worked OK.

See below for my first attempts at testing :-

=============================================================================================

Having had some success with all the first test I changed the script and the type of error I 'forced' into it.

See below for more testing - AND - the results by 'magic' screen capture too :-

===================================================================================================================================================

[ *** note *** Once again the testing was done just before the end of the Initialize() method in the main Window1 form.]

From the above runtime output it would seem that the SQL server uses the same table of error messages when

it provides us with an exception message in the form shown below :-

===========================================================================

In that case we may be better to say the exception message itself, rather than do a lookup of the message table.

Anyway, enough for the moment, now let us see the improved code I used in the new query methods - below is

the one for DataSets - check it out thoroughly :-

==================================================================================================================

Looking at the above image I realise that the green L.H.S. markings more or less indicate where the changes

have been done.

Summary :

To summarise, we can now automatically check for errors after each type of statement query to the server, and the results

of the error check are stored in fields in the static class.

Since we are not making a special effort to check for an error, we need to use the field values from other code when we feel

we may have a problem.

In fact, now that we auto capture any errors, we could decide to save them in a table too. We could do this on an

automatic basis as well.

I will have a think about this and add more here if anything comes of the idea.

Now it is time to move on to the next section ...............

PHH - 13th. April 2012


Alternative Error Handling

So far in the handling of errors taking place within the SQL server, we have tried to take control of

things ourselves by sending the SELECT statement with '@@ERROR' included.

Well, that works okay, and can be done from Management Studio and other places too. After all

it is just standard T-SQL stuff and regular server behaviour.

HOWEVER, we are using dotNet code, and have more sophisticated tools at our disposal. Previously,

you may have seen me use code similar to that shown below :-

=============================================================================================

In the above code you will see that in the TRY / CATCH we use the exception class 'DbException'.

Usually I use 'SystemException' for my TRY / CATCH structures, but there are certain benefits to us

using this other class - which is specially designed for SQL server use.

If we take a look at the SDK documentation for the class - shown below - you see we ought to have

all the details of the server error already at our finger tips, we don't need enquire of the server ourselves.

Study these details carefully :-

===============================================================================================================================

In the image shown above it is the handful of properties which prove to be the most benefit to us trying to

acquire and store the details of any error occurring.

Let us now do a test to see if we can use some of these properties, ones like ErrorCode, InnerException, and Source -

as well as the Message we already use.

====================================================================================================

=========================================================================================================================================================

=====================================================================================================

====================================================================================================================================================

=======================================================================================================================================

Problems - numeric code returned from this method should be 102, 208, and 207 - but is always -2146232060

==================================================================================================================

PHH - 27th. August 2011.

PHH - 13th. April 2012


Backing up of the Database

To do basic backups of the full database being used in an application is actually quite easy.

In fact sometimes more than one database may be being used, and in that case 'they' all can be

backed up in a similar fashion - all quite straightforward.

First Attempt - Basic backup :

To our existing STATIC class 'SQL_my_funcs' let us add a further static method, and call it 'DoBasicBackup'.

I have coded this new method and run a test too - the image below shows a lot, study it carefully :-

=========================================================================================================================

Although the backing up members of the STATIC class are very much a 'work in progress' project area, the above

code does work nicely and can form a good basis to build upon.

Following is a few small images to show the calling code and the results in a 'WE' pane output :-

====================================================================================

And just to prove that if we get something wrong, and still try to send an incomplete statement to the server, we

have the following images to study - check these out :-

*** notice **** the sting for the database is obviously 'empty' !

The following is what we get back from the server after using the STATIC method called 'GetErrorCodeFromServer()'.

We will need a way to lookup the code (here 1038) to see what the message is.

"Watch this space" ;-)

And finally a couple of error boxes to show that my Try/Catches are working at various levels :-

*** Note *** 'GAC' is not the usual interpretation, it is my acronym and stands for 'Get All Customers'.

So I have an issue somewhere in the following code :-

=============================================================================================================

And yes, it is because I am still converting parts of the original Small Trader app to the new way of using

the 'SQL_my_funcs' static class, in place of the older 'built-in' SQL methods.

And finally the highest of my level catches :-

PHH - 13th. April 2012


Example 2, the 'SQL_SmallTrader' class

Background to making the Class :

Earlier I explained that it was a good idea to keep any methods related to SQL tables, and hence business

objects, from the 'SQL_my_funcs' static class. We ideally need to keep this class a general one, and one which

can support any application needing SQL database access.

To be in-line with this 'aim' or design idea, I have created a new STATIC class called 'SQL_SmallTrader' which

now has pairs of members to deal specifically with tables and objects which are in the Small Trader solution.

Let us have a look at what we now have - study the following image carefully :-

=============================================================================================================================

Since previously mentioning this 'pairs' of methods (members) structure, I have added three further pairs for the Service Job sheet,

the Goods (or parts) required for a repair, and the Services carried out during a repair. These can be seen at the end of the above

image of my coding.

If we now look at my cleaned up MS image of the database tables - in the object explorer - we see the details shown

in the image below :-

========================================================

In the above image we can see that we have a table called 'MyErrors' and this does match have a matching pair of

members in the new static class. Why is this?

Well, it is quite simple, the table called 'MyErrors' will be a general table, and one present in all my solutions to SQL

application problems. The idea is that it will hold errors generated in the server itself, as opposed to application

dotNet exceptions.

The consequence of this is that the similar pair of members required for INSERT and UPDATE need to be in the

generally available 'SQL_my_funcs' class - see below :-

=================================================================================================================

Although I have decided to place the error members within the class 'SQL_my_funcs' the handling of the server errors

is still a 'work in progress' task, so thing will change and improve before I document anything in these eNotes.

In the code above you may also notice a few general members related to displaying screen messages from exception

objects - I have included an overload to provide the inner message, an idea first put to me in a news group posting

from Johan in SA. (Thanks.)

Going Further :

Now that we have firmly in our mind that a complex set of members is required to carry out any INSERT and UPDATE

procedure from our business objects in RAM, we can look at what is different about the DELETE side of the general

phrase 'CRUD' - create, update and delete.

Foolow the ideas in the next subsection to see how DELETE differs, and is much easier to deal with if we choose to

use in in our projects and solutions.

Putting the 'D' into 'CRU' :

With the acronym 'CRU' we are covering commands in the T-SQL scripting language of 'INSERT' and 'UPDATE' and both

of these feature and involve field data - that is data values for each column in the table.

However, when we come to the DELETE command in T-SQL we are simply identifying the rows or records which need to

be removed. So the statement we have to construct is very similar to a query statement, or script.

Since we are running a selection query in principle, we can use the existing static method(s) to achieve our goals.

To make this more clear let us see this in action in the MS shell.

Study carefully the following sequence of scripting actions :-

===============================================================================================

By using the above query script we see that for some reason we have two entries (rows) for Lee Purkiss.

So we would like to remove one of these rows with 'UnRef' of value 113. Let us try to select the one without

'himself' as the 'KnowBy' value.

Let us start by trying to select both of the rows - see below :-

===========================================================================================

Okay, this is fine, but now we need to identify the empty field under 'KnownBy' - see below :-

===========================================================================================

Having got the SELECT query to find the very row we are after, we can now simply change the content

of the query pane in MS to carry out the removal. Note that I have followed what Paul Nielsen recommends

and test the script with SELECT first, then, and ONLY then, change it to 'DELETE' and re-run it.

Check out the following :-

=================================================================================

Well, the messages tab content would suggest that all went well and that now we should have only one

Lee Purkiss in our table.

Let us try re-running the earlier general query to see what we have - check this out :-

==============================================================================

Yes, checking the above image of the results after removal / deleting, we see the result we were after.

So Where does this Leave Us ? :

If you remember, we started this DELETE section with the purpose of finding out how we could use the existing

member(s) of our STATIC class to achieve the results we are after.

Since we ended up using a relatively short and simple statement, and that we required no data returned from the

server itself, we could in fact do what we did in the MS shell with a :-

SendNonQueryScript(cScript)        //----- where 'cScript' is the tested query from above

so we could code something like :-

cScript := "DELETE FROM [C:\USERS\BEST\SMALLTRADER.MDF].[dbo].[Customer] where UnRef = 113 and [KnownBy] = ''"

and then calling the method as seen above.

If we once again look directly at our data we see we have another duplicate on which we could try out our Vulcan code.

See below for the double entry of '101' :-

===========================================================================

This time we can use the 'Salutation' to distinguish one row from another.

Here is my test in the MS shell :-

===================================================================================================================================================

And when I transfer this to the Initialize() method of my Small Trader application we get this :-

=======================================================================================================================================================

Well, the above image seems to suggest to me that we have had success with our simple test.

Let us now return to the MS shell and re-run the general query as previously :-

====================================================================================

Yep! things look good, and we now have only one Stephen Smith, the 'Mr' row has been removed by the server.

Summary :

Although DELETE puts the 'D' into 'CRUD' we do not need any special member in our STATIC class to deal with the

removal of unwanted rows.

If we construct a query statement and then use the member called 'SendNonQueryScript' we get the job done.

Oh! one word of caution, if you have referential integrity set on the database tables then you may NOT be able to do

some deletions, as their are rows in other tables which depend on some of those you are trying to remove.

At the moment with this development / test app I have left it off and even Primary key values may be duplicated. We

can see how to change things to switch on referential checking etc. later in the eNotes.

Finally - The 'ServiceJob' Members :

Since I first started taliking about the 'pairs' of members related to each table and business object, I have gone and

created those for ServiceJob and the related Goods and Services.

This refreshed in my mind how easy / difficult the task was, to first create and then test a new pair of methods.

Let me not run away from reporting back to you that the task is a little tedious, it has to be said, BUT, it is a one-off

task for each major table - and business object. So well worth doing carefully, and testing thoroughly too.

It may be best to see the results of my labour before I discuss any more details of my efforts and testing.

Study carefully the new code for the 'ServiceJob' pair of members shown below :-

========================================================================================================================

Since there are few changes to be made to the second of these methods - I took and existing one and copies, pasted, and

adapted it - then it is how I went about creating the first method which is of particular interest.

My approach was to have the table open in the Object Explorer (OE) in MS, showing the column / field specifications - see below

for an illustration of this :-

====================================================

To make and code each line of my Vulcan static method, I first clicked on the relevant

column in the OE, simply to help keep track of where I was up to, and so as not to miss out any

columns from the set required.

Then each dotNet coded line was created with the appropriate column name, parameter 'symbol' or tag,

the data type, and then the property of the business object which is to provide the data value at runtime.

In the above image I am pointing at the first column, that named 'UnRef' and so the dotNet code line being

worked on is the following one :-

============================================================================================

Although I have stated what the four parameters represent in the above code line, we need to look in more detail

what actually the 'SQLfieldSpec' class is and does for us. So we understand what happens when we instantiate

with these parameters.

Before we move on to see the 'SQLfieldSpec' class we need to say that the overall process, or procedure, of adding

the code lines for each column, is a matter of taking care, and checking spelling, as well as making sure that the data

type is correct - AND - that each chosen 'tag' for the parameter is unique.

** Reminder ** The approach I am taking with the 'SQL_myFuncs_ and 'SQL_SmallTrader' static classes is that each

business object relates one-to-one with an SQL table. Also that each field is given a value by the appropriate property

of the business object having an acceptable value. The constructor in the business object class makes sure that we

have no NULLs.

Not because I have a 'thing' about NULLs, but rather that for the tasks I do with the static class, it suits me best to have

chosen default values, and not NULLs. Personally, I think that every guy should get into using NULLs correctly, and do

some thorough homework on the topic. If you run away from NULL use it will come back to bite you sometime ;-0)

>> I suggest you look at Section E in these notes, at the relevant Tips & Tricks on NULL. <<

Below is the way I initialize the values of the fields in my 'ServiceJob' class at instantiation, study carefully :-

=================================================================================

There are 27 fields in the MS OE pane for the table, and also 27 class fields up to line 110 where we have

other field data related to the Goods and Services tables.

Lets move on to the 'SQLfieldSpec' class, and its use to us in the application.

The 'SQLfieldSpec' Class :

To support the STATIC class of 'SQL_my_funcs' we need to have a class to hold the four parameter values we

saw earlier in this subsection - while making the 'FillTypeList' for the ServiceJob table and business object.

Below is the collapsed outline of the new class :-

=============================================================================================================

Basically, the point of this class is simply to hold in one place all of the necessary four parameter values

which we will need to create the SQL statement for INSERT and UPDATE.

Having such a class makes it easier for the developer to code correctly and as fast as possible without using

EF (Entity Framework).

Let us expand the parts we have used so far in the images above, firstly the Constructor and Initialize method :-

========================================================================================================

We have used the overload of the constructor which takes all of the four parameters, and so we also need to

look into the relevant overload of the Initialize() method.

As I said earlier, the class is really a 'container' for the four different parameters.

Here are the properties of the class :-

=========================================================

Think about it now, I may have been better advised to have called the 'Symbol' property 'Tag'. But the

purpose would not have changed in doing this, just the semantics and the syntax a little ;-0))

What is done with the set of 'SQLfieldSpec' objects ? :

For us to see and understand the use of this class we need to start the code trail within the 'ServiceJob_FillTypedList()'

member on the STATIC class 'SQL_SmallTrader'.

Let us view the code below :-

========================================================================================================================

Looking at the above image we can clearly see that a strongly typed list is being made.

The 'List' is of 'SQLfieldSpec' objects, and in our current example we should get a list of length 27, one for

each field of the SQL table in focus.

So one 'Job' object (class 'ServiceJob') is made into a single typed list in line 211, with 27 'SQLfieldSpec' items.

All that is left to do is done in line 213, where an SQL statement is both constructed and also sent to the server.

Once again, hindsight is a wonderful thing, and maybe another, and better, name ought to have been chosen. I think

what I was thinking was that a "business object" was being processed to use its detail to make a statement, which could

then be sent to the server.

You need to realise that this method handles any type of business object structured in the fashion described above, and so

we could not use a specific name - just plain old 'Business Object'.

The subsequent method called 'MakeBusinessObjectChange' was actually going to carry out the querying of the server, and

hence bring about 'change'.

Let us first study the static method which creates the SQL string to use in the query. See below :-

========================================================================================================================================================

When we examine the above code we see that in lines 146 and 153 there are a couple of string variables which are being

used to construct the statement - these are generated in the static members called 'CreateFieldSymbolPairs' and

'CreateFieldsSymbols'.

We will look at this aspect later, but for now we need to see that the 'eMode' parameter switches between INSERT and UPDATE

as the style of SQL statement being constructed. We will look at the related enumeration a little later on.

Also in the above code, before a Boolean result is returned, the code of line 157 sends the string statement to the server

for execution within the server itself.

By way of a good test, if I uncomment the line at 156 and run the application we will see just what statements are being

made for us by the method.

In the sample illustrations below I am saving a 'ServiceJob' in the usual way for the Small Trader app.

Study these images carefully, they tell us a lot :-

========================================================================================================================================================

The above statement is for the Service Job business object itself, and as usual shows something a bit of a nightmare

to read, check, code, or script manually. Works nicely in a code method however ;-) I know what I will be using

from now on.

Now, because I chose a Service Job with some parts and service too, we get as many Goods and Services statement

message boxes as there are items in the Goods and Services arrays - see one illustration form each set :-

First for a single 'Goods' object :-

====================================================================================================

And now for a single 'Services' object :-

========================================================================================================

Okay then, we can see that the static method creating the statement seems to be working fine, but how

does the method which sends the SQL query statement look ?

And don't forget, as well as a script statement like those in the message boxes above, we have to create ad provide

a whole bunch of parameter objects for the Command object, so we will need 27 of these added to the array or

collection needed by the Command object.

Firstly, let's check this out below :-

===============================================================================================================================================

Above we see the Vulcan code associates the 'cText' input parameter with the SQL query statement - line 170.

And the static member used in line 175 turns our set of 27 'SQLfieldSpec' object items into a collection of parameter objects

for the Command object.

====================================================================================================================

Parameter Objects - Making and Adding to Collection :

Let us concentrate now on how a single parameter object is created, and then how these are arranged into a collection

for use with the Command object of the ADO.NET querying process.

Let us start with the static method of the 'SQL_my_funcs' class which makes the collection- see below :-

=================================================================================================================

This static method needs to use the static member (method) 'SetAddParam()' for each of the 27 strongly typed list

items for our example using the 'ServiceJob' business object.

Look carefully at the following code :-

===================================================================================================================================

So in the above image line 225 is the point at which each parameter item is added to the growing collection.

Field Symbols and Field Symbol Pairs :

The differing styles of the INSERT and UPDATE statements mean we need some string 'snippets' which are either

field and symbol 'pairs' like those below :-

==================================================================

or a whole set of fields followed by a whole and matching set of symbols, as seen below :-

===========================================================================

First let us see the static method which makes the UPDATE style of fields/symbols :-

==================================================================================================================================

And then the member which does the whole set grouping of fields and symbols for the INSERT style of statement.

Study the following carefully :-

==========================================================================================================================================

This is not complicated, or rocket science - BUT - it is complex, and because it has many parts and can be

just a touch longwinded, it can be hard to know what you are doing - on a bad day !!

However, lets put the finishing touches to a difficult and long - but VERY necessary - section.

The 'Row Change' Enumeration :

You may have noticed in the code we have seen previously, that instead of me using fixed string values as "INSERT" and "UPDATE",

I have used enumerations in their place.

Enumerations offer a lot of benefits including making code better to read, easier to maintain, and checkable by the compiler.

Also of course we have intellisense in the editor as well.

The possible variations are illustrated in the image shown below :-

==========================================

At the moment with VN2 version 207 we have to place the 'Enum' definition outside of any class definition, in the same way at the

moment that we can't have nested classes defined.

This now makes a lot of sense of code like the following (which you saw earlier) :-

========================================================================================================================================================

Well, that is all for now I think - it has been a long journey but I can tell you the rewards are GREAT.

I wish I had had this solution to using SQL in such a business object way some years back, still, better late

than never they say ;-)

See you in the next section ................

PHH - 13th. April 2012


Full working code

//----- SQL_my_funcs.prg -----

//----- created August 2nd. 2011 for VODC in November -----

//----- for "Small Trader" app. - by PHH -----

#using System

#USING System.IO

#using System.Data

#using System.Xml.Serialization

#using System.Data.Common

#using System.Collections.Generic

#using System.Collections.ObjectModel

#using System.Collections

#using System.Windows

//------------------------------------

BEGIN NAMESPACE SmallTrader

STATIC CLASS SQL_my_funcs

#region {.. class variables ..}

static public lConnectionTestedOK as Boolean

static private cFileXML as string

static private cAppDir as string

static private cWork as string

#endregion

#region {.. other variables of the class ..}

//----------------------------------------------

static public oConnDetails AS ConnectionDetails

static private oDFactory AS DbProviderFactory

static private oDConn AS DbConnection

static private cStrConn as string

//----------------------------------------------

#endregion

#region {.. more class variables ..}

static private cSource as string

static private cAttach as string

static private cSecurity as string

static private cTime as string

static private cInst as string

static private cDBfilePath as string

static private lConnected as Boolean

static private lShowMessages as Boolean

#endregion

#region {.. and more ..}

//----------------------------------------------

static private nErrorCode as int

static private cErrorMessage as string

static private cErrorLanguage as string

#endregion

STATIC CONSTRUCTOR()

nErrorCode := 0

cErrorMessage := "No Error"

cErrorLanguage := "1033"

//-----------------------

lConnected := false

lShowMessages := false

//lShowMessages := true

//-------------------------------------------------------

cAppDir := System.IO.Directory.GetCurrentDirectory() +"\"

cWork := System.IO.Directory.GetCurrentDirectory()

cFileXML := cAppDir +"ConnectionDetails.xml"

SQL_my_funcs.ReadSerialXmlFile()

RETURN

//========================= [ Backup Database ] ===========================

static method DoBasicBackup() as Boolean

local lResult := true as Boolean

local oF as DirectoryInfo

oF := Directory.CreateDirectory("C:\BackupSQL")

local cName2Use as string

cName2Use := oF:FullName+"\StaticBasic.bak"

MessageBox.Show(cName2Use,"Will be Used :")

local cScript as string

cScript := "BACKUP DATABASE [" + SQL_my_funcs.DBfilePath + "] "

cScript += "TO DISK = '"+cName2Use+"' "

cScript += "WITH FORMAT, "

cScript += "NAME = 'Full Backup of SmallTrader' "

MessageBox.Show(cScript, "Back-Up :")

try

SQL_my_funcs.KeepOpenSendNonQueryScript(cScript)

MessageBox.Show("Query has been sent .... ","After BU :")

catch oEx as SystemException

MessageBox.Show(oEx:Message, "ERROR :")

end try

local nError as int

nError := SQL_my_funcs.GetErrorCodeFromServer()

if nError = 0

MessageBox.Show("No apparent error","Error ?")

else

MessageBox.Show("Error number :- "+nError:ToString(),"Error ?")

endif

//MessageBox.Show("Just before force disconnect ....","TEST :")

SQL_my_funcs.DoForceDisconnect()

RETURN lResult

//========================= [ End Backup] ===========================

//========================= [ Error Object ] ==============================

static method SaveErrorToSQLtable(oE as MyError) as void

SQL_my_funcs.ChangeErrorRecord(oE, RowChangeModeSQL.Insert)

return

static method Error_FillTypedList(oE as MyError) as List<SQLfieldSpec>

local aSFS as List<SQLfieldSpec>

aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[UnRef]", "@ur", DbType.Int32, oE:UnRef})

aSFS:Add(SQLfieldSpec{"[Occurred]", "@occ", DbType.DateTime, oE:Occurred})

aSFS:Add(SQLfieldSpec{"[Method_Name]", "@mn", DbType.String, oE:MethodName})

aSFS:Add(SQLfieldSpec{"[Object_Class]", "@obj", DbType.String, oE:ObjectClass})

aSFS:Add(SQLfieldSpec{"[Namespace]", "@nsp", DbType.String, oE:Namespace})

aSFS:Add(SQLfieldSpec{"[Operation]", "@op", DbType.String, oE:Operation})

aSFS:Add(SQLfieldSpec{"[Main_Message]", "@mm", DbType.String, oE:MainMessage})

aSFS:Add(SQLfieldSpec{"[Inner_Message]", "@im", DbType.String, oE:InnerMessage})

aSFS:Add(SQLfieldSpec{"[SQL_Error_Text]", "@set", DbType.String, oE:SQLerrorText})

aSFS:Add(SQLfieldSpec{"[SQL_Error_Code]", "@sec", DbType.Int32, oE:SQLerrorCode})

return aSFS

static method ChangeErrorRecord(oE as MyError, eMode as RowChangeModeSQL) as Boolean

local lResult := false as Boolean

if eMode = RowChangeModeSQL.Delete

MessageBox.Show("Wrong mode requested >> DELETE !?","Change MODE :")

return lResult

endif

local cTable := "[MyErrors]" as string //----- change for other Tables -----

//==========================================================================

local aSFS as List<SQLfieldSpec>

aSFS := SQL_my_funcs.Error_FillTypedList(oE)

//--------------------------------------------

//MessageBox.Show("Just about to try the STATIC func!","Testing :")

lResult := SQL_my_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

//MessageBox.Show("Just after MBO - try the STATIC func!"+lResult:ToString(),"Testing :")

return lResult

//========================= [ End Error Object ] ===========================================

//========================= [ Message for Exceptions ] =============================

static method MyInnerMessage(oEX as Exception) as string

local cText := "" as String

if oEX:InnerException <> NULL

cText := oEX:InnerException:Message+CRLF

cText += "Exception Type : "+oEX:InnerException:GetType():Fullname+CRLF

cText += "Source : "+oEX:InnerException:Source+CRLF

cText += oEX:InnerException:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oEX:InnerException:TargetSite:ToString()+CRLF

endif

//MessageBox.Show(cText)

return cText

static method MyMainMessage(oEX as Exception) as string

local cText as String

cText := oEX:Message+CRLF

cText += "Exception Type : "+oEX:GetType():Fullname+CRLF

cText += "Source : "+oEX:Source+CRLF

cText += oEX:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oEX:TargetSite:ToString()+CRLF

//MessageBox.Show(cText)

return cText

//------------------- And Another Overload --------------------

static method MyInnerMessage(oDBX as DbException) as string

local cText := "" as String

if oDBX:InnerException <> NULL

cText := oDBX:InnerException:Message+CRLF

cText += "Exception Type : "+oDBX:InnerException:GetType():Fullname+CRLF

cText += "Source : "+oDBX:InnerException:Source+CRLF

cText += oDBX:InnerException:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oDBX:InnerException:TargetSite:ToString()+CRLF

endif

//MessageBox.Show(cText)

return cText

static method MyMainMessage(oDBX as DbException) as string

local cText as String

cText := oDBX:Message+CRLF

cText += "Exception Type : "+oDBX:GetType():Fullname+CRLF

cText += "Source : "+oDBX:Source+CRLF

cText += oDBX:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oDBX:TargetSite:ToString()+CRLF

//MessageBox.Show(cText)

return cText

//--------------------------------------------------------------

//========================= [ End Exception Messages ] =============================

static method MakeBusinessObjectDetail(aSFS as List<SQLfieldSpec>, cTable as string, eMode as RowChangeModeSQL) as Boolean

local lResult := false as Boolean

local cText, cHeading as string

if eMode = RowChangeModeSQL.Update

local cFieldSymbolPairs := "" as string

SQL_my_funcs.CreateFieldSymbolPairs(cFieldSymbolPairs,aSFS)

cText := "UPDATE "+cTable+" SET "+cFieldSymbolPairs+" WHERE [UnRef] = @ur ;"

cHeading := "Updating :"

endif

if eMode = RowChangeModeSQL.Insert

local cFields := "" as string

local cSymbols := "" as string

SQL_my_funcs.CreateFieldsSymbols(cFields,cSymbols,aSFS)

cText := "INSERT INTO "+cTable+" ("+cFields+") VALUES ("+cSymbols+");"

cHeading := "Inserting :"

endif

//MessageBox.Show(cText,"Script :")

lResult := SQL_my_funcs.MakeBusinessObjectChange(aSFS, cText, cHeading)

return lResult

static method MakeBusinessObjectChange(aSFS as List<SQLfieldSpec>, cText as string, cHeading as string) as Boolean

local lResult := false as Boolean

//====================================================

SQL_my_funcs.DoSetConnectionString()

IF !SQL_my_funcs.DoTryToConnect()

RETURN lResult

ENDIF

//==========================

LOCAL oCommand AS DbCommand

oCommand := SQL_my_funcs.DFactory:CreateCommand()

oCommand:CommandText := cText

oCommand:CommandType := CommandType.Text

oCommand:Connection := SQL_my_funcs.DConn

//----------------------------------------------------

SQL_my_funcs.AllParamsSetAdd(oCommand, aSFS)

TRY //----- execute the insert statement -----

oCommand:ExecuteNonQuery()

lResult := true

CATCH DbEx AS DbException

MessageBox.Show(DbEx:Message, cHeading)

END TRY

SQL_my_funcs.DoForceDisconnect()

return lResult

static method CreateFieldSymbolPairs(cFieldSymbolPairs ref string, aSFS as List<SQLfieldSpec>) as void

local oLTE as IEnumerator

oLTE := aSFS:GetEnumerator()

local oSFS as SQLfieldSpec

local i := 0 as int

do while oLTE:MoveNext()

i +=1

if i > 1

cFieldSymbolPairs += ","

endif

oSFS := (SQLfieldSpec)oLTE:Current

cFieldSymbolPairs += oSFS:FieldName+"="+oSFS:@@Symbol

enddo

return

static method CreateFieldsSymbols(cFields ref string, cSymbols ref string, aSFS as List<SQLfieldSpec>) as void

local i := 0 as int

local oLTE as IEnumerator

oLTE := aSFS:GetEnumerator()

local oSFS as SQLfieldSpec

do while oLTE:MoveNext()

i +=1

if i > 1

cFields += ","

cSymbols += ","

endif

oSFS := (SQLfieldSpec)oLTE:Current

cFields += oSFS:FieldName

cSymbols += oSFS:@@Symbol

enddo

return

static method SetAddParam(oCommand AS DbCommand , oDbT as DbType, cSym as string, oV as object) as void

LOCAL oParam AS DbParameter

oParam := SQL_my_funcs.DFactory:CreateParameter()

oParam:ParameterName := cSym

oParam:DbType := oDbT

oParam:Value := oV

oCommand:Parameters:Add(oParam)

return

static method AllParamsSetAdd(oCommand AS DbCommand, aSFS as List<SQLfieldSpec>) as void

local oSFS := SQLfieldSpec{} as SQLfieldSpec

local oLTE := aSFS:GetEnumerator() as IEnumerator

oLTE:Reset()

do while oLTE:MoveNext()

oSFS := (SQLfieldSpec)oLTE:Current

SQL_my_funcs.SetAddParam(oCommand, oSFS:DbT, oSFS:@@Symbol, oSFS:Val)

enddo

return

static method ReadSerialXmlFile() as logic

local serial as XmlSerializer

local oSR as StreamReader

local cText, cHead as string

local lResult as Boolean

lResult := false

lConnectionTestedOK := false

if File.Exists(cFileXML)

cText := "Existing file will be read!"

cHead := "XML file :"

//----- use my STATIC toggled message system -----

MyMessages(cText,cHead)

//----- read object from XML file -----

serial := XmlSerializer{typeof(ConnectionDetails)}

oSR := StreamReader{cFileXML}

oConnDetails := (ConnectionDetails)serial:Deserialize(oSR)

oSR:Close()

//-------------------------------------

lResult := true

lConnectionTestedOK := true

//----- use my STATIC toggled message system -----

MyMessages(oConnDetails:ToString(),cHead)

else

cHead := "XML file :"

cText := "File can't be found !?"+CRLF

cText += "Go to Connection and create a string."+CRLF

MessageBox.Show(cText,cHead)

endif

return lResult

static method SerializationToFile() as void

local cText, cHead as string

if oConnDetails = NULL_object

cText := "NULL object ! - ConnectionDetails ..."

cHead := "My Error :"

//----- use my STATIC toggled message system -----

MyMessages(cText,cHead)

return

endif

local serial as XmlSerializer

local oSW as StreamWriter

if File.Exists(cFileXML)

cText := "File exists already!"

cHead := "XML file :"

MessageBox.Show(cText,cHead)

endif

//----- save object to XML file -----

serial := XmlSerializer{typeof(ConnectionDetails)}

oSW := StreamWriter{cFileXML}

serial:Serialize(oSW, oConnDetails)

oSW:Close()

return

static method SerializeOthersToDisk() as void

/*local cFile as string

cFile := "C:\Temp\customers.xml"

local oSW as StreamWriter

oSW := StreamWriter{cFile}

//----- Save object to XML file -----

local serial as XmlSerializer

local aTypes as Type[]

aTypes := Type[]{1}

aTypes[1] := typeof(Customer)

serial := XmlSerializer{typeof(ArrayList),aTypes}

serial:Serialize(oSW,self:alCustomers)

oSW:Close()*/

return

static method MyMessages(cB as string, cH as string) as void

if ShowMessages

MessageBox.Show(cB,cH)

endif

return

//======================== [ basic ADO.net & settings ] ==============================================

static METHOD DoSetConnectionString() AS VOID

IF String.IsNullOrEmpty(oConnDetails:cProvider)

MessageBox.Show("Provider string NOT available !","WARNING")

RETURN

ENDIF

//====================================================================

IF oConnDetails:cUserName:IndexOf("<") > -1 .OR. oConnDetails:cUserName:IndexOf(">") > -1

oConnDetails:cUserName := ""

ENDIF

IF oConnDetails:cPassWord:IndexOf("<") > -1 .OR. oConnDetails:cPassWord:IndexOf(">") > -1

oConnDetails:cPassWord := ""

ENDIF

//----- get the factory for the provider we require -----

oDFactory := DbProviderFactories.GetFactory("System.Data."+oConnDetails:cProvider)

//----- create a connection STRING ------

oDConn := oDFactory:CreateConnection()

//====================================================

cStrConn := ""

cSource := "Data Source="+oConnDetails:cServerName+";"

cAttach := "AttachDbFilename="+oConnDetails:cFilePath+";;"

//--------------------------------------------------------------

cDBfilePath := oConnDetails:cFilePath //----- for backup ! -----

//--------------------------------------------------------------

cSecurity := "Integrated Security="+oConnDetails:cSecurity+";"

cTime := "Connect Timeout="+oConnDetails:nTimeOut:ToString()+";"

//----- Willie said this would leave it always open if ZERO is supplied! -----

cInst := ""

cStrConn := cSource + cAttach + cSecurity + cTime +cInst

oDConn:ConnectionString := cStrConn

oConnDetails:cConnectionString := cStrConn

MyMessages(cStrConn,"My Connecting String :")

RETURN

static METHOD DoTryToConnect() AS LOGIC

LOCAL lResult AS Boolean

lResult := FALSE

lConnected := false

local cText := "" as string

TRY

oDConn:Open()

cText := "Vulcan.NET : Small Trader : << Connected! >>"

MyMessages(cText,"Try to Connect :")

lResult := TRUE

lConnected := true

CATCH DbEx AS DbException

MessageBox.Show(DbEx:Message)

END TRY

RETURN lResult

static METHOD DoForceDisconnect() AS VOID

LOCAL cText AS STRING

IF ! oDConn = NULL_OBJECT

oDConn:Close()

cText := oDConn:ConnectionString+" - State = "+ oDConn:State:ToString()

MyMessages(cText,"Disconnecting? :")

lConnected := false

else

cText := "Vulcan.NET : Small Trader : << Disconnected! >>"

MyMessages(cText,"Disconnecting? :")

ENDIF

RETURN

//========================= [ query ADO.net members ] ================================================

static method KeepOpenSendScalarQueryScript(cScript as string) as Object

local oResult as object

oResult := object{}

try

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch oEx as SystemException

MessageBox.Show(oEx:message,"Start of Scalar Query !")

end try

//----- getting a scalar quanity returned -----

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oResult := oDComm:ExecuteScalar()

//----- need to cast in calling code section! -----

CATCH DbEx AS DbException

//SaveAndDisplayErrorDetails(DbEx,"Keep, Returned Data Object :")

MessageBox.Show(DbEx:Message,"Keep, Returned Data Object :")

END TRY

return oResult

static method KeepOpenSendNonQueryScript(cScript as string) as Boolean

local lResult as Boolean

lResult := false

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch DbEx AS DbException

MessageBox.Show(DbEx:message,"Start of Non Query !")

end try

//----- getting no data returned -----

oDComm:CommandType := CommandType.Text

TRY

LOCAL cStatement AS STRING

//----- use a terminator ; as recommended by Paul Nielsen -----

cStatement := cScript

oDComm:CommandText := cStatement

oDComm:CommandType := CommandType.Text

//----- execute the supplied statement -----

oDComm:ExecuteNonQuery()

lResult := true

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Keep-Non Query :")

/* local cText := "" as string

cText += "Error Code :- "+DbEx:ErrorCode:ToString()+CRLF

if !DbEx:InnerException = NULL_object

cText += "Inner Exception :- "+DbEx:InnerException:ToString()+CRLF

endif

cText += "Source :- "+DbEx:Source:ToString()+CRLF

cText += "Message :- "+DbEx:Message+CRLF

//cText += "Stack Trace :- "+DbEx:StackTrace+CRLF

MessageBox.Show(cText,"DbEx Details :")*/

//MessageBox.Show(DbEx:Message,"Non Query :")

END TRY

return lResult

//------------------------------------------------------------------------------------------------

static method SendNonQueryScript(cScript as string) as Boolean

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

local lResult as Boolean

lResult := false

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch DbEx AS DbException

MessageBox.Show(DbEx:message,"Start of Non Query !")

end try

//----- getting no data returned -----

//oDComm:CommandType := CommandType.Text

TRY

LOCAL cStatement := cScript AS STRING

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cStatement

oDComm:CommandType := CommandType.Text

//----- execute the supplied statement -----

oDComm:ExecuteNonQuery()

nError := 0

lResult := true

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Non Query :")

/* nError := GetErrorCodeFromServer()

nErrorCode := nError

//FindErrorMessage()

cErrorMessage := DbEx:Message

MessageBox.Show(nError:ToString(),"Code ?! :")

MessageBox.Show(DbEx:Message,"Non Query :") */

END TRY

SQL_my_funcs.DoForceDisconnect()

return lResult

static method SendDataSetQueryScript(cScript as string, cTableTitle as string) as DataSet

nErrorCode:= -999

cErrorMessage := ".. reset for use .."

//------------------------------------

local oDS_Result as DataSet

oDS_Result := DataSet{}

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

//----------------------------------

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"Start of DataSet Query !")

end try

//----- getting a data set returned! -----

oDComm:CommandType := CommandType.Text

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

LOCAL oDA AS DbDataAdapter

oDA := oDFactory:CreateDataAdapter()

oDA:SelectCommand := oDComm

oDS_Result := DataSet{}

//----- execute the select statement -----

oDA:Fill(oDS_Result, cTableTitle)

nErrorCode := 0

cErrorMessage := "No error thrown"

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Error DataSet :")

END TRY

SQL_my_funcs.DoForceDisconnect()

return oDS_Result

static method SaveAndDisplayErrorDetails(DbEx AS DbException, cHead as string) as void

cErrorMessage := DbEx:Message //----- CARE with order, must be before next line -----

nErrorCode := GetErrorCodeFromServer()

if !lShowMessages

local cText := "" as string

cText += ErrorMessage+CRLF

cText += ErrorCode:ToString()+CRLF

MessageBox.Show(cText, cHead)

endif

#region {.. test code ..}

//---------------------------------------

//----- make oE from properties and fields -----

//as MyError, eMode as RowChangeModeSQL

/*local eMode := RowChangeModeSQL.Insert as RowChangeModeSQL

local oE := MyError{} as MyError*/

//ChangeErrorRecord(oE, eMode)

#endregion

return

static method SendReaderQueryScript(cScript as string) as DbDataReader

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

//----- using the DataReader approach as an alternative -----

//----- to the 'DataTable' one I like particularly -----

local oDr as DbDataReader

try

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

//----------------------------------

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"Error DataReader :")

return NULL_OBJECT

end try

//----- getting a data set returned -----

oDComm:CommandType := CommandType.Text

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oDr := oDComm:ExecuteReader()

//----- execute the select statement -----

nError := 0

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Error Datareader :")

/*MessageBox.Show(DbEx:Message,"Error DataReader :")

nError := GetErrorCodeFromServer()

MessageBox.Show(nError:ToString(),"Code ?! :")

nErrorCode := nError*/

//FindErrorMessage()

END TRY

//----- do NOT close the connection - needed Open by DataReader ! -----

return oDr

static method SendScalarQueryScript(cScript as string) as Object

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

local oResult as object

oResult := object{}

try //----- to Open a connection & make a command object -----

SQL_my_funcs.DoSetConnectionString()

SQL_my_funcs.DoTryToConnect()

LOCAL oDComm AS DbCommand

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch oEx as SystemException

MessageBox.Show(oEx:message,"Start of Scalar Query !")

end try

//----- now try getting a scalar quanity returned -----

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oResult := oDComm:ExecuteScalar()

//----- need to cast in calling code section! -----

nError := 0

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Returned Data Object :")

END TRY

SQL_my_funcs.DoForceDisconnect()

return oResult

//------------------------------------------------------------------------

static method GetErrorCodeFromServer() as int

local nResult := -999 as int

local cScript as string

cScript := "SELECT @@Error;"

nResult := (int)SQL_my_funcs.KeepOpenSendScalarQueryScript(cScript)

if lShowMessages

if nResult = 0

MessageBox.Show("No apparent error ! ","Error Code :")

else

MessageBox.Show("Returned code is :- "+nResult:ToString(),"Error Code :")

endif

endif

return nResult

//================================================================================================

/*static method FindErrorMessage() as void

local cScript as string

cScript := "SELECT text FROM [master].[sys].[messages] where [message_id] = '"

cScript += ErrorCode:ToString() + "' and [language_id] = '"+cErrorLanguage+"' "

cErrorMessage := (string)SQL_my_funcs.KeepOpenSendScalarQueryScript(cScript)

return*/

//----------------------------------

static access Source() as string

return cSource

//----------------------------------

static access Attach() as string

return cAttach

//----------------------------------

static access StrConn() as string

return cStrConn

//----------------------------------

static access DBfilePath() as string

return cDBfilePath

//----------------------------------

static access DFactory() AS DbProviderFactory

return oDFactory

//----------------------------------

static access DConn AS DbConnection

return oDConn

//----------------------------------

static access ShowMessages() as Boolean

return lShowMessages

static assign ShowMessages(lM as Boolean) as void

lShowMessages := lM

return

//----------------------------------

static access ErrorCode() as int

return nErrorCode

//----------------------------------

static access ErrorMessage() as string

return cErrorMessage

//----------------------------------

END CLASS

END NAMESPACE // SmallTrader

============================================================================================

PHH - 13th. April 2012


Layer 5 - creating within a new app

It has already become apparent that it is 'Layer 5' of our overall scheme of things which will need

most of the work and effort.

Every time we start a new application we will need to construct a fresh 'Layer 5'.

The 'STATIC class' which makes up this layer reflects the business objects in use. Remember, the BOs

also have a close relationship to the data tables in the SQL database.

Structure with the 'STATIC class' :

The whole of Layer5 for an app with contain a group of methods which are specific to each and every Business

Object (BO) used in the application.

So for our Small Trader app we have a class called 'SQL_SmallTrader'. This class will be appropriately named in

any new application.

Within the static class we will see a set of groups of methods - I think it is best to group them such that all the

methods for say 'Customer' are together - see below for an example of the collapsed groupings :-

=================================================================================

If I now expand the 'Customer' group so we see the next 'level' it looks as follows :-

=========================================================================================================================

Within the 'Customer' group of methods I have placed the two important ones to add data to the SQL 'Customer' table,

together at the top, and then bunched up the others in another group.

All of these methods have SQL query statements constructed within them and relating to the 'Customer' table. Also

each of these methods will call on a suitable method from Layer 6 - a general ADO.net related method.

For example, let us see the detail within the 'FindNextCustomerUnRef' method - check this out :-

============================================================================================

Line of code 163 constructs the statement string (in this case it is quite short and simple) and line

numbered 168 calls a method from the next layer, 'Layer 6'.

We will see this relationship between layers 5 and 6 in each of the methods in our BO grouping, except

one - that is the 'FillTypedList' for the BO, as this is actually used within the 'Change_XXXXX_Record' method.

It may look like quite a bit of work, and indeed that will most likely be the case, BUT, it does do the majority

of the work for our application, and hence makes our Business Logic (BL) layer much simpler. All we need do

in the BL layer is call on the 'Layer 5' static members.

Let us move on to see this in action - within our Small Trader application.

Mini Project / Task - Finding Duplicates :

In the topic section we will add some extra functionality to the main menu by creating options to find and display

any duplicate entries in the Customer, ServiceJob, and Invoice SQL tables.

Let us see a good start - the menu and XAML script for it - as yet we have no functionality behind the menu items.

This is what it looks like now :-

====================================================

And the XAML script to do this was quite easy to insert into the existing stuff - see below :-

========================================================================================================

And then after interactively adding the events handler 'stuff' in the XAML designer surface we see this below :-

=========================================================================================================

And the dotNet 'stub' code too has been added by the Designer - see below for this :-

================================================================================================================================================

So before we 'hook' anything up, we now need to go and make some dotNet methods to carry out the necessary

tasks querying the server.

Exploring the Query Script :

Let us now turn to our trusty tool, the Management Studio 'shell'.

Look at a couple of query statements I have developed and got to work for us :-

===============================================================================

As you can see in the above image, we have one query, the second one, which seems to do just as

we require. It finds only the duplicate Customer entries, and returns the reference number of any who

have more that one entry, along with the number of occurrences within the table.

*** Note *** the result of the probable required script is right at the bottom of the output pane.

And correct to what we have previously seen, Customer 103 is duplicated, we have two of Julie.

Now that we have something suitable, and which does the 'set algebra' just as we require, we can try

to massage the script to be more suitable to how we need to construct it when we write our dotNet code

and static method.

Checkout the new script syntax, seen below :-

=======================================================

The SELECT statement is now in a form we can use with our ADO.net static members in 'Layer 6'.

So let us look at an image of success before we examine the working dotNet code in detail :-

====================================================================================================

As you can see, a simple test from within the Initialize() method of the main window gives us a data table

with only one row.

And taking our test and print-out a little further we see the following at runtime :-

================================================================================================================================

This looks very promising, we seem to be finding just what we need.

Let us now see the static method we have used to create our output :-

==============================================================================================================

I simply copied and pasted the script from MS and then tidied the code by adding double quotes etc..

Don't forget, Management Studio is your friend when dealing in all matters SQL server ;-0))

Before we move on to use the same 'set based' algebra to find other duplicates, in the ServiceJob and Invoice table,

we first need to link-up the menu option click to at least a simple MessageBox output display.

Well below is shown a very basic way, but one which needs improving as far as display of information is concerned.

Check this out :-

===============================================================================================

I will leave it to you to add the loop code to cycle through all records, when there are more than just one !

Finally we see the event handler being called - see below :-

=============================================================================================

Lets move on to the duplicate ServiceJobs.

ServiceJobs :

If we once again start in the MS 'shell' and view the table data, we see we have in my test data one duplicate.

ServiceJob '102' has a double entry. Don't get confused with the 'CustomerRef' - that is just unfortunate coincidence ;-0))

Find the duplicate in the image following :-

================================================================

So all we have to do it take the script for Customer, and adjust it ever so slightly to make it work for 'ServiceJob'.

Checkout the details below :-

========================================================

And because we have done all the basic work previously, it has been VERY easy for me to copy, paste, and adapt

the dotNet code and script to do as we need for the ServiceJob duplicates.

I have even added the loop structure to handle displays of more than one dunplicate found.

Checkout the following images, with all their detail :-

=================================================================================

Here is the new static method - only a few small changes from the Customer 'flavour' :-

=======================================================================================================

And finally the method to include in the Windows1 class, note the extra loop structure :-

============================================================================

>>Whoops, forgot to edit line 239 - should read 'ServiceJobs' ! <<

And for the sake of being complete, lets move on to the duplicate invoices.

Invoices :

If we are going to do this - since it is VERY similar to the other two - we need to do something extra.

What if I make the effort to add a few extra duplicates, just so we can test the code more thoroughly ?

Okay then, just spent a few minutes in the MS shell, and the "Edit Top 200 Rows" context menu option.

I think we have some useful test data - see below :-

======================================================

These MS results would suggest that we have three duplicate invoice references, and with 2, 3 and 4 occurrences

of each it would seem.

These results seem to be totally in-line with the recollections I have of actually doing the editing in MS.

Now for some positive results :-

===============================================================================

Yep! that seems to square up with my thoughts on what should be found, and displayed.

Now here is the new static method :-

==============================================================================================================

And also the event handler method for the Window1 class :-

======================================================================

This time I think I have managed to edit all the places 'Invoices' should appear ;-0))

Now let us move on to the next subsection ................

PHH - 13th. April 2012


Layer 6 - a couple of exceptions!

In layer 6 the ADO.net layer, we said we would not generally see query statements being constructed in

the static methods. That is generally true but there are a couple of exceptions.

In layer 5 we see MANY static methods with query statements relating to the BO class, and the BO related

tables in the database - these belong in layer five as they are BO related tasks.

However, there are a couple of statements we can construct which do NOT relate to the BOs at all.

As way of an example lets see the 'DoBasicBackup' static method - check this out :-

================================================================================================

Like a lot of things in SQL Server, we send a query string to get something to happen.

In this example above the task is NOT related to the BOs at all, but the database itself, we are requesting a back-Up.

Another obvious one we have used in these eNotes is the 'GetErrorCodeFromServer' static method.

If we do more and more database related task, the more likely we are to need to add methods to our layer 6 which

have query statements.

However, it is still true that all BO related statements belong in layer 5.

Let us move on, see you in the next subsection ............

PHH - 13th. April 2012


Chapter 4 - 'TestBed' app to support ADO.NET

Even with a STATIC class or two to support SQL Server access from our .NET code, it is still a HUGE

effort to code and run even a small SQL example.

I realised after a few years with SQL and Vulcan.NET (and ADO.NET), that what we needed as a learning

tool was a framework in which we could add and change our own code, while having everything available

to do testing.

So the 'TestBedSQL' application was born.

This allows us to use and checkout all the features of the STATIC layer 6 which I designed to work with

MS SQL Server.

In a simple way it provides many features of the Management Studio shell, and queries can be typed in

and run directly from the main Form.

Take a look at he current state of development of this form - see below :-

The above image shows a mid-portion into which we can enter a query and after pressing the 'Send Data Query'

button, see our returned data in a DataGrid control at the bottom of the Form.

Please be aware that the applet is based on WPF technologies, and so we have some simple XAML and

also some data binding too.

Currently there is a second Form, called 'ConnectLayer6' as a class name, and this displays a Form

looking like the following illustration :-

Yes, I think you can see that this Form (which supports Layer 6) is all about Server connection.

It allows us to search and find MS SQL Server instances, as well as the attached databases, and tables in a

selected database.

*** Note *** the status bar at the bottom where the current 'connection string' is displayed.

Also notice that in each of the two Forms we have a check box which allows the user to turn on/off the

detailed helpful messages to do with what is going on behind the scenes when queries are sent to the server.

Finally, view the main Form once more, and see that the query text is that copied and pasted from the

working query pane of Management Studio - see below :-

In the above image we see that we can save queries we choose to, as well as being able to re-select them,

and even remove then when they are in focus within the drop list.

Notice that there are two 'flavours' of query 'Send' buttons. However, we can simplify this to one, as the

code behind the button does the job for us.

Take a look at the following code, before we change the buttons :-

==================================================================================================

Yes, the 'SELECT' is used to tell if we want data returned. This affects which form of the ADO query we

use. We will see more of this later.

Here is our new-look Form :-

===============================================================

So now when we use the one 'Query' button the code behind decides which form of our STATIC methods are called upon.

==========================================================================================

In the above image, and the one two above that, we see we are calling on the STATIC method :-

'SendNonQueryScript'

or that called :-

'SendDataSetQueryScript'

There are two others we will use from the STATIC Layer 6 class of mine - see below for all four forms :-

========================================================================================================================

In these four methods the return value type reflects the purpose of the member. So the first one simply

returns a logic value as to whether the query looks to have succeeded - or not.

=============================================================================================

The above code handles the ADO.NET sending of a query type which expects no return value. These sort

of queries usually are dealing with objects within the database - like when making or changing a View, Function,

or Stored Procedure.

Another VERY useful 'Non Query' is that to backup the database - which is .NET Vulcan code looks like

the following :-

===========================================================================

Okay then, time to move on I feel.

See you in the next section ............................

PHH - 27th. April 2012.


What does it allow us to do?

Well, the 'TestBedSQL' applet is like an 'Experimental Rig'.

It is code which the user can change and adapt in order to learn the techniques of using ADO.NET for

Vulcan.NET applications accessing MS SQL Server.

However, it really is for doing this using the STATIC class I have started to put together, in order to

make the coding process much more 'object' oriented.

By using STATIC classes to effectively make Layer6 and Layer5 in our GUI / data access model, we

can think much more of the time in business and custom objects.

So for example, we can instantiate a 'Customer' object, add and change property details (usually through

a GUI and data binding in WPF) and then simply say - save my customer object. Obviously, we will

also have ways of saying, update my customer object' as well as 'remove my customer object'.

This is a MUCH better way to design and create our business logic code, as I suspect that more and more

of us are starting to think in objects much more (or all) of the time.

Getting away from 'Management Studio' style querying ! :

This subheading is 'strange' in many respects.

Since I do so much of my client's work on SQL within the Management Studio (MS) shell, as well as the

shell of VS2005 for Reporting Studio (also known as BIDS - Business Intelligence Development Studio), this

statement seems a little od coming from me.

By this I mean I am always telling guys to make sure they can write queries at the lowest level, that where

the script (T-SQL) goes straight to the Server. Try to learn to work without a layer in between - and by that

token ADO.NET is another layer.

Yes, I accept this - BUT - there is a difference in knowing and learning the basic T-SQL script way to get the

best out of SQL, and actually trying to mix and use T-SQL style script in our business logic .NET code.

Illustration of this new style querying :

Okay then, it is easier to show a good example of what I have been talking about, than talk even more.

Study the following image carefully :-

============================================================================================================================

Reading the code above it goes like this - instantiate a new 'Customer' object, assign certain (and appropriate)

values to the properties, then depending on which of the Layer5 STATIC methods is chosen, either Insert, Delete or

Update the SQL table using the 'Customer' object.

This is a lot more acceptable for application building than standard T-SQL scripting !

It means that if we look at the 'Initialize()' method all we need to do to add a new customer to our 'Customer' table

is the following - see below :-

========================================================================================

Notice that in the spirit of this being a 'Test Rig' I have commented the other three lines, so I could re-use them

at a later stage if necessary.

Matching this up with the Table details :

It is now interesting to compare this code above, with the details in the Object Explorer pane of MS.

Check this out below :-

========================================================

As you can see in the above image, the table mirrors the custom object. And this is the approach used

by MCV3 as well as in my STATIC classes.

Try to design and create a table for each business object, and 'sub-objects'. Design your detail in the custom

objects so that multiple object requirements are handled by reference ID and a second custom class, where we

get multiple rows to handle situations where a single column can't cope with an array data type.

It is best to give a further example, if we are to store the vehicles a person (customer) has, we are better to

create a new Table with a name like 'Vehicle' and then record one row for every car, bike etc. the customer has,

using the Customer_ID as the pointer.

Do NOT try and have multiple named fields / columns. That is old fashioned rubbish !

Where are all the 'crunchy' bits ? :

So far so good, but I hear all those guys who are familiar with SQL script saying :-

........ "where is all the detail ? ... the crunchy parts ?"

Well if we are talking about the old MS style code of straight T-SQL and basic ADO.NET stuff, then please see

below to see what we have left behind :-

=================================================================================================================================================================================================

============================================================================

[ ... one other issue with the above method is that the unique 'mapping' variables we need to create are ]

[ a long way apart - the object bit and the SQL query bit. e.g. line 297 and line 184 ... ]

Well, there you have it, long and boring, and easy to get small errors creeping into the code.

This stuff was VERY tedious to create, BUT, crucial to get correct.

So the answer to our question above is simply that the 'crunchy' bits are coded in 'Layer5' which is the

STATIC class which is designed and made for each application.

Layer5 uses the standard Layer6 direct ADO.NET STATIC members, but the Layer5 members are all related

to the business objects of the application itself.

Let us see a collapsed version of the Layer5 class for the 'TestBedSQL' application :-

===========================================================

As you can see from the image above, we have regions related to 'Objects', and so if we take the

opportunity to expand the 'Customer' region we see the following :-

============================================================================================================================================================================

============================================================================================================================================================================

These much more concise and compact members allow us to either, insert, update, or delete a 'Customer' object

from the existing Table in out SQL database.

And, all we need do for each new object is create another pair of such members, when only half of the line details

need changing.

Another benefit :

There is another real benefit to this, as well as being shorter in length,and cleaner to read. That is the structure

of the Table in the O.E. of MS is very similar indeed. And with practise we get to be able to take a SQL table

and code in .NET a suitable member of the "_FilledTypeList()" form, and quite easily too.

The way this Layer5 stuff interfaces with Layer6 is simply in one call, from line 200.

Having prepared our type list of 'SQLfieldSpecs' we send it along with a table name and a mode 'Enum' to Layer6.

Yep ! as simple as that.

Other 'Object' related members :

Layer5, the application and business logic related layer, needs to have some other group of members which are

not to do with making and updating data rows, but finding out data results to queries - either calculated values or

simple 'chunks' of data returned in DataTables.

It is when we design and create these other members that we use the Layer6 STATIC members directly.

Usually, there is at least one call to a Layer6 member. See the following illustration of this :-

=============================================================================================================

The only exception to this rule about calling at least one Layer6 member is when we make a wrapper

method to go around an existing Layer5 member - so it is effectively a 'calculated member'.

Summary :

So to summarise, we can say that we just have to make the effort to create a pair of members in Layer5 for each

business object class we need to use.

The STATIC class for Layer5 will contain the members for each and every custom class - as well as some extra

members to provide more interesting information regarding our BO classes.

Time to move on.

See you in the next subsection .................

PHH - 27th. April 2012.


What is hidden from us by Layer6

We need to discuss somewhere, and at least once, what messy coding the Layer6 members are doing

for us when we wish to change Table row entries from existing custom objects at runtime.

We have seen how the older SQL scripting approach with ADO.NET was messy, in that we had lots

of 'Param' code sections to create, and a long way away from the script text itself, which included the unique

variable 'identifiers'.

Well, this still needs to be done somewhere in order to be able to send the same types of query script to the

SQL Server.

By making STATIC classes Layer6 and Layer5 we have not changed the way in which the Server works, or

the sort of query scripts the Server requires. What we have done is wrap up the messy bits once and for

all in the Layer6 class, which always stays the same from app to app.

Basically we have made our code 'surface' much cleaner and simpler, and this is seen and done in Layer5.

So let us remind ourselves of the 'shape' of our code in Layer5 - the custom SQL layer. This time we will look

at a smaller object and table structure, that for the 'Query' itself.

Yes, we have a table which stores the field data from a 'Query' object we define in our code.

Let us study the 'shape' of our Layer5 code in the image shown below :-

===================================================================================================================

Yep ! This is all we have to code to be able to create and change table rows for the 'Query' object.

At this point we may as well look at the old way we used to code our class of 'Query'.

I say old even though we use the new PROPERTY syntax, simply because there ar 'smarter' and cleaner ways to do this

using the 'Auto' syntax,, as well as a few other tricks too.

We will look at these a little later, first lets study below the existing (and working) 'Query' class :-

============================================================

In the above image I have left one specific property expanded, that of 'Query_ID', just so you can see what

the 'shape' of the code looks like.

And now we can see the more modern way of coding this, one which I personally like a great deal ;-0)

Before we discuss any particular detail, look at the following image to see the new 'shape' of our class code :-

=================================================================

Wow ! That does look much neater - AND - notice there is not even any collapsed regions.

Getting back to the Messy bits ! :

Okay guys, we need to do this next subsection, just so we realise how lucky we are to have a Layer6 STATIC

class to do our 'dirty work' for us.

The code line in our Layer5 which links to the 'messy bits' is seen below - line 67 :-

===========================================================================================================

You may remember that the typed List 'aSFS' was made by us in Layer5, see above a few images to remind yourself.

So then, we now pass this typed List to a member of the STATIC class Layer6 where the communication with the

Server takes place.

We need to get up enough courage to see what happens in the Layer6 member called 'MakeBusinessObjectDetail'.

Study the following image for Vulcan.NET code details :-

=============================================================================================================================

Okay, it looks like pay-back time ;-0) And we haven't even got to the final messy bits either.

As you can see in the above image, we have four sections where a script text string is assembled, depending on the

type of operation required, Insert, Update or delete, as well as a further complication with a Primary Key Identifier.

While we are looking at the general method of changing rows we can ignore the PKI for the moment, that is there

just to make our coding life easier at a later stage.

You can see above, in lines 282, 291, and 300, that when we need to supply lots of field data for a change of type

Insert or Update, we need to call upon the member entitled 'MakeBusinessObjectChange'. This is another method

of the STATIC class of Layer6 called 'SQL_L6_funcs'.

=================================================================================================================

Oh! finally, we see in the above image that the script text string we have composed is eventually sent to the database

engine of the Server.

Or is it ? No, line 327 looks as if we need to send out typed List to yet another member of our STATIC class. Line 327

shows us that the member is a method called 'AllParamsSetAdd'.

What I can say is that we are VERY close to finishing the long trail required for us to bury our SQL complexity away

from our OOPs coding of our business app.

Let us look now at the final two code methods, one to enumerate through each of our Parameters ('Param' objects), and

the other to actually make the 'Param' object, as well as add it to the 'Param' collection associated with the 'DbCommand'

object. I told you all this ADO.NET stuff was messy in the amount of detail that is required !!

Well, look at the detail here, set out below in the final images :-

=================================================================================================================

And the next small image which actually looks like all those greyed out lines we had in our old styled first attempts.

So we have split out typed List object back into its constituent pieces, and used these to make the 'Param' object.

Check this out below :-

==================================================================================================================================

So there we have it ! A huge amount of necessary - but messy - stuff hidden within Layer6 which we don't need to

code, or even change the code at all, from application to applet that we create and write.

All we need to is create the Layer5 small and 'short' members for each app., and apply them correctly, and all

should work as planned and hoped.

I think you will agree that the following image - one we saw at the top of the section - is a GREAT trade-off in the process

of us making our own VS solutions - see below to put a smile back on your face :-

===================================================================================================================

Also keep in mind that most of the second method above hardly needs much change at all - just small changes in

lines 58, 61, and 65, where we are effectively specifying the table name and the data object type.

I am sure that some would like to reduce this even further, with another STATIC member, but for me it is enough for the

moment, and for how I like to work.

Maybe as I get more experience with using my new STATIC classes I will make some small changes. But for now there

is sufficient in this 'work in progress' project for us all to get some good results.

Time to move on.

Let us go to the next subsection where we will see the VERY easy way to design and make a new business object, and

then to make a suitable table to go with it, as well as adding data from Vulcan.NET code.

See you there ..................

PHH - 28th. April 2012.

Addendum :

=========================================

===========================================

The above Enum details will be useful in fully understanding the code throughout the rest of this subsection.

==============================================================================================================


Making and testing - new Object and Table

Although there is nothing to stop us either making a Table first, or designing a Business Object, if we are

doing 'Top Down Design' of our application, we are likely to design and make a new business object

before we come to think of an SQL Table, and/or storing data for it.

So to illustrate, and/or demonstrate, this whole .NET design approach, with Layer5 and Layer6 use, let

us do something quite new, something not in code or script as yet in my demo system here on my development PC.

And once we have designed and coded this we will use the next subsection to add some suitable data too !

Attendee - New Class :

Okay then guys, in the spirit of DevShare 'Olympic' we will create from scratch a new small class called 'Attendee'.

We will provide the class definition such that we include a few different data types, just for good practise.

Let us have :-

Attendee_ID (as Integer)

Surname (a string)

Firstname (a string)

RegDate (as DateTime)

FeePaid (as Double)

IsSpeaker (as Boolean)

DaysAtDSO (as Integer)

Yes, I know, it is always difficult to come up with anything really suitable, and good.

This will have to do !

Making a Start - object class :

Right guys, now that we have a few property names and data types, let us move over to the VS editor and focus our

attention on a PRG file I have in the solution called 'OtherClasses.prg' - see the following image :-

So I have moved to this file and got it in focus in my VS editor.

There I have added the necessary lines to give us what we are after according to our specification of

the new class as stated above.

I will also use the new and tidy code style for the properties.

So then keeping things as simple as possible we now have the following code in our PRG file - see below :-

===========================================================

And in many ways we not have made the effort to have provided default values as we have done , BUT, it will

not be a bad idea when it comes to providing data to out database in another section.

For now, we need to move over to our Management Studio 'shell' and create a suitable Table using the

facilities provided there.

Creating the Table :

Okay then, now we have a new object Class, we need to move on to make a suitable Table to which we can

eventually 'map' it.

So in the next few images below I will give you a good flavour of what I have just done on my desktop PC.

First of all let us see the state of the Object Explorer pane before we add our new Table - see below :-

=========================================================================================================

And to add a new Table we use the interactive facilities in MS - see the following images :-

============================================

Below we see the MS input grid for the Table structure - I have already added a few fields :-

===========================================================

Remember we need a field, or column, in our Table for each of the properties in our object class.

Before we can enter a Logic, or Boolean, field we need to see an existing example, so below we are

looking at the 'BESTinvoices' application of mine - check this out :-

==============================================

Yes, we seem to need a data type of 'bit' for a logic field.

Looking at another part of this same app. I see I must have use 'Numeric' for my cash fields.

Since there is a 'Money' type available to us in MS SQL Server I think we ought to try that instead.

below is what I used years back for my invoices application :-

====================================

So continuing with my input of Table structure data, names and types etc., we

see the following in the Table Editor tool :-

===========================================================

Please note that we have deselected all the check boxes which allow NULL entries into the fields.

This is probably a good idea for the particular circumstances of us mapping each object property to

a Table field / column.

Although I am not for one minute advocating that you run away from the use of NULLs, it will make our

object data transfer a lot less likely to cause us problems when we need to transfer data.

Once you get experienced at all of this stuff, you can then go on to other sub-topics with this general

'umbrella' of of a main topic. I will highlight other areas where changes may be made later, as we go

through the details.

For now we will just make sure all our object properties have a default value at minimum, and transfer

this data to out Table rows.

Oh!, we had better save our newly structured Table as shown below :-

=============================================================

And remember that in the image shown below we need to provide the correct name :-

============================================================================

Once we have done this successfully we see a tool-tip appear on the Editor tab - see below :-

=====================================================================================================================================

Yes, I know, the name of the database is VERY long - we can live with this however, and do realise that MS

made it so - it was not my making ;-0)

Don't forget to Refresh the Tables list (folder contents) in the O.E. pane, just in case the new table does not show.

Quite often new objects do not show immediately in the O.E. pane, and refreshing is common practise.

Check this out :-

=================================================================================================

And once the Table does appear in the list then expand the entry and then the Columns folder to

see the sort of image shown below :-

=================================================

Make sure you check the structure shown against the object code listing in the PRG. The two

must match exactly, before we attempt to do a matchup with some suitable 'mapping' code.

Let us move on, and also do some testing.

Testing the Object and the Table with data :

Go back to the Object Explorer (OE) pane and with a right click bring up the context menu as seen below :-

========================================================

Select to use the Editor - this time it is a Data Entry editor,like that seen below :-

==============================================================================================================

As we should expect, there seems to be no data at all - so let us add some :-

=======================================================================================================================

If you have used the data editor previously you will know that it works on a 'line-by-line' basis.

When cell values have been added and the cursor moved to another field of the same row, a red and white 'pling' icon shows.

The icon means that the row is 'dirty' meaning it needs to be sent back to the Server to update the row.

Once we have completed all the field values (test sample data only at the moment) we click the mouse in the next (or other) row

and the editor sends an 'Update' back to the Server.

*** Note *** That if we leave any NULLs in place and try to move the cursor off the row in focus we get an error box as shown below :-

=============================================================================================================

So ignoring our failed attempt at row three, let us use the MS 'shell' facility to list out the data - see below :-

[ .. I know we do NOT have 1000 lines of data as yet ! .. ]

========================================================

After selecting the 'Top 1000' option in the context menu we see our two successfully uploaded (or inserted)

data rows.

Check out the following :-

=====================================================================================

Okay guys, we are doing well, but now it is time to move on.

Let us move to the next section in which we do a mapping, as well as test out sending object property

data to the fields in the new Table.

See you in the next section ..............................

PHH - 27th. April 2012.


Saving object data to an existing table

Mapping - Object to Table :

Right then guys, we have successfully made and tested both our new object class and our new

SQL Table.

What we now need to do is write some code which maps the two items. We need to write code which

maps out the relationship between each object data property and an equivalent field within the Table structure.

Note that because this code is application related it will appear in the Layer5 STATIC class.

Only standard ADO.NET member detail is placed in Layer6 STATIC class coding, that stuff which stays the same

from aplication to application.

So we need to find or make, an empty and suitable part of the Layer5 PRG file, into which we can place the new

mapping code.

We are best advised to take an existing (and thoroughly tested) pair of methods called :-

'xxx_FillTypedList' [ .. where 'xxx' stands for the object name ..]

and

'ChangeQueryRecord'

I will copy and paste those for 'Query' as it is not too big, and it has a variety of data types. However, it does not

have a Boolean property and so I will need to go looking in other existing classes for the correct syntax for that type.

First let us copy the two members I said, and I will make a few of the basic code changes required.

This is what we now have, ready to do the serious part of the code addition - see below :-

====================================================================================================================

As you can see above, we have pasted and changed much of the standard code stuff of the older 'Query' class related

mapping code.

We now need to add lines to replace those commented - and place these somewhere between 52 and 59.

I will now give this a try and see what I come up with - watch this space !

====================================================================================================================================================

As you can see above, I am doing well for us, and while I get the opportunity I would like to illustrate to you via the

above image, that the 'DbType' enum has a 'Currency' option.

In fact the 'DbType' enum makes the mapping task so much easier than other alternative methods, since the available

listed types are easy to read, interpret and choose from.

Here is what we seen from the list offered when we need to select a Logic or Boolean :-

===========================================================================================================================================

Okay then, finishing off the mapping code we get the method as seen below - yes, it may well still have some

errors associated with it, or lurking in the mistyped code, but we will find these out when attempt to compile, and

then also attempt to run it.

Oh! somewhere along the code creation process I realised I had a few 'oQ' entries which needed to be 'oA' ones. Did

you spot that mistake ?

Lets see how we go - and remember, this is all part of learning how the 'TestBedSQL' app can help us with our coding.

Checkout the following code and look for any small errors that may have occurred :-

[ .. please note that the mapping variables starting with a ' @ ' have to be unique and made up by us .. ]

[ .. you will see that I try to use a few characters which 'sound like' the property name .. ]

==============================================================================================================

One thing we need to mentally focus upon is that if we have made any errors in our script text entry (as opposed to the .NET

code input) then these will not show at compile time, and only when the script is sent to the SQL Server 'engine', where the

parser tries to make sense of it.

Okay then, before we can test our mapping we need a suitable object, with suitable property data values.

We also need some suitable code to send the business object to the STATIC member to try and query the database.

Look at the following image to see how we can use one of a few Enum values / options, we will start with 'Insert'.

Check this out :-

==============================================================================================================================

And after selecting we get the completed code as shown below :-

=============================================================================================================================

Right then, lets try to compile our coding efforts to see what we get - see below :-

Ahaaarrr ! we have an error and the message points us to the code portion shown beneath :-

====================================================================================================

So I have inserted the missing closing square bracket " ] " without any double quotes. Remember this error message is

sent back from the SQL Server engine as an error code, which then gets interpreted.

Compiling and running the app. we see the following dialog form :-

So we have two issues, the first is easy, it was a spelling mistake - 'FeePaid' now in place of 'FreePaid' in our mapping

code - I have corrected thsi.

However, the other one is more serious and is typical of what will happen to you when you try adding your ouwn B.O.s and Tables.

We have missed out a whole column - see below :-

==============================================================================================================================

Yes, the 'IsSpeaker' logic column is missing from our table structure. See the proof shown below :-

=================================================

So now if we re-launch the Table structure editor in MS we see the following :-

===========================================================

Because we have existing data we need to 'Allow Nulls' for our new field / column, otherwise we can't add it.

Once the new columns exists we can go back and change this after manually adding some values in the existing rows.

Now let us see the updated Table structure, after refreshing the Columns folder in the Object Explorer - see below :-

[ .. CARE - the 'IsSpeaker' setting will first need to be 'null' instead of 'not null', this will be explained .. ]

=============================================

** CARE ** any already open tab displaying table data for the first 1000 rows will now be incorrect for our purposes

as the following image illustrates :-

=============================================================================================

So either add the extra field manually into the query tab, or close it and open a fresh one using the context menu

after right clicking in table name in the O.E. pane.

Here is what I have :-

===========================================================================================================================================================

And as you can see above, the new column is displayed at the R.H.S. of the grid.

Now you may wish to change the 'Allow Nulls' in the editor, after supplying appropriate values to the 'IsSpeaker' column

in the data editor.

I have already done this and that is how Paul and Alwyn got checked for the 'IsSpeaker' field.

Now for the Saving of Data :

Let us write the .NET code to test out our new mapping. Line 54 shown below will do this for us :-

========================================================

I have already got the test data in my Table at this end of things, and so I will now try the 'Update' variety, or 'flavour'.

Check this out :-

=============================================================================================================================

The following image looks promising :-

Yes, the Update works with a unique identifier - BUT note in this demonstration we have not set a Primary Key because I was

trying to keep things as simple as possible for the first time I showed you the general approach.

Lets make some changes to our existing test object and then update it once more.

Let us try our friend Dan Logan - see below :-

===========================================================================================================================

And after compiling and running the code we see the following data, directly from the MS 'shell' options and facilities :-

======================================================================================================

Notice that if we supply an object data set which has an ID which is not in the rows of the Table data we do

not get an error, just no changes made.

Let us change friend Dan to number 101 from 111 - see below :-

The above comes as a result of the following code :-

[ ** Note ** the mode we are trying is 'Delete' ]

============================================================================================================================

But because we have no existing row with an 'Attendee_ID' of 101, nothing happens.

This is NOT an error situation, just zero lines (or rows) are affected.

So in the following image we still see Dan.

======================================================================================================

Enough for now. Hopefully you can see how to save and change data in tables from .NET objects.

Time to move on I feel, see you in the next subsection ................

PHH - 27th. April 2012.


Writing and sending Queries scripts

Okay you guys, this should be a lot easier topic than the last couple of sections ;-0)

If you like this is the 'fun' part.

Look at the image below, as this was done using the 'SQLTestBed' app. itself, straight after finishing the

last subsection - nothing else was done.

Checkout the query typed into the 'New Query' control, and the resulting returned data in the grid - see below :-

In the above image we are writing queries 'on the fly' - or 'off the top of our heads'. We can get things wrong as

well as get them right ! - just like in the query panes of Management Studio itself.

However, once we have tested a new query, and seen the results are as we may expect, we can then easily turn

this into a new member in the Layer5 STATIC class.

In the following image I am try such a query, albeit it simple one - check this out :-

===============================

As you can see in the above image we get a result of '3'.

Okay then, suppose we wish to make a member of our Layer5 STATIC class return the result to this same query.

The following illustration shows how we may do this - checkout the code and in particular line 83 :-

===============================================================================================

You can see that once we have made the script from the tested query text, we send it to the server using a

Scalar based member. The 'scalar' refers to the fact that we expect a single data value.

Now look at the code which may call upon our new STATIC member in Layer5, the application based STATIC class.

========================================================================================================

Okay then guys, this seems easy enough, and the process is as easy for longer and more complex queries as

it is for the simple one shown above.

Another way to look at this ! :

While we are researching this particular issue, we may as well see that we can send the exact same script to the

Server as a query, but with the member which returns a data table.

Let us look at the code of a method I had previous to this latest bout of development and testing, a member

called 'AttendeeCountTable'.

Checkout the following code :-

======================================================================================================

Please notice in the above image that line 85 calls on the 'SendDataSetQueryScript' method, which we usually

use when we expect a table or list of data returned.

However, since a single value is really just a single columned, single row, table, then it works also.

We just need to remember to treat the returned output as a table and not a single data type value.

In the illustration following, we see this member being called :-

============================================================================================================

And the GUI display of the returned data is as shown beneath :-

So there you have it, you can get back a single value in a data table format, but not a table in a single simple data type value.

Half way between these two extremes we get a list of values as a single columned table.

Now time to move on I feel.

See you in the next subsection ...........................

PHH - 27th. April 2012.


Saving, recalling, and removing Queries

So far we have used the 'TestBedSQL' application to explore the coding and scripting side of our

own application creation and code generation.

Now I would like to focus for a while on the 'TestBedSQL' tool itself.

Remember the ted bed tool actually uses the STATIC members in a similar way to the way I am

suggesting you use them for your own SQL applications,and therefore it is worth studying a few of

the features of the tool to see how things may be done.

Since the tool can be most helpful to you as you write and test Queries to support you growing app.,

we are going to look at the code behind how I have provided for saving queries, as well as recalling

them and also removing them too.

When I talk of Save, Recall, and Remove, I am talking of at a level of both the collection behind the

drop list of 'Old Queries' as well as the Table rows stored in the SQL database.

So we need to make sure that our code keeps in step at all times, the collection bound to the ComboBox

control and the data rows help in the SQL Table - table entitled 'Query'.

Below is an image of the Object Explorer pane in Management Studio which shows this table :-

=================================================

And if we use MS to query this table in a simple fashion, we get the following results :-

=======================================================================================================================================================

If we now look at the test-bed tool (TestBedSQL) we see that the drop list labelled 'Old Query' has a long

list of saved queries.

Check out the following illustration :-

Notice in the above image that the item lines displayed in the drop list are quite simple in nature.

It was decided that the date of making / saving, and the nature of the query itself, was important to show,

but that any complex detail was better left to a Tooltip.

If you study the following image you will see that we have our first rudiments (start) of a tooltip support feature

even though we can improve it very easily as shown after this image below :-

Before we improve this with some possible colouration, let us first see what the XAML script is that does this for us.

Please remember that everything that can be done in XAML can be coded in .NET code, but using XAML is so easy

in cases like this, that it would be silly not to use it.

Checkout the following script and in particular line 19 :-

====================================================================================================================================================

Notice that in line 19 we are binding the Tooltip of the StackPanel to the 'Script' property of the 'Query' objects in the collection,

or DataTable, which is bound to the drop list 'ComboBox' control.

Shown be low is the .NET code to do the binding association:-

==================================================================================================

And obviously we need to see how these data rows are retrieved and put into the DataTable - check this out :-

==================================================================================================

The code in the above image is obviously going to be in Layer5 as it relates to this particular app, and one of its business

object related Tables.

Now let us look at some of the issues related to the displaying of the tooltip itself.

In the following image we see a typical 'nested' manner of WPF in which we supply the content of the Tooltip with other WPF

GUI elements.

Once we have our tooltip comprising of 'StackPanel' and nested 'TextBlock' we can then set the appropriate properties to suit

our needs.

Checkout the way to set the colours of the Back and Fore grounds :-

=============================================================================================================================

And now of course we need to see the visual results at runtime.

Checkout this for an improvement, see below :-

Okay, I hear you moaning about my choice of colours. But Hey! that is easily change by yourself, to

your own interesting tastes.

Saving a Query :

Recalling a Query :

Removing / Deleting a Query :

PHH - 27th. April 2012.


Where next to take the 'TestBedSQL' app.

PHH - 27th. April 2012.


Setting and making Connections

This is always the 'tricky' issue. Some would call it the 'thorn in the side' of using SQL.

Over 'three years on' with using SQL quite extensively, and I still have problems and issues with connections.

Yes, I have gotten a lot better, in that I am more successful, most of the time, and I seem to understand a few things

much better than I did - BUT - I feel in no way that I fully understand what is going on, and why :((

However, this is such a pivotal issue that we MUST try to get to grips with it. It is no good at all running away

from it. So be prepared to roll up your sleeves, and get to work.

General Comment :

Many people in web articles and text books, shy away from the full details of this issue. They just don't talk about

it enough (if at all) for my liking.

Probably because it bridges a number of things, some of which we have no control over in our day-to-day coding.

One of these general issues is the access rights for the folder in Windows where you place the database (DB).

Another is if you have renamed the automatic name supplied by Management Studio when it attaches a DB.

There also seems to be some inter-relation between MS and my Vulcan.NET coding - or should I say when I try to

run my SQL apps, usually the first time or two. Sometimes I seem to have to detach and reattach the DB in the MS

shell, before my own dotNet apps with run without connection errors.

I have captured on of the 'not very helpful' error messages we seem to get from time to time. And for me personally

developing this app I get it frequently, depending on my choice of databases.

Check this out before we discuss any more details :-

Notice that the image shown below has the DB we are after as a renamed one.

I have a feeling (thinking way back) that this used to be a LONG named entry in the OE list of databases.

I am reasonably sure that I changed the naming from "C:\Users\BEST\SMALLTRADER.MDF" but we need

to do plenty more testing - for now just look and learn !? :-

=====================================================================

==========================================================

==============================================================================================================

=======================================================================================================================

PHH - 27th. April 2012.


Finding DBs, Tables, and Columns

PHH - 29th. April 2012.


Creating tidy Dependency classes

*** Note *** the following work on the Preprocessor was all the idea and inspiration of Johan Nell, South Africa.

Thanks Johan !

================================================================================

Well any of you guys who have ever looked at my WPF and XAML eNotes in the 'ClickStart' series will have

seen just how long and verbose they can get.

When the new PROPERTY syntax became available in version VN 210 I started to work on a way of improving

the look of the code in our Dependency object classes, where we declare Dependency Properties.

Let us have a Top Down look at what I now have for you, courtesy of Johan Nell, a Vulcan VIP in South Africa.

Now that I have changed the code in the 'Clerk' class within this Test-Bed app. we can see the nice results - see below :-

=============================================================================================================

Yes, to those with some prior knowledge of data binding and WPF this indeed looks both short and clean !

Notice lines 372 through 378, these do not look like standard Vulcan.NET syntax, and indeed they aren't.

However, they will be converted into standard code but by the Pre-Processor rather than the compiler.

How do we do this ? :

Okay then guys, its an obvious enough question, what do the instructions to the Preprocessor look like ?

Well, at the top of the PRG file which holds the 'Clerk' class declaration we see the following #command statement.

Study in particular lines 25 through 38 - see below :-

========================================================================================================

What we have in the image above is a 'masked pattern' I suppose.

The first line (25) declares the format of what will be found in our code in the following Namespace, that of "TestBedSQL".

The ' < >' symbols contain that which needs to be swapped at compile time.

If we study the following illustration carefully, we will see what one line of code becomes when the Preprocessor has

done it job, and the compiler switches in the preprocessed text strings.

[ .. in fact this task is likely to be done before the compiler swings into action, but you get the drift ! .. ]

==========================================================================================

Whoops, sorry, I have not removed the semi colons - lets do that to see what out code is more likely to look like at runtime.

Check this out :-

===============================================================================

That looks better - we only have semi-colons now where there would be ones in the Vulcan code.

If you need to know more about the Preprocessor then apparently it is a feature of Clipper. So have you any

Clipper manuals hanging around ;-0)

PHH - 29th. April 2012.


working code and script

========================================================================================

// partWindow1.prg

#using System

#using System.Collections.Generic

#using System.Linq

#using System.Text

#using System.Windows

#using System.Windows.Controls

#using System.Windows.Data

#using System.Windows.Documents

#using System.Windows.Input

#using System.Windows.Media

#using System.Windows.Media.Imaging

#using System.Windows.Navigation

#using System.Windows.Shapes

//==================================

#using System.Data.Common

//==================================

#USING System.IO

#using System.Data

#using System.Data.SqlTypes

#using System.Collections.ObjectModel

#using Microsoft.SqlServer.Server

#using System.Data.Sql

#using System.Text.RegularExpressions

BEGIN NAMESPACE TestBedSQL

/// <summary>

/// Interaction logic for Window1.xaml

/// </summary>

PARTIAL CLASS Window1 INHERIT Window

METHOD Initialize() AS VOID

SQL_L6_funcs.ShowMessages := TRUE

SELF:DoConnectionForm()

SELF:tblkScalar:Text := SQL_L5_TestBed.QueryCount():ToString()

SELF:tblkQueryScript:Text := SQL_L5_TestBed.Script

/*LOCAL oDT AS DataTable

oDT := SQL_L5_TestBed.QueryCountTable()

MessageBox.Show(oDT:ToString(),"My Table Returned :")

SELF:dgReturn:DataContext := oDT*/

//SELF:TestAttendee()

//LOCAL cNum AS STRING

//cNum := SQL_L5_TestBed.NumberOfAttendees():ToString()

//SQL_L5_TestBed.AttendeeCountTable():ToString()

//MessageBox.Show("Number of current attendees is :- "+cNum, "Attendee Count :")

SQL_L6_funcs.ShowMessages := FALSE

SELF:FillQueryComboBox()

//----- test the Insert/Update & Delete? of Party records -----

//SELF:TestParty()

//SELF:TestQuery()

//SELF:TestClerk()

//SELF:TestCustomer()

SELF:pbConnectionForm:DataContext := SQL_L6_funcs.oSCSB:ConnectionString

/* LOCAL oDT AS DataTable

oDT := SQL_L6_funcs.FindTables("[C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL_DEV_08_R2\MSSQL\DATA\TestBed.mdf]")

SELF:dgReturn:DataContext := oDT*/

//SQL_L6_funcs.DoBasicBackup() //----- works just fine! messages and all -----

RETURN

METHOD DoBackupDatabase() AS VOID

SQL_L6_funcs.DoBasicBackup(SQL_L6_funcs.oSCSB:AttachDBFilename)

RETURN

METHOD DoConnectionForm() AS VOID

SQL_L6_funcs.DisplayConnectForm()

RETURN

METHOD DoChangeMessageDisplaySetting() AS VOID

IF (Boolean)SELF:chkbxDisplayMessages:IsChecked

SQL_L6_funcs.ShowMessages := TRUE

ELSE

SQL_L6_funcs.ShowMessages := FALSE

ENDIF

RETURN

METHOD FillQueryComboBox() AS VOID

SELF:cbxQuery:DataContext := SQL_L5_TestBed.GetAllQueryRows()

LOCAL oDT AS DataTable

oDT := SQL_L5_TestBed.GetAllQueryRows()

LOCAL oOC AS ObservableCollection<DataRow>

oOC := ObservableCollection<DataRow>{}

LOCAL i, nCount AS INT

nCount := oDT:Rows:Count

//MessageBox.Show(nCount:ToString(),"my Count :")

FOR i := 0 UPTO nCount-1

oOC:Add(oDT:Rows[i])

NEXT

RETURN

METHOD TestClerk() AS VOID

LOCAL oK AS Clerk

oK := Clerk{}

oK:Booking_Clerk_ID := 4 //----- ignored on INSERT but needed for UPDATE -----

oK:Surname := "Clarkson33"

oK:Firstname := "Jenny"

oK:password := "Clarkers(*&"

oK:BC_EntryStamp := DateTime.Now

oK:BC_ModifyStamp := DateTime.Now

oK:internal_phone := "Ext 546372"

//SQL_L5_TestBed.ChangeClerkRecord(oK, RowChangeModeSQL.InsertPKI) //----- works OK ----

SQL_L5_TestBed.ChangeClerkRecord(oK, RowChangeModeSQL.Update) //----- works fine !! -----

//SQL_L5_TestBed.ChangeClerkRecord(oK, RowChangeModeSQL.Delete) //----- also works -----

RETURN

METHOD TestCustomer() AS VOID

LOCAL oC AS Customer

oC := Customer{}

oC:UnRef := 103

oC:Salutation := "Prof."

oC:Surname := "Custard"

oC:Firstname := "Carol"

oC:KnownBy := "Creamy"

oC:Add1 := "88 Sunset Strip"

oC:Add2 := "Sunshine Boulevard"

oC:Add3 := "Downtown"

oC:Add4 := "Somewhere"

oC:Add5 := "Whata State"

oC:PostCode := "FL5 6TY"

oC:PhoneHome := "01633 247654"

oC:PhoneWork := "01633 245678"

oC:Mobile := "0785 213 6119"

oC:Email := "me@there.net"

oC:DateTimeStamp := DateTime.Now

oC:EditStamp := DateTime.Now

//SQL_L5_TestBed.ChangeCustomerRecord(oC, RowChangeModeSQL.InsertPKI) //----- works OK ----

//SQL_L5_TestBed.ChangeCustomerRecord(oC, RowChangeModeSQL.Delete) //----- also works -----

SQL_L5_TestBed.ChangeCustomerRecord(oC, RowChangeModeSQL.Update) //----- works fine !! -----

RETURN

METHOD TestQuery() AS VOID

LOCAL oQ AS Query

oQ := Query{}

oQ:Query_ID := 1 //----- ignored on INSERT but needed for UPDATE -----

oQ:Script := ".. UP test THREE ..."

oQ:EntryStamp := DateTime.Now

oQ:ResultType := SQL_ResultType.Nothing

oQ:Mode := SQL_Mode.Data

//SQL_L5_TestBed.ChangeQueryRecord(oQ, RowChangeModeSQL.InsertPKI) //----- works OK ----

//SQL_L5_TestBed.ChangeQueryRecord(oQ, RowChangeModeSQL.UpdatePKI) //----- both forms now work -----

//SQL_L5_TestBed.ChangeQueryRecord(oQ, RowChangeModeSQL.Update) //----- works fine !! -----

//SQL_L5_TestBed.ChangeQueryRecord(oQ, RowChangeModeSQL.Delete) //----- also works OK -----

//SQL_L5_TestBed.ChangeQueryRecord(oQ, RowChangeModeSQL.DeletePKI) //----- also works -----

RETURN

METHOD TestAttendee() AS VOID

LOCAL oA AS Attendee

oA := Attendee{}

oA:Attendee_ID := 101

oA:Surname := "Logan"

oA:Firstname := "Dan"

oA:RegDate := dateTime{2012,02,13}

oA:FeePaid := 69.00

oA:IsSpeaker := FALSE

oA:DaysAtDSO := 5

//SQL_L5_TestBed.ChangeQueryRecord(oA, RowChangeModeSQL.Insert) //----- tested A-Okay -----

SQL_L5_TestBed.ChangeQueryRecord(oA, RowChangeModeSQL.Update) //----- works fine !! -----

//SQL_L5_TestBed.ChangeQueryRecord(oA, RowChangeModeSQL.Delete) //----- also works OK -----

RETURN

METHOD TestParty() AS VOID

LOCAL oP AS Party

oP := Party{}

oP:Party_ID := 10010

oP:Party_Name := "Testing GGG insert"

oP:Salutation := "Mr."

oP:Surname := "Logan"

oP:Firstname := "Danny"

oP:phone_number := "01010 456789"

oP:add1 := "10, Side Street"

oP:add2 := "Broad Avenue"

oP:add3 := "Somerton"

oP:postcode := "NP11 8GH"

oP:credit_card := "4045 5671 2085 5099"

oP:Date_Entered := DateTime{2012,02,12,12,34,13}

//SQL_L5_TestBed.ChangePartyRecord(oP, RowChangeModeSQL.Insert) //----- does NOT work ! ----

//SQL_L5_TestBed.ChangePartyRecord(oP, RowChangeModeSQL.InsertPKI) //----- works OK ----

SQL_L5_TestBed.ChangePartyRecord(oP, RowChangeModeSQL.Update)

//SQL_L5_TestBed.ChangePartyRecord(oP, RowChangeModeSQL.Delete)

RETURN

METHOD DoSendAnyQuery() AS VOID

LOCAL cScript AS STRING

cScript := SELF:tbxNewQuery:Text:Trim()

//---------------------------------------

IF cScript:ToUpper():Substring(0,7) = "SELECT "

SELF:DoSendDataQuery()

RETURN

ENDIF

//---------------------------------------

MessageBox.Show(cScript,"Query :")

IF SQL_L6_funcs.SendNonQueryScript(cScript)

MessageBox.Show("Yep! seems to have worked OK.","Non Query :")

ELSE

MessageBox.Show("NO NO !! something has gone wrong.","Non Query :")

ENDIF

RETURN

METHOD DoSendDataQuery() AS VOID

LOCAL cScript AS STRING

cScript := SELF:tbxNewQuery:Text:Trim()

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript, "Test table")

oDT := oDS:Tables[0]

SELF:dgReturn:DataContext := oDT //----- works fine -----

RETURN

METHOD DoSelectQuery() AS VOID

LOCAL cText AS STRING

LOCAL oDRV AS DataRowView

oDRV := (DataRowView)SELF:cbxQuery:SelectedItem

TRY

cText := oDRV:Item[1]:ToString()

SELF:tbxNewQuery:Text := cText

CATCH oEX AS SystemException

MessageBox.Show(cText,"My Error:")

end try

RETURN

METHOD DoSaveQuery() AS VOID

LOCAL cScript AS STRING

cScript := SELF:tbxNewQuery:Text:Trim()

//---------------------------------------

LOCAL oQ AS Query

oQ := Query{}

oQ:Query_ID := -999 //----- ignored on INSERT but needed for UPDATE -----

oQ:Script := cScript

oQ:EntryStamp := DateTime.Now

//----- check for 'SELECT ' at front - force 'Table' -----

IF cScript:Substring(0,7):ToUpper() = "SELECT "

oQ:ResultType := SQL_ResultType.Table

ELSE

oQ:ResultType := TestBedSQL.SQL_ResultType.Nothing

//----- will be correct in next compiler version! -----

ENDIF

oQ:Mode := SQL_Mode.Data

//------------------- now send to Server --------------------

SQL_L5_TestBed.ChangeQueryRecord(oQ, RowChangeModeSQL.InsertPKI) //----- works OK ----

RETURN

METHOD DoRemoveSelected() AS VOID

LOCAL cText AS STRING

IF SELF:cbxQuery:SelectedIndex < 0

RETURN

ENDIF

LOCAL oDRV AS DataRowView

oDRV := (DataRowView)SELF:cbxQuery:SelectedItem

TRY

cText := oDRV:Item[0]:ToString()

MessageBox.Show(cText,"Query0 :")

cText := oDRV:Item[1]:ToString()

MessageBox.Show(cText,"Query1 :")

cText := oDRV:Item[2]:ToString()

MessageBox.Show(cText,"Query2 :")

cText := oDRV:Item[3]:ToString()

MessageBox.Show(cText,"Query3 :")

/* cText := oDRV:Row:Item[0]:ToString()

MessageBox.Show(cText,"Query 0:")*/

CATCH oEX AS SystemException

MessageBox.Show(cText,"My Error:")

END TRY

RETURN

END CLASS

//====================================================================================

[Serializable]CLASS ConnectionDetails

EXPORT cFile AS STRING

EXPORT cFolder AS STRING

EXPORT cFilePath AS STRING

EXPORT cProvider AS STRING

EXPORT cServerName AS STRING

EXPORT cSecurity AS STRING

EXPORT nTimeOut AS INT

EXPORT cUserName AS STRING

EXPORT cPassWord AS STRING

EXPORT lWindowsUserAccess AS Boolean

EXPORT cInstance AS STRING

EXPORT cConnectionString AS STRING

CONSTRUCTOR()

SUPER()

cFile := ""

cFolder := ""

cFilePath := ""

cProvider := ""

cServerName := ""

cSecurity := ""

nTimeOut := 0

cUserName := ""

cPassWord := ""

lWindowsUserAccess := TRUE

cInstance := ""

cConnectionString := ""

RETURN

END CLASS

//====================================================================================

class MyError

private nUnRef as int

private cMethodName as string

private cObjectClass as string

private cNamespace as string

private cOperation as string

/* private cMainMessage as string

private cInnerMessage as string*/

public cMainMessage as string

public cInnerMessage as string

private dOccurred as DateTime

/*private nSQLerrorCode as int

private cSQLerrorText as string*/

public nSQLerrorCode as int

public cSQLerrorText as string

CONSTRUCTOR()

SUPER()

self:Initialize()

RETURN

CONSTRUCTOR(cM as string, cCL as string, cNS as string, oEX as SystemException)

SUPER()

self:Initialize(cM, cCL, cNS, oEX)

RETURN

CONSTRUCTOR(cM as string, cCL as string, cNS as string, oDBX as DbException)

SUPER()

self:Initialize(cM, cCL, cNS, oDBX)

RETURN

method ErrorInit() as void

//self:nSQLerrorCode := -999

//self:nSQLerrorCode := SQL_L6_funcs.GetErrorCodeFromServer()

//----------------------------------------------------------

self:nUnRef := -999

self:nUnRef := (int)SQL_L6_funcs.SendScalarQueryScript("select MAX(UnRef)+1 from [dbo].[MyErrors]")

self:MethodName := ".."

self:ObjectClass := ".."

self:Namespace := ".."

self:Operation := ".."

self:cMainMessage := ".."

self:cInnerMessage := ".."

self:dOccurred := DateTime.Now

//----------------------------------------------------------------------------

self:cSQLerrorText := ".." //----- make a method to find this from code -----

return

method Initialize() as void

self:ErrorInit()

return

method Initialize(cM as string, cCL as string, cNS as string, oEX as SystemException) as void

self:ErrorInit()

self:MethodName := cM

self:ObjectClass := cCL

self:Namespace := cNS

self:cMainMessage := SQL_L6_funcs.MyMainMessage(oEX)

self:cInnerMessage := SQL_L6_funcs.MyInnerMessage(oEX)

/*self:MainMessage(oEX)

self:InnerMessage(oEX)*/

return

method Initialize(cM as string, cCL as string, cNS as string, oDBX as DbException) as void

self:ErrorInit()

self:MethodName := cM

self:ObjectClass := cCL

self:Namespace := cNS

self:cMainMessage := SQL_L6_funcs.MyMainMessage(oDBX)

self:cInnerMessage := SQL_L6_funcs.MyInnerMessage(oDBX)

/*self:MainMessage(oEX)

self:InnerMessage(oEX)*/

return

method SaveToSQLtable() as void

//SQL_L6_funcs.ChangeErrorRecord(self, RowChangeModeSQL.Insert)

return

//--------------------------------------

access UnRef() as int

return self:nUnRef

/*assign UnRef(nU as int) as void

self:nUnRef := nU

return*/

//--------------------------------------

access MethodName() as string

return self:cMethodName

assign MethodName(cM as string) as void

self:cMethodName := cM

return

//--------------------------------------

access ObjectClass() as string

return self:cObjectClass

assign ObjectClass(cO as string) as void

self:cObjectClass := cO

return

//--------------------------------------

access Namespace() as string

return self:cNamespace

assign Namespace(cN as string) as void

self:cNamespace := cN

return

//--------------------------------------

access Operation() as string

return self:cOperation

assign Operation(cO as string) as void

self:cOperation := cO

return

//--------------------------------------

access MainMessage() as string

return self:cMainMessage

/*assign MainMessage(oEX as SystemException) as void

self:cMainMessage := SQL_L6_funcs.MyMainMessage(oEX)

return*/

//--------------------------------------

access InnerMessage() as string

return self:cInnerMessage

/*assign InnerMessage(oEX as SystemException) as void

self:cInnerMessage := SQL_L6_funcs.MyInnerMessage(oEX)

return*/

//--------------------------------------

access Occurred() as DateTime

return self:dOccurred

/*assign Occurred(dtO as DateTime) as void

self:dOccurred := dtO

return*/

//--------------------------------------

access SQLerrorCode() as int

return self:nSQLerrorCode

/*assign SQLerrorCode(nEC as int) as void

self:nSQLerrorCode := nEC

return*/

//--------------------------------------

access SQLerrorText() as string

return self:cSQLerrorText

/*assign SQLerrorText(cET as string) as void

self:cSQLerrorText := cET

return*/

//--------------------------------------

END CLASS

//=======================================

ENUM RowChangeModeSQL

MEMBER Insert := 1

MEMBER Update := 2

MEMBER Delete := 3

MEMBER InsertPKI := 4

MEMBER UpdatePKI := 5

MEMBER DeletePKI := 6

END ENUM

END NAMESPACE

========================================================================================

//----- SQL_L5_TestBed.prg -----

//----- February 11th. 2012 -----

//----- for presentation at DevShare 'Olympic' -----

//----- tidied up April 29th. 2012 -----

#using System

#using System.Collections.Generic

#using System.Linq

#using System.Text

#using System.Windows

#using System.Windows.Controls

#using System.Windows.Data

#using System.Windows.Documents

#using System.Windows.Input

#using System.Windows.Media

#using System.Windows.Media.Imaging

#using System.Windows.Navigation

#using System.Windows.Shapes

//==================================

#USING System.IO

#using System.Data

#using System.Data.Common

#using System.Data.SqlTypes

BEGIN NAMESPACE TestBedSQL

//----------------------------------

STATIC CLASS SQL_L5_TestBed

STATIC PRIVATE cScript AS STRING

//--------------------------------------

STATIC PROPERTY Script AS STRING

GET

return cScript

END GET

SET

cScript := VALUE

END SET

END PROPERTY

//--------------------------------------

STATIC CONSTRUCTOR()

//----- no call to 'SUPER' in a closed CONSTRUCTOR ! -----

cScript := ""

RETURN

#region {Attendee <table>}

//=========================== [ Attendee <table> Stuff ] =============================

STATIC METHOD Query_FillTypedList(oA AS Attendee) AS List<SQLfieldSpec>

LOCAL IMPLIED aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[Attendee_ID]", "@aid", DbType.Int32, oA:Attendee_ID})

aSFS:Add(SQLfieldSpec{"[Surname]", "@sn", DbType.String, oA:Surname})

aSFS:Add(SQLfieldSpec{"[Firstname]", "@fn", DbType.String, oA:Firstname})

aSFS:Add(SQLfieldSpec{"[RegDate]", "@rd", DbType.DateTime, oA:RegDate})

aSFS:Add(SQLfieldSpec{"[FeePaid]", "@fp", DbType.Currency, oA:FeePaid})

aSFS:Add(SQLfieldSpec{"[IsSpeaker]", "@isp", DbType.Boolean, oA:IsSpeaker})

aSFS:Add(SQLfieldSpec{"[DaysAtDSO]", "@das", DbType.Int32, oA:DaysAtDSO})

RETURN aSFS

STATIC METHOD ChangeQueryRecord(oA AS Attendee, eMode AS RowChangeModeSQL) AS Boolean

//----- a User 'Attendee' can be stored for later! -----

LOCAL IMPLIED lResult := FALSE

LOCAL IMPLIED cTable := "[Attendee]" //----- change for other Tables -----

//==========================================================================

//----- will now do 'Delete' as well as 'Insert' & 'Update' -----

LOCAL aSFS AS List<SQLfieldSpec>

aSFS := SQL_L5_TestBed.Query_FillTypedList(oA) //----- change object -----

//--------------------------------------------

lResult := SQL_L6_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

RETURN lResult

//========================= [ End Attendee Object / Table ] ===========================

//=========================== [ start of 'Attendee' stuff ] =============================

STATIC METHOD NumberOfAttendees() AS INT

Script := "select COUNT(*) from [Attendee]"

SQL_L6_funcs.MyMessages(Script,"Attendee count :")

LOCAL nNum AS INT

TRY

LOCAL cNum AS STRING

cNum := SQL_L6_funcs.SendScalarQueryScript(cScript):ToString()

nNum := convert.ToInt32(cNum)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error Double ? :")

end try

RETURN nNum

STATIC METHOD AttendeeCountTable() AS DataTable

Script := "select COUNT(*) from [Attendee]"

SQL_L6_funcs.MyMessages(Script,"Attendee count Table :")

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

TRY

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript, "Attendee Table")

oDT := oDS:Tables[0]

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error Double ? :")

end try

RETURN oDT

//========================= [ End 'Attendee' stuff ] ==========================================

#endregion

#region {Query <table>}

//=========================== [ Query <table> Stuff ] =============================

STATIC METHOD Query_FillTypedList(oQ AS Query) AS List<SQLfieldSpec>

LOCAL IMPLIED aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[Query_ID]", "@qid", DbType.Int32, oQ:Query_ID})

aSFS:Add(SQLfieldSpec{"[Script]", "@sc", DbType.String, oQ:Script})

aSFS:Add(SQLfieldSpec{"[EntryStamp]", "@es", DbType.DateTime, oQ:EntryStamp})

aSFS:Add(SQLfieldSpec{"[Result_Type]", "@rt", DbType.String, oQ:ResultType:ToString()})

aSFS:Add(SQLfieldSpec{"[Mode]", "@md", DbType.String, oQ:Mode:ToString() })

RETURN aSFS

STATIC METHOD ChangeQueryRecord(oQ AS Query, eMode AS RowChangeModeSQL) AS Boolean

//----- a User Query can be stored for later! -----

LOCAL IMPLIED lResult := FALSE

LOCAL IMPLIED cTable := "[Query]" //----- change for other Tables -----

//==========================================================================

//----- will now do 'Delete' as well as 'Insert' & 'Update' -----

LOCAL aSFS AS List<SQLfieldSpec>

aSFS := SQL_L5_TestBed.Query_FillTypedList(oQ)

//--------------------------------------------

lResult := SQL_L6_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

RETURN lResult

//========================= [ End Query Object / Table ] ===========================

//=========================== [ start of 'Query' stuff ] =============================

STATIC METHOD QueryCount() AS INT

Script := "select COUNT(*) from [Query]"

//MessageBox.Show(Script,"Query count :")

SQL_L6_funcs.MyMessages(Script,"Query count :")

LOCAL nNum AS INT

TRY

LOCAL cNum AS STRING

cNum := SQL_L6_funcs.SendScalarQueryScript(cScript):ToString()

nNum := convert.ToInt32(cNum)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error Double ? :")

end try

RETURN nNum

STATIC METHOD QueryCountTable() AS DataTable

Script := "select COUNT(*) from [Query]"

//MessageBox.Show(Script,"Query count Table :")

SQL_L6_funcs.MyMessages(Script,"Query count Table :")

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

TRY

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript, "TableTest")

oDT := oDS:Tables[0]

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error Double ? :")

end try

RETURN oDT

STATIC METHOD GetAllQueryRows() AS DataTable

LOCAL IMPLIED cScript := "select * FROM [Query];"

MessageBox.Show(cScript,"My Script :")

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

TRY

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript,"All Queries :")

oDT := oDS:Tables[0]

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN oDT

STATIC METHOD GetAllQueries() AS DataTable

LOCAL cScript AS STRING

cScript := "select [Script] from [dbo].[Query] order by [EntryStamp];"

SQL_L6_funcs.MyMessages(cScript,"My Script :")

LOCAL oDS as DataSet

LOCAL oDT AS DataTable

TRY

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript,"Query Scripts :")

oDT := oDS:Tables[0]

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN oDT

STATIC METHOD GetQueryList() AS List<STRING>

LOCAL IMPLIED oList := List<STRING>{}

LOCAL oDT AS DataTable

oDT := GetAllQueries()

LOCAL IMPLIED nCount := oDT:Rows:Count

FOR LOCAL IMPLIED i := 0 UPTO nCount-1

oList:Add(oDT:Rows[i]:Item[0]:ToString())

NEXT

RETURN oList

//========================= [ End 'Query' stuff ] ==========================================

#endregion

#region {Clerk - Booking}

//=========================== [ Booking 'Clerk' Stuff ] =============================

STATIC METHOD Clerk_FillTypedList(oC AS Clerk) AS List<SQLfieldSpec>

LOCAL IMPLIED aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[Booking_Clerk_ID]", "@bcid", DbType.Int32, oC:Booking_Clerk_ID})

aSFS:Add(SQLfieldSpec{"[Surname]", "@sur", DbType.String, oC:Surname})

aSFS:Add(SQLfieldSpec{"[Firstname]", "@fn", DbType.String, oC:Firstname})

aSFS:Add(SQLfieldSpec{"[password]", "@pw", DbType.String, oC:password})

aSFS:Add(SQLfieldSpec{"[BC_EnterStamp]", "@es", DbType.DateTime, oC:BC_EntryStamp})

aSFS:Add(SQLfieldSpec{"[BC_ModifyStamp]", "@ms", DbType.DateTime, oC:BC_ModifyStamp})

aSFS:Add(SQLfieldSpec{"[internal_phone]", "@ip", DbType.String, oC:internal_phone})

RETURN aSFS

STATIC METHOD ChangeClerkRecord(oC AS Clerk, eMode AS RowChangeModeSQL) AS Boolean

//----- a Clerk does the booking of tickets etc. -----

LOCAL IMPLIED lResult := FALSE

LOCAL IMPLIED cTable := "[Clerk]" //----- change for other Tables -----

//==========================================================================

//----- will now do 'Delete' as well as 'Insert' & 'Update' -----

LOCAL aSFS AS List<SQLfieldSpec>

aSFS := SQL_L5_TestBed.Clerk_FillTypedList(oC)

//--------------------------------------------

lResult := SQL_L6_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

RETURN lResult

//========================= [ End 'Clerk' Object ] ==========================================

#endregion

#region {Customer - Booking}

//=========================== [ 'Customer' Stuff ] =============================

static method Customer_FillTypedList(oC as Customer) as List<SQLfieldSpec>

LOCAL IMPLIED aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[UnRef]", "@ur", DbType.Int32 , oC:UnRef})

aSFS:Add(SQLfieldSpec{"[Salutation]", "@sal", DbType.string, oC:Salutation})

aSFS:Add(SQLfieldSpec{"[Surname]", "@sn", DbType.string, oC:Surname})

aSFS:Add(SQLfieldSpec{"[Firstname]", "@fn", DbType.string, oC:Firstname})

aSFS:Add(SQLfieldSpec{"[KnownBy]", "@kb", DbType.string, oC:Knownby})

aSFS:Add(SQLfieldSpec{"[Add1]", "@ad1", DbType.string, oC:Add1})

aSFS:Add(SQLfieldSpec{"[Add2]", "@ad2", DbType.string, oC:Add2})

aSFS:Add(SQLfieldSpec{"[Add3]", "@ad3", DbType.string, oC:Add3})

aSFS:Add(SQLfieldSpec{"[Add4]", "@ad4", DbType.string, oC:Add4})

aSFS:Add(SQLfieldSpec{"[Add5]", "@ad5", DbType.string, oC:Add5})

aSFS:Add(SQLfieldSpec{"[PostCode]", "@pc", DbType.string, oC:PostCode})

aSFS:Add(SQLfieldSpec{"[PhoneHome]", "@ph", DbType.string, oC:PhoneHome})

aSFS:Add(SQLfieldSpec{"[PhoneWork]", "@pw", DbType.string, oC:PhoneWork})

aSFS:Add(SQLfieldSpec{"[Mobile]", "@mob", DbType.string, oC:Mobile})

aSFS:Add(SQLfieldSpec{"[Email]", "@em", DbType.string, oC:Email})

aSFS:Add(SQLfieldSpec{"[DateTimeStamp]", "@dts", DbType.DateTime, oC:DateTimeStamp})

aSFS:Add(SQLfieldSpec{"[EditStamp]", "@es", DbType.DateTime, oC:EditStamp})

return aSFS

STATIC METHOD ChangeCustomerRecord(oC as Customer, eMode AS RowChangeModeSQL) AS Boolean

//----- a Customer in 'Small Trader' / 'Invoice' application -----

LOCAL IMPLIED lResult := FALSE

LOCAL IMPLIED cTable := "[Customer]" //----- change for other Tables -----

//==========================================================================

//----- will now do 'Delete' as well as 'Insert' & 'Update' -----

LOCAL aSFS AS List<SQLfieldSpec>

aSFS := SQL_L5_TestBed.Customer_FillTypedList(oC)

//--------------------------------------------

lResult := SQL_L6_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

RETURN lResult

//========================= [ End 'Customer' Object ] ==========================================

#endregion

#region {Party - Booking}

//=========================== [ 'Party' object ] =============================

STATIC METHOD Party_FillTypedList(oP AS Party) AS List<SQLfieldSpec>

LOCAL IMPLIED aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[Party_ID]", "@pid", DbType.Int32 , oP:Party_ID})

aSFS:Add(SQLfieldSpec{"[Party_Name]", "@pn", DbType.string, oP:Party_Name})

aSFS:Add(SQLfieldSpec{"[Salutation]", "@sal", DbType.string, oP:Salutation})

aSFS:Add(SQLfieldSpec{"[Surname]", "@sn", DbType.string, oP:Surname})

aSFS:Add(SQLfieldSpec{"[Firstname]", "@fn", DbType.string, oP:Firstname})

aSFS:Add(SQLfieldSpec{"[phone_number]", "@phn", DbType.string, oP:phone_number})

aSFS:Add(SQLfieldSpec{"[add1]", "@ad1", DbType.string, oP:add1})

aSFS:Add(SQLfieldSpec{"[add2]", "@ad2", DbType.string, oP:add2})

aSFS:Add(SQLfieldSpec{"[add3]", "@ad3", DbType.string, oP:add3})

aSFS:Add(SQLfieldSpec{"[postcode]", "@pc", DbType.string, oP:postcode})

aSFS:Add(SQLfieldSpec{"[credit_card]", "@cc", DbType.string, oP:credit_card})

aSFS:Add(SQLfieldSpec{"[Date_Entered]", "@de", DbType.DateTime, oP:Date_Entered})

RETURN aSFS

STATIC METHOD ChangePartyRecord(oP AS Party, eMode AS RowChangeModeSQL) AS Boolean

//----- a 'Party' as with meaning for 'Hall Seat Booking' application -----

LOCAL IMPLIED lResult := FALSE

LOCAL IMPLIED cTable := "[Party]" //----- change for other Tables -----

//==========================================================================

//----- will now do 'Delete' as well as 'Insert' & 'Update' -----

LOCAL aSFS AS List<SQLfieldSpec>

aSFS := SQL_L5_TestBed.Party_FillTypedList(oP)

//--------------------------------------------

lResult := SQL_L6_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

RETURN lResult

STATIC METHOD DeletePartyOnID(nID AS INT) AS Boolean

LOCAL lCheck1, lCheck2 AS Boolean

lCheck1 := DoesPartyExist(nID)

//----- check to see exists, and then again after -----

LOCAL lResult AS Boolean

LOCAL cScript AS STRING

cScript := "delete from [Party] where [Party_ID] = "+nID:ToString()+" ;"

TRY

lResult := SQL_L6_funcs.SendNonQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

END TRY

LOCAL IMPLIED lResult2 := FALSE

lCheck2 := DoesPartyExist(nID)

IF lCheck1 .and. !lCheck2

lResult2 := true

ENDIF

RETURN lResult2

STATIC METHOD DeletePartyOnID(oP as Party) AS Boolean

LOCAL IMPLIED lResult := DeletePartyOnID(oP:Party_ID)

RETURN lResult

//========================= [ End 'Party' object ] ==========================================

//=========================== [ start of 'Party' stuff ] =============================

STATIC METHOD HighestPartyID() AS INT

LOCAL IMPLIED cScript := "select MAX([Party_ID]) FROM [Party] ;"

MessageBox.Show(cScript,"My Script :")

LOCAL IMPLIED nX := (INT)SQL_L6_funcs.SendScalarQueryScript(cScript)

RETURN nX

STATIC METHOD NextPartyID() AS INT

//----- simpler than looks ! -----

//----- BUT care - could be that deleted IDs exist !? -----

RETURN SQL_L5_TestBed.HighestPartyID()+1

STATIC METHOD DoesPartyExist(nID AS INT) AS Boolean

LOCAL IMPLIED lResult := FALSE

LOCAL cScript AS STRING

cScript := "select count(*) FROM [Party] where [Party_ID] = "+nID:ToString()+";"

MessageBox.Show(cScript,"My Script :")

LOCAL IMPLIED nX := (INT)SQL_L6_funcs.SendScalarQueryScript(cScript)

IF nX > 0

lResult := TRUE

ENDIF

RETURN lResult

STATIC METHOD GetAllPartyRows() AS DataTable

LOCAL IMPLIED cScript := "select * FROM [Party];"

MessageBox.Show(cScript,"My Script :")

LOCAL oDS as DataSet

LOCAL oDT AS DataTable

TRY

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript,"All Parties :")

oDT := oDS:Tables[0]

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN oDT

//========================= [ End 'Party' stuff ] ==========================================

#endregion

#region {DumpSetHeader}

//=========================== [ DumpSetHeader Stuff ] =============================

/*STATIC METHOD CountDumpSets4Batch_ID(nSet as int) AS INT

LOCAL nResult AS INT

LOCAL cScript, cSet AS STRING

cSet := nSet:ToString()

//----- final 'space' below is necessary, in all lines -----

cScript := "select COUNT(*) "

cScript += "FROM [C:\USERS\GROUNDPROBE08.MDF].[dbo].[DumpSetHeader] "

cScript += "where [Batch_ID] = "+cSet+" "

//cScript += "where [Batch_ID] = 123 "

TRY

nResult := (INT)SQL_L6_funcs.SendScalarQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN nResult*/

//========================= [ End DumpSetHeader Object ] ==========================================

#endregion

#region {Batch}

//=========================== [ BatchHeader Stuff ] =============================

/*static method BatchHeader_FillTypedList(oBH as BatchHeader) as List<SQLfieldSpec>

local aSFS as List<SQLfieldSpec>

aSFS := List<SQLfieldSpec>{}

//aSFS:Add(SQLfieldSpec{"[Batch_ID]", "@bid", DbType.Int64 , 131313})

aSFS:Add(SQLfieldSpec{"[Filename]", "@fn", DbType.string, oBH:Filename})

aSFS:Add(SQLfieldSpec{"[Created]", "@cr", DbType.DateTime, oBH:Created})

aSFS:Add(SQLfieldSpec{"[Processed]", "@pr", DbType.DateTime, oBH:Processed})

aSFS:Add(SQLfieldSpec{"[NumDumpSets]", "@nds", DbType.Int32, oBH:NumDumpSets})

aSFS:Add(SQLfieldSpec{"[NumSensors]", "@nsn", DbType.Int32, oBH:NumSensors})

return aSFS*/

/*STATIC METHOD ChangeBatchRecord(oBH AS BatchHeader, eMode AS RowChangeModeSQL) AS Boolean

//----- a DumpSetHeader decribes the 'dumped' Readings set ! -----

local lResult := false as Boolean

if eMode = RowChangeModeSQL.Delete

MessageBox.Show("Wrong mode requested >> DELETE !?","Change MODE :")

return lResult

endif

local cTable := "[BatchHeader]" as string //----- change for other Tables -----

//==========================================================================

local aSFS as List<SQLfieldSpec>

aSFS := SQL_GroundProbe.BatchHeader_FillTypedList(oBH)

//--------------------------------------------

lResult := SQL_L6_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

RETURN lResult*/

//========================= [ End BatchHeader Object ] ==========================================

/*STATIC METHOD NumOfDumpSets(oDT as DateTime) AS INT

LOCAL nResult AS INT

LOCAL cScript AS STRING

LOCAL cDateTime AS STRING

//----- now conversion function is in the basic ADO.NET Layer 6 -----

cDateTime := SQL_L6_funcs.DotnetDT2SQLstring(oDT)

//----- final 'space' below is necessary, in all lines -----

cScript := "select [NumDumpSets] "

cScript += "from [C:\USERS\GROUNDPROBE08.MDF].[dbo].[BatchHeader] "

cScript += "where [Created] = ' "+cDateTime+" ' "

//cScript += "where [Created] = '2011-12-22 10:26:28.970' "

//cScript += "where [Created] = '2011-11-22 10:26:28.970' "

TRY

nResult := (INT)SQL_L6_funcs.SendScalarQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN nResult*/

/*STATIC METHOD BatchProcessedOn() AS DateTime

LOCAL dtResult AS DateTime

LOCAL cScript AS STRING

cScript := "select MAX([Processed]) "

cScript += "from [C:\USERS\GROUNDPROBE08.MDF].[dbo].[BatchHeader] "

cScript += "where [Created] = '2011-12-22 10:26:28.970' "

//cScript += "where [Created] = '2011-11-22 10:26:28.970' "

TRY

dtResult := (DateTime)SQL_L6_funcs.SendScalarQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN dtResult*/

/*STATIC METHOD IsBatchAlreadyProcessed() AS Boolean

LOCAL lResult AS Boolean

LOCAL cScript AS STRING

cScript := "select COUNT(*) "

cScript += "from [C:\USERS\GROUNDPROBE08.MDF].[dbo].[BatchHeader] "

cScript += "where [Created] = '2011-12-22 10:26:28.970' "

//cScript += "where [Created] = '2011-11-22 10:26:28.970' "

TRY

LOCAL nNum AS INT

nNum := (INT)SQL_L6_funcs.SendScalarQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

lResult := FALSE

IF nNum > 0

lResult := TRUE

ENDIF

RETURN lResult*/

//=========================== [ END - BatchHeader Stuff ] =============================

#endregion

#region {biggets PK values}

/*STATIC METHOD GetLargestReadingID() AS INT64

LOCAL cScript AS STRING

cScript := "select MAX([Reading_ID]) FROM [Reading]"

LOCAL nBiggest AS INT64

try

nBiggest := (INT64)SQL_L6_funcs.SendScalarQueryScript(cScript)

//MessageBox.Show(nBiggest:ToString(),"Reading Biggest :")

catch oEX as SystemException

MessageBox.Show(oEX:Message,"My Error :")

end try

RETURN nBiggest*/

/*STATIC METHOD GetLargestDumpSetID() AS INT64

LOCAL cScript AS STRING

cScript := "select MAX([DumpSet_ID]) FROM [DumpSetHeader]"

LOCAL nBiggest AS INT64

try

nBiggest := (INT64)SQL_L6_funcs.SendScalarQueryScript(cScript)

//MessageBox.Show(nBiggest:ToString(),"DumpSet Biggest :")

catch oEX as SystemException

MessageBox.Show(oEX:Message,"My Error :")

end try

RETURN nBiggest*/

/*STATIC METHOD GetLargestBatchID() AS INT64

LOCAL cScript AS STRING

//cScript := "select MAX([Batch_ID]) FROM [C:\USERS\GROUNDPROBE08.MDF].[dbo].[BatchHeader]"

//cScript := "select MAX([Batch_ID]) FROM [dbo].[BatchHeader]"

cScript := "select MAX([Batch_ID]) FROM [BatchHeader]"

LOCAL nBiggest AS INT64

try

nBiggest := (INT64)SQL_L6_funcs.SendScalarQueryScript(cScript)

//MessageBox.Show(nBiggest:ToString(),"Batch Biggest :")

catch oEX as SystemException

MessageBox.Show(oEX:Message,"My Error :")

end try

RETURN nBiggest*/

#endregion

#region {clear tables}

/*STATIC METHOD EmptyTable(cTable as string) AS void

LOCAL cScript AS STRING

cScript := "delete FROM ["+cTable:Trim()+"]"

MessageBox.Show(cScript,"My Script :")

try

SQL_L6_funcs.SendNonQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN */

//-------------------------------------------------------

/*STATIC METHOD EmptyTableReading() AS void

LOCAL cScript AS STRING

cScript := "delete FROM [Reading]"

try

SQL_L6_funcs.SendNonQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message,"My Error :")

end try

RETURN

STATIC METHOD EmptyTableDumpSetHeader() AS void

LOCAL cScript AS STRING

cScript := "delete FROM [DumpSetHeader]"

try

SQL_L6_funcs.SendNonQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message,"My Error :")

end try

RETURN

STATIC METHOD EmptyTableBatchHeader() AS void

LOCAL cScript AS STRING

cScript := "delete FROM [BatchHeader]"

try

SQL_L6_funcs.SendNonQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message,"My Error :")

end try

RETURN*/

#endregion

#region {general table stats}

/*STATIC METHOD NumRows(cTable as string) AS int64

LOCAL cScript AS STRING

cScript := "select Count(*) FROM ["+cTable:Trim()+"]"

MessageBox.Show(cScript,"My Script :")

TRY

LOCAL nNum AS INT

nNum := (INT)SQL_L6_funcs.SendScalarQueryScript(cScript)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN nNum*/

/*STATIC METHOD MaxTempSensorN(nSensor as int) AS Double

LOCAL cScript AS STRING

cScript := "select MAX(Temp) FROM [Reading] where [Sensor_ID] = "+nSensor:ToString()

MessageBox.Show(cScript,"My Script :")

TRY

LOCAL nNum AS Double

LOCAL cNum AS STRING

cNum := SQL_L6_funcs.SendScalarQueryScript(cScript):ToString()

nNum := convert.ToDouble(cNum)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error Double ? :")

end try

RETURN nNum*/

/*STATIC METHOD MinTempSensorN(nSensor as int) AS Double

LOCAL cScript AS STRING

cScript := "select MIN(Temp) FROM [Reading] where [Sensor_ID] = "+nSensor:ToString()

MessageBox.Show(cScript,"My Script :")

TRY

LOCAL nNum AS Double

LOCAL cNum AS STRING

cNum := SQL_L6_funcs.SendScalarQueryScript(cScript):ToString()

nNum := convert.ToDouble(cNum)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error :")

end try

RETURN nNum*/

/*STATIC METHOD MaxTempSensorsAll() AS Double

LOCAL cScript AS STRING

cScript := "select MAX(Temp) FROM [Reading]"

MessageBox.Show(cScript,"MAX temp. Script :")

TRY

LOCAL nNum AS Double

LOCAL cNum AS STRING

cNum := SQL_L6_funcs.SendScalarQueryScript(cScript):ToString()

nNum := convert.ToDouble(cNum)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error Double ? :")

end try

RETURN nNum*/

/*STATIC METHOD MinTempSensorsAll() AS Double

LOCAL cScript AS STRING

cScript := "select MIN(Temp) FROM [Reading]"

MessageBox.Show(cScript,"MIN temp. Script :")

TRY

LOCAL nNum AS Double

LOCAL cNum AS STRING

cNum := SQL_L6_funcs.SendScalarQueryScript(cScript):ToString()

nNum := convert.ToDouble(cNum)

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error Double ? :")

end try

RETURN nNum*/

#endregion

END CLASS

END NAMESPACE //----- TestBedSQL -----

========================================================================================

<Window x:Class="TestBedSQL.ConnectLayer6"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="Connection for STATIC Layer6 :" Height="420" Width="540" Background="MintCream">

<Grid>

<ComboBox Height="23" HorizontalAlignment="Right" Margin="0,7,12,0" Name="cbxMSSS_Instances" VerticalAlignment="Top"

Width="244" ItemsSource="{Binding}"

SelectionChanged="cbxMSSS_Instances_SelectionChanged" />

<Label Content="Server Instances : [0]" Height="28" HorizontalAlignment="Right" Margin="0,3,253,0" Name="lbInstances"

VerticalAlignment="Top" Width="124" Foreground="Red"/>

<Button Content="Find Instances" Height="23" HorizontalAlignment="Left" Margin="12,6,0,0" Name="pbFindInstances"

VerticalAlignment="Top" Width="115" Click="pbFindInstances_Click" Background="Wheat"/>

<Button Content="Find Databases" Height="23" HorizontalAlignment="Left" Margin="12,57,0,0" Name="pbFindDatabases"

VerticalAlignment="Top" Width="115" Click="pbFindDatabases_Click" Background="Wheat"/>

<DataGrid AutoGenerateColumns="True" Margin="12,198,12,60" ItemsSource="{Binding}"

Name="dgReturn" AlternatingRowBackground="Beige" AlternationCount="2"/>

<ComboBox Height="23" HorizontalAlignment="Right" Margin="0,57,12,0" Name="cbxDatabases" VerticalAlignment="Top"

Width="244" ItemsSource="{Binding}" SelectionChanged="cbxDatabases_SelectionChanged" />

<Label Content="Databases : [0]" Height="28" HorizontalAlignment="Right" Margin="0,54,268,0" Name="lbDatabases"

VerticalAlignment="Top" Width="109" Foreground="Red"/>

<Button Content="Find Tables" Height="23" HorizontalAlignment="Left" Margin="12,102,0,0" Name="pbFindTables"

VerticalAlignment="Top" Width="115" Click="pbFindTables_Click" Background="Wheat"/>

<ComboBox Height="23" HorizontalAlignment="Right" Margin="0,102,12,0" Name="cbxTables" VerticalAlignment="Top"

Width="244" ItemsSource="{Binding}" SelectionChanged="cbxTables_SelectionChanged" />

<Label Content="Tables : [0]" Height="28" HorizontalAlignment="Right" Margin="0,99,268,0" Name="lbTables"

VerticalAlignment="Top" Width="109" Foreground="Red"/>

<CheckBox Content="Display Messages :" Height="16" HorizontalAlignment="Left" Margin="14,135,0,0" Name="chbxDisplayMessages"

VerticalAlignment="Top" IsChecked="True" Click="chbxDisplayMessages_Click" />

<StatusBar Height="46" Name="statusBar1" VerticalAlignment="Bottom" >

<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Auto" Width="auto"

VerticalScrollBarVisibility="Disabled" Height="auto">

<TextBlock Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="auto"

Name="txblkConnectionString" Background="Beige" Text="{Binding UpdateSourceTrigger=PropertyChanged}">

</TextBlock>

</ScrollViewer>

</StatusBar>

<Button Content="Find Columns" Height="23" HorizontalAlignment="Left" Margin="12,158,0,0" Name="pbFindColumns"

VerticalAlignment="Top" Width="115" Background="Wheat" Click="pbFindColumns_Click"/>

<GroupBox Header="Specific Connections :" Height="57" HorizontalAlignment="Right" Margin="0,135,12,0" Name="groupBox1"

VerticalAlignment="Top" Width="244" Foreground="Teal">

<Grid>

<Button Content="Default" Height="23" HorizontalAlignment="Left" Margin="9,7,0,0" Name="pbDefault"

VerticalAlignment="Top" Width="90" Background="Chartreuse" Click="pbDefault_Click" />

<Button Content="Open" Height="23" HorizontalAlignment="Right" Margin="0,7,6,0" Name="pbOpen"

VerticalAlignment="Top" Width="90" Background="Chartreuse" Click="pbOpen_Click" />

</Grid>

</GroupBox>

<Button Content="Clear Choices" Height="23" HorizontalAlignment="Left" Margin="145,159,0,0" Name="pbClearChoices"

VerticalAlignment="Top" Width="105" Background="LightCoral" Foreground="AliceBlue"

Click="pbClearChoices_Click" />

</Grid>

</Window>

========================================================================================

#using System

#using System.Collections.Generic

#using System.Linq

#using System.Text

#using System.Windows

#using System.Windows.Controls

#using System.Windows.Data

#using System.Windows.Documents

#using System.Windows.Input

#using System.Windows.Media

#using System.Windows.Media.Imaging

#using System.Windows.Navigation

#using System.Windows.Shapes

BEGIN NAMESPACE TestBedSQL

/// <summary>

/// Interaction logic for ConnectLayer6.xaml

/// </summary>

PARTIAL CLASS ConnectLayer6 INHERIT Window

CONSTRUCTOR()

SUPER()

InitializeComponent()

SELF:Initialize()

RETURN

PRIVATE METHOD pbFindInstances_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoFindInstances()

RETURN

PRIVATE METHOD pbFindDatabases_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoFindDatabases()

RETURN

PRIVATE METHOD cbxMSSS_Instances_SelectionChanged( sender AS System.Object, e AS System.Windows.Controls.SelectionChangedEventArgs ) AS System.Void

SELF:DoSelectInstance()

RETURN

PRIVATE METHOD cbxDatabases_SelectionChanged( sender AS System.Object, e AS System.Windows.Controls.SelectionChangedEventArgs ) AS System.Void

SELF:DoSelectDB()

RETURN

PRIVATE METHOD pbFindTables_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoFindTables()

RETURN

PRIVATE METHOD pbFindColumns_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoFindColumns()

RETURN

PRIVATE METHOD chbxDisplayMessages_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoChangeMessageStatus()

RETURN

PRIVATE METHOD cbxTables_SelectionChanged( sender AS System.Object, e AS System.Windows.Controls.SelectionChangedEventArgs ) AS System.Void

SELF:DoSelectTable()

RETURN

PRIVATE METHOD pbOpen_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoSetOpenConn()

RETURN

PRIVATE METHOD pbDefault_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoSetDefaultConn()

RETURN

PRIVATE METHOD pbClearChoices_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoClearChoices()

RETURN

END CLASS

END NAMESPACE

========================================================================================

//----- partConnectLayer6.prg -----

//----- by PHH on April 24th. 2012 -----

//----- for DSO and ClickStart -----

#using System

#using System.Collections.Generic

#using System.Linq

#using System.Text

#using System.Windows

#using System.Windows.Controls

#using System.Windows.Data

#using System.Windows.Documents

#using System.Windows.Input

#using System.Windows.Media

#using System.Windows.Media.Imaging

#using System.Windows.Navigation

#using System.Windows.Shapes

//==================================

#using System.Data

BEGIN NAMESPACE TestBedSQL

/// <summary>

/// Interaction logic for ConnectLayer6.xaml

/// </summary>

PARTIAL CLASS ConnectLayer6 INHERIT Window

PRIVATE WinUser AS STRING

PRIVATE oDTdbs AS DataTable

PROPERTY WinUser AS STRING Auto

PROPERTY SelectedTable AS STRING Auto

METHOD Initialize() AS VOID

SQL_L6_funcs.ShowMessages := FALSE

SELF:WinUser := "BEST-V64QUAD\"

SELF:oDTdbs := DataTable{}

//SQL_L6_funcs.oSCSB:AttachDBFilename := "C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL_DEV_08_R2\MSSQL\DATA\TESTBED.MDF"

SELF:DoSetDefaultConn()

SQL_L6_funcs.DoSetConnectionString()

SQL_L6_funcs.DoTryToConnect()

SELF:txblkConnectionString:DataContext := SQL_L6_funcs.oSCSB:ConnectionString

/*LOCAL oDT AS DataTable

oDT := SQL_L6_funcs.GetJohanDBs()

SELF:dgReturn:DataContext := oDT

MessageBox.Show(oDT:Rows[6]:Item[0]:ToString(),"Sixth Row :" )*/

RETURN

METHOD DoClearChoices() AS VOID

SELF:ClearInstanceList()

SELF:ClearDatabaseList()

SELF:ClearTableList()

//-------------------------

RETURN

METHOD DoSetDefaultConn() AS VOID

SQL_L6_funcs.oSCSB:AttachDBFilename := "C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL_DEV_08_R2\MSSQL\DATA\TESTBED.MDF"

SELF:txblkConnectionString:DataContext := SQL_L6_funcs.oSCSB:ConnectionString

RETURN

METHOD DoSetOpenConn() AS VOID

SQL_L6_funcs.oSCSB:AttachDBFilename := ""

SELF:txblkConnectionString:DataContext := SQL_L6_funcs.oSCSB:ConnectionString

RETURN

//----- Instances -----

METHOD DoFindInstances() AS VOID

SELF:ClearDatabaseList()

SELF:ClearTableList()

//-------------------------

SELF:Cursor := Cursors.Wait

LOCAL aInst AS STRING[]

aInst := SQL_L6_funcs.FindServerInstances()

SELF:cbxMSSS_Instances:DataContext := aInst

SELF:Cursor := Cursors.Arrow

SELF:lbInstances:Content := "Server Instances : ["+aInst:Length:ToString()+"]"

RETURN

METHOD DoSelectInstance() AS VOID

SQL_L6_funcs.oSCSB:DataSource := SELF:WinUser+SELF:cbxMSSS_Instances:SelectedItem:ToString()

SQL_L6_funcs.MyMessages(SQL_L6_funcs.oSCSB:DataSource,"Data Source :")

SELF:txblkConnectionString:DataContext := SQL_L6_funcs.oSCSB:ConnectionString

RETURN

METHOD ClearInstanceList() AS VOID

LOCAL IMPLIED aInst := STRING[]{0}

SELF:cbxMSSS_Instances:DataContext := aInst

RETURN

//----- Databases -----

METHOD DoFindDatabases2() AS VOID

SELF:ClearTableList()

//-------------------

SELF:oDTdbs := SQL_L6_funcs.FindDatabases()

SELF:dgReturn:DataContext := SELF:oDTdbs

SQL_L6_funcs.MyMessages(SELF:oDTdbs:Rows:Count:ToString(),"Row Count :")

TRY

SELF:FillDBdropList(SELF:oDTdbs)

SELF:lbDatabases:Content := "Databases : ["+SELF:oDTdbs:Rows:Count:ToString()+"]"

CATCH oEX AS SystemException

MessageBox.Show(oEX:Message,"my Error Fill DList :")

END TRY

RETURN

METHOD DoFindDatabases() AS VOID

SELF:ClearTableList()

//-------------------

//SELF:oDTdbs := SQL_L6_funcs.FindDatabases2()

SELF:oDTdbs := SQL_L6_funcs.FindDatabases()

SELF:dgReturn:DataContext := SELF:oDTdbs

SQL_L6_funcs.MyMessages(SELF:oDTdbs:Rows:Count:ToString(),"Row Count :")

TRY

SELF:FillDBdropList(SELF:oDTdbs)

SELF:lbDatabases:Content := "Databases : ["+SELF:oDTdbs:Rows:Count:ToString()+"]"

CATCH oEX AS SystemException

MessageBox.Show(oEX:Message,"my Error Fill DList :")

END TRY

RETURN

METHOD FillDBdropList(oDT AS DataTable) AS VOID

//C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL_DEV_08_R2\MSSQL\DATA

//C:\Users\BEST

LOCAL nCount AS INT

IF !oDT = NULL_OBJECT

nCount := oDT:Rows:Count

ELSE

nCount := 0

ENDIF

SQL_L6_funcs.MyMessages(nCount:ToString(),"DBs :")

//--------------------------------------------------

LOCAL IMPLIED aDatabases := STRING[]{nCount}

LOCAL IMPLIED cText := ""

IF nCount > 0

FOR LOCAL IMPLIED i := 0 UPTO nCount-1

cText := oDT:Rows[i]:Item[0]:ToString():Trim()

IF !STRING.IsNullOrEmpty(cText)

aDatabases[i+1] := cText

ELSE

aDatabases[i+1] := "..n/a.."

ENDIF

NEXT

ENDIF

TRY

IF aDatabases = NULL_OBJECT

MessageBox.Show("aDatabases issue !? .... ","my null_object error :")

ENDIF

SQL_L6_funcs.MyMessages(aDatabases:Length:ToString(),"Count DBs :")

SELF:cbxDatabases:DataContext := aDatabases

CATCH oEX AS SystemException

MessageBox.Show(oEX:Message,"my Error Fill DB :")

SELF:cbxDatabases:DataContext := STRING[]{0}

END TRY

RETURN

METHOD DoSelectDB() AS VOID

LOCAL IMPLIED cName := SELF:cbxDatabases:SelectedItem:ToString()

LOCAL IMPLIED nCount := SELF:oDTdbs:Rows:Count

LOCAL cFilePath AS STRING

FOR LOCAL i := 0 AS INT UPTO nCount-1

IF SELF:oDTdbs:Rows[i]:Item[0] = cName

cFilePath := (STRING)SELF:oDTdbs:Rows[i]:Item[1]

//cFilePath := (STRING)SELF:oDTdbs:Rows[i]:Item[0]

EXIT

ENDIF

NEXT

SQL_L6_funcs.oSCSB:AttachDBFilename := cFilePath

SQL_L6_funcs.MyMessages(SQL_L6_funcs.oSCSB:AttachDBFilename,"Database :")

SELF:txblkConnectionString:DataContext := SQL_L6_funcs.oSCSB:ConnectionString

RETURN

METHOD ClearDatabaseList() AS VOID

SELF:dgReturn:DataContext := DataTable{}

SELF:FillDBdropList(DataTable{})

SELF:lbDatabases:Content := "Databases : [0]"

RETURN

//----- Tables -----

METHOD DoFindTables() AS VOID

LOCAL oDT AS DataTable

local oDS as DataSet

LOCAL IMPLIED cScript := ""

cScript := "SELECT TABLE_NAME, OBJECTPROPERTY(object_id(TABLE_NAME),"

cScript += " N'IsUserTable') AS type FROM "

cScript += SQL_L6_funcs.Attach+".INFORMATION_SCHEMA.TABLES"

cScript += " where OBJECTPROPERTY(object_id(TABLE_NAME),N'IsUserTable') = 1"

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript, "My Tables")

oDT := oDS:Tables["My Tables"]

TRY

SQL_L6_funcs.MyMessages(oDT:Rows:Count:ToString(),"Table Rows :")

SELF:FillTablesDropList(oDT)

CATCH oEX AS SystemException

MessageBox.Show(oEX:Message,"My Tables Error :")

END TRY

RETURN

METHOD FillTablesDropList(oDT as DataTable) AS VOID

LOCAL nCount AS INT

nCount := oDT:Rows:Count

SQL_L6_funcs.MyMessages(nCount:ToString(),"Tables :")

LOCAL IMPLIED aTables := STRING[]{nCount}

FOR LOCAL IMPLIED i := 0 UPTO nCount-1

LOCAL IMPLIED cText := ""

cText := oDT:Rows[i]:Item[0]:ToString():Trim()

IF !STRING.IsNullOrEmpty(cText)

aTables[i+1] := cText

ELSE

aTables[i+1] := "..n/a.."

ENDIF

NEXT

TRY

SQL_L6_funcs.MyMessages(aTables:Length:ToString(),"Count Tables :")

SELF:cbxTables:DataContext := aTables

self:lbTables:Content := "Tables : ["+oDT:Rows:Count:ToString()+"]"

CATCH oEX AS SystemException

MessageBox.Show(oEX:Message,"my Error Fill Tables :")

END TRY

RETURN

METHOD DoSelectTable() AS VOID

SELF:SelectedTable := (STRING)SELF:cbxTables:SelectedItem

//MessageBox.Show(SELF:SelectedTable,"my Table :")

//SQL_L6_funcs.MyMessages(SELF:SelectedTable,"my Table :")

RETURN

METHOD ClearTableList() AS VOID

SELF:FillTablesDropList(DataTable{})

SELF:lbTables:Content := "Tables : [0]"

RETURN

//----- Columns -----

METHOD DoFindColumns() AS VOID

LOCAL cScript AS STRING

cScript := "SELECT sysobjects.name, column_name=syscolumns.name, "

cScript += "datatype=systypes.name, length=syscolumns.length "

cScript += " FROM sysobjects "

cScript += " JOIN syscolumns "

cScript += " ON sysobjects.id = syscolumns.id "

cScript += " JOIN systypes "

cScript += " ON syscolumns.xtype=systypes.xtype "

cScript += " WHERE sysobjects.xtype='U'and sysobjects.name='"+SELF:SelectedTable +"'"

cScript += " ORDER BY sysobjects.name,syscolumns.colid "

LOCAL oDT AS DataTable

LOCAL oDS AS DataSet

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript,"my_Columns")

oDT := oDS:Tables[0]

SELF:dgReturn:DataContext := oDT

RETURN

METHOD DoChangeMessageStatus() AS VOID

IF (Boolean)SELF:chbxDisplayMessages:IsChecked

SQL_L6_funcs.ShowMessages := TRUE

ELSE

SQL_L6_funcs.ShowMessages := FALSE

ENDIF

RETURN

//----------------------------------------

END CLASS

END NAMESPACE

========================================================================================

//----- SQL_L6_funcs.prg -----

//----- created August 2nd. 2011 for VODC in November -----

//----- for "Small Trader" app. - by PHH -----

#using System

#USING System.IO

#using System.Data

#using System.Xml.Serialization

#using System.Data.Common

#using System.Collections.Generic

#using System.Collections.ObjectModel

#using System.Collections

#using System.Windows

//==================================

#USING System.IO

#using System.Data

#using System.Data.SqlTypes

#using System.Collections.ObjectModel

#using Microsoft.SqlServer.Server

#using System.Data.Sql

#using System.Text.RegularExpressions

#using System.Data.SqlClient

//------------------------------------

BEGIN NAMESPACE TestBedSQL

STATIC CLASS SQL_L6_funcs

#region {.. class variables ..}

STATIC PUBLIC lConnectionTestedOK AS Boolean

STATIC PUBLIC oSCSB AS SqlConnectionStringBuilder

STATIC PRIVATE cFileXML AS STRING

STATIC PRIVATE cAppDir AS STRING

STATIC PRIVATE cWork AS STRING

#endregion

#region {.. other variables of the class ..}

//----------------------------------------------

STATIC PUBLIC oConnDetails AS ConnectionDetails

STATIC PRIVATE oDFactory AS DbProviderFactory

STATIC PRIVATE oDConn AS DbConnection

STATIC PRIVATE cStrConn AS STRING

//----------------------------------------------

#endregion

#region {.. more class variables ..}

STATIC PRIVATE cSource AS STRING

STATIC PRIVATE cAttach AS STRING

STATIC PRIVATE cSecurity AS STRING

STATIC PRIVATE cTime AS STRING

STATIC PRIVATE cInst AS STRING

STATIC PRIVATE cDBfilePath AS STRING

STATIC PRIVATE lConnected AS Boolean

#endregion

#region {.. and more ..}

//----------------------------------------------

STATIC PRIVATE nErrorCode AS INT

STATIC PRIVATE cErrorMessage AS STRING

STATIC PRIVATE cErrorLanguage AS STRING

#endregion

STATIC CONSTRUCTOR()

nErrorCode := 0

cErrorMessage := "No Error"

cErrorLanguage := "1033"

//-----------------------

SQL_L6_funcs.SetDefaultConnectionString()

MessageBox.Show(SQL_L6_funcs.ReadConnectionStringSB(),"Builder String :")

lConnected := false

ShowMessages := false //----- this will be the default setting ! -----

ShowMessages := true

//-------------------------------------------------------

cAppDir := System.IO.Directory.GetCurrentDirectory() +"\"

cWork := System.IO.Directory.GetCurrentDirectory()

cFileXML := cAppDir +"ConnectionDetails.xml"

SQL_L6_funcs.ReadSerialXmlFile()

/* cSource := "Data Source="+oConnDetails:cServerName+";"

//cSource := "Data Source="+Source+";"

cAttach := "AttachDbFilename="+oConnDetails:cFilePath+";;"

//cAttach := "AttachDbFilename="+Attach+";;" */

RETURN

//========================= [ Backup Database ] ===========================

STATIC METHOD DoBasicBackup(cFilepath AS STRING) AS Boolean

LOCAL IMPLIED lResult := TRUE

LOCAL oF AS DirectoryInfo

LOCAL IMPLIED cPath := "C:\BackupSQL13"

oF := Directory.CreateDirectory(cPath)

IF Directory.Exists(cPath)

MessageBox.Show("Yes, now exists as folder ...","Folder News :")

ELSE

MessageBox.Show("No, folder has NOT been made ...","Folder News :")

RETURN FALSE

ENDIF

//=====================================================================

LOCAL IMPLIED cName2Use := oF:FullName+"\StaticBasic.bak"

MessageBox.Show(cName2Use,"Will be Used :")

LOCAL cScript AS STRING

cScript := "BACKUP DATABASE [" + cFilepath + "] "

cScript += "TO DISK = '"+cName2Use+"' "

cScript += "WITH FORMAT, "

cScript += "NAME = 'Full Backup of SmallTrader' "

MessageBox.Show(cScript, "Back-Up :")

//-----------------------------------------------------------

TRY

SQL_L6_funcs.KeepOpenSendNonQueryScript(cScript)

MessageBox.Show("Query has been sent .... ","After BU :")

CATCH oEx AS SystemException

MessageBox.Show(oEx:Message, "ERROR :")

END TRY

//-----------------------------------------------------------

LOCAL nError AS INT

nError := SQL_L6_funcs.GetErrorCodeFromServer()

IF nError = 0

MessageBox.Show("No apparent error","Error ?")

ELSE

MessageBox.Show("Error number :- "+nError:ToString(),"Error ?")

ENDIF

MyMessages("Just before force disconnect ....","TEST :")

//MessageBox.Show("Just before force disconnect ....","TEST :")

SQL_L6_funcs.DoForceDisconnect()

RETURN lResult

STATIC METHOD DoBasicBackup() AS Boolean

LOCAL IMPLIED lResult := TRUE

LOCAL oF AS DirectoryInfo

LOCAL IMPLIED cPath := "C:\BackupSQL13"

oF := Directory.CreateDirectory(cPath)

IF Directory.Exists(cPath)

MessageBox.Show("Yes, now exists as folder ...","Folder News :")

ELSE

MessageBox.Show("No, folder has NOT been made ...","Folder News :")

RETURN FALSE

ENDIF

//=====================================================================

LOCAL IMPLIED cName2Use := oF:FullName+"\StaticBasic.bak"

MessageBox.Show(cName2Use,"Will be Used :")

LOCAL cScript AS STRING

cScript := "BACKUP DATABASE [" + SQL_L6_funcs.DBfilePath + "] "

cScript += "TO DISK = '"+cName2Use+"' "

cScript += "WITH FORMAT, "

cScript += "NAME = 'Full Backup of SmallTrader' "

MessageBox.Show(cScript, "Back-Up :")

//-----------------------------------------------------------

TRY

SQL_L6_funcs.KeepOpenSendNonQueryScript(cScript)

MessageBox.Show("Query has been sent .... ","After BU :")

CATCH oEx AS SystemException

MessageBox.Show(oEx:Message, "ERROR :")

END TRY

//-----------------------------------------------------------

LOCAL nError AS INT

nError := SQL_L6_funcs.GetErrorCodeFromServer()

IF nError = 0

MessageBox.Show("No apparent error","Error ?")

ELSE

MessageBox.Show("Error number :- "+nError:ToString(),"Error ?")

ENDIF

MyMessages("Just before force disconnect ....","TEST :")

//MessageBox.Show("Just before force disconnect ....","TEST :")

SQL_L6_funcs.DoForceDisconnect()

RETURN lResult

//========================= [ End Backup] ===========================

//========================= [ Error Object ] ==============================

STATIC METHOD SaveErrorToSQLtable(oE AS MyError) AS VOID

SQL_L6_funcs.ChangeErrorRecord(oE, RowChangeModeSQL.Insert)

return

STATIC METHOD Error_FillTypedList(oE AS MyError) AS List<SQLfieldSpec>

LOCAL IMPLIED aSFS := List<SQLfieldSpec>{}

aSFS:Add(SQLfieldSpec{"[UnRef]", "@ur", DbType.Int32, oE:UnRef})

aSFS:Add(SQLfieldSpec{"[Occurred]", "@occ", DbType.DateTime, oE:Occurred})

aSFS:Add(SQLfieldSpec{"[Method_Name]", "@mn", DbType.String, oE:MethodName})

aSFS:Add(SQLfieldSpec{"[Object_Class]", "@obj", DbType.String, oE:ObjectClass})

aSFS:Add(SQLfieldSpec{"[Namespace]", "@nsp", DbType.String, oE:Namespace})

aSFS:Add(SQLfieldSpec{"[Operation]", "@op", DbType.String, oE:Operation})

aSFS:Add(SQLfieldSpec{"[Main_Message]", "@mm", DbType.String, oE:MainMessage})

aSFS:Add(SQLfieldSpec{"[Inner_Message]", "@im", DbType.String, oE:InnerMessage})

aSFS:Add(SQLfieldSpec{"[SQL_Error_Text]", "@set", DbType.String, oE:SQLerrorText})

aSFS:Add(SQLfieldSpec{"[SQL_Error_Code]", "@sec", DbType.Int32, oE:SQLerrorCode})

RETURN aSFS

STATIC METHOD ChangeErrorRecord(oE AS MyError, eMode AS RowChangeModeSQL) AS Boolean

LOCAL IMPLIED lResult := FALSE

IF eMode = RowChangeModeSQL.Delete

MessageBox.Show("Wrong mode requested >> DELETE !?","Change MODE :")

RETURN lResult

ENDIF

LOCAL IMPLIED cTable := "[MyErrors]" //----- change for other Tables -----

//==========================================================================

LOCAL aSFS AS List<SQLfieldSpec>

aSFS := SQL_L6_funcs.Error_FillTypedList(oE)

//--------------------------------------------

//MessageBox.Show("Just about to try the STATIC func!","Testing :")

lResult := SQL_L6_funcs.MakeBusinessObjectDetail(aSFS, cTable, eMode)

//MessageBox.Show("Just after MBO - try the STATIC func!"+lResult:ToString(),"Testing :")

RETURN lResult

//========================= [ End Error Object ] ===========================================

//========================= [ Message for Exceptions ] =============================

STATIC METHOD MyInnerMessage(oEX AS Exception) AS STRING

LOCAL IMPLIED cText := ""

IF oEX:InnerException <> NULL

cText := oEX:InnerException:Message+CRLF

cText += "Exception Type : "+oEX:InnerException:GetType():Fullname+CRLF

cText += "Source : "+oEX:InnerException:Source+CRLF

cText += oEX:InnerException:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oEX:InnerException:TargetSite:ToString()+CRLF

ENDIF

//MessageBox.Show(cText)

RETURN cText

STATIC METHOD MyMainMessage(oEX AS Exception) AS STRING

LOCAL cText AS STRING

cText := oEX:Message+CRLF

cText += "Exception Type : "+oEX:GetType():Fullname+CRLF

cText += "Source : "+oEX:Source+CRLF

cText += oEX:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oEX:TargetSite:ToString()+CRLF

//MessageBox.Show(cText)

RETURN cText

//------------------- And Another Overload --------------------

STATIC METHOD MyInnerMessage(oDBX AS DbException) AS STRING

LOCAL IMPLIED cText := ""

IF oDBX:InnerException <> NULL

cText := oDBX:InnerException:Message+CRLF

cText += "Exception Type : "+oDBX:InnerException:GetType():Fullname+CRLF

cText += "Source : "+oDBX:InnerException:Source+CRLF

cText += oDBX:InnerException:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oDBX:InnerException:TargetSite:ToString()+CRLF

ENDIF

//MessageBox.Show(cText)

RETURN cText

STATIC METHOD MyMainMessage(oDBX AS DbException) AS STRING

local cText as String

cText := oDBX:Message+CRLF

cText += "Exception Type : "+oDBX:GetType():Fullname+CRLF

cText += "Source : "+oDBX:Source+CRLF

cText += oDBX:StackTrace:ToString()+CRLF

cText += "Targert Site : "+oDBX:TargetSite:ToString()+CRLF

//MessageBox.Show(cText)

return cText

//--------------------------------------------------------------

//========================= [ End Exception Messages ] =============================

STATIC METHOD FilterCondionDelete(aSFS as List<SQLfieldSpec>) AS STRING

LOCAL IMPLIED cResult := ""

LOCAL IMPLIED cFieldname := aSFS[0]:Fieldname

LOCAL IMPLIED nValue := (INT)aSFS[0]:Val

cResult := " WHERE "+cFieldname+" = "+nValue:ToString()+" ;"

MessageBox.Show(cResult,"New Filter :")

RETURN cResult

STATIC METHOD FilterCondionUpdate(aSFS as List<SQLfieldSpec>) AS STRING

LOCAL IMPLIED cResult := ""

LOCAL implied cFieldname := aSFS[0]:Fieldname

LOCAL IMPLIED cSymbol := aSFS[0]:@@Symbol

cResult := " WHERE "+cFieldname+" = "+cSymbol+" ;"

MessageBox.Show(cResult,"New Filter :")

RETURN cResult

STATIC METHOD MakeBusinessObjectDetail(aSFS AS List<SQLfieldSpec>, cTable AS STRING, eMode AS RowChangeModeSQL) AS Boolean

LOCAL IMPLIED lResult := FALSE

LOCAL cText, cHeading AS STRING

LOCAL cFields AS STRING

LOCAL cSymbols AS STRING

IF eMode = RowChangeModeSQL.Update .or. eMode = RowChangeModeSQL.UpdatePKI

LOCAL IMPLIED cFieldSymbolPairs := ""

SQL_L6_funcs.CreateFieldSymbolPairs(cFieldSymbolPairs,aSFS)

//----- use first pair as filter values -----

cText := "UPDATE "+cTable+" SET "+cFieldSymbolPairs+" "+FilterCondionUpdate(aSFS)

//MessageBox.Show(cText,"Update String :")

cHeading := "Updating :"

lResult := SQL_L6_funcs.MakeBusinessObjectChange(aSFS, cText, cHeading)

RETURN lResult

ENDIF

IF eMode = RowChangeModeSQL.Insert

cFields := ""

cSymbols := ""

SQL_L6_funcs.CreateFieldsSymbols(cFields,cSymbols,aSFS,FALSE)

cText := "INSERT INTO "+cTable+" ("+cFields+") VALUES ("+cSymbols+");"

cHeading := "Inserting :"

lResult := SQL_L6_funcs.MakeBusinessObjectChange(aSFS, cText, cHeading)

RETURN lResult

ENDIF

IF eMode = RowChangeModeSQL.InsertPKI //----- 'skip' first ID field! -----

cFields := ""

cSymbols := ""

SQL_L6_funcs.CreateFieldsSymbols(cFields,cSymbols,aSFS,TRUE)

cText := "INSERT INTO "+cTable+" ("+cFields+") VALUES ("+cSymbols+");"

cHeading := "Inserting :"

lResult := SQL_L6_funcs.MakeBusinessObjectChange(aSFS, cText, cHeading)

RETURN lResult

ENDIF

//--------------------------------------------------------------------------------

IF eMode = RowChangeModeSQL.Delete .or. eMode = RowChangeModeSQL.DeletePKI

cText := "DELETE "

cText += "FROM "+cTable+" "

cText += " "+FilterCondionDelete(aSFS)

SQL_L6_funcs.SendNonQueryScript(cText)

ENDIF

//MessageBox.Show(cText,"Script :")

RETURN lResult

STATIC METHOD MakeBusinessObjectChange(aSFS AS List<SQLfieldSpec>, cText AS STRING, cHeading AS STRING) AS Boolean

LOCAL IMPLIED lResult := FALSE

//====================================================

SQL_L6_funcs.DoSetConnectionString()

IF !SQL_L6_funcs.DoTryToConnect()

RETURN lResult

ENDIF

//==========================

LOCAL oCommand AS DbCommand

oCommand := SQL_L6_funcs.DFactory:CreateCommand()

oCommand:CommandText := cText

oCommand:CommandType := CommandType.Text

oCommand:Connection := SQL_L6_funcs.DConn

//----------------------------------------------------

SQL_L6_funcs.AllParamsSetAdd(oCommand, aSFS)

TRY //----- execute the insert statement -----

oCommand:ExecuteNonQuery()

lResult := TRUE

CATCH DbEx AS DbException

MessageBox.Show(DbEx:Message, cHeading)

END TRY

SQL_L6_funcs.DoForceDisconnect()

RETURN lResult

STATIC METHOD CreateFieldSymbolPairs(cFieldSymbolPairs REF STRING, aSFS AS List<SQLfieldSpec>) AS VOID

//----- used for UPDATE modes -----

LOCAL IMPLIED oLTE := aSFS:GetEnumerator()

LOCAL oSFS AS SQLfieldSpec

LOCAL i := 0 AS INT

oLTE:MoveNext() //---- needed to 'skip' the ID field ! -----

DO WHILE oLTE:MoveNext()

i +=1

IF i > 1

cFieldSymbolPairs += ","

ENDIF

oSFS := (SQLfieldSpec)oLTE:Current

cFieldSymbolPairs += oSFS:FieldName+"="+oSFS:@@Symbol

ENDDO

RETURN

STATIC METHOD CreateFieldsSymbols(cFields REF STRING, cSymbols REF STRING, aSFS AS List<SQLfieldSpec>, lSkipID AS Boolean) AS VOID

//----- used for INSERT modes -----

LOCAL i := 0 AS INT

LOCAL IMPLIED oLTE := aSFS:GetEnumerator()

LOCAL oSFS AS SQLfieldSpec

IF lSkipID

oLTE:MoveNext()

ENDIF

DO WHILE oLTE:MoveNext()

i +=1

IF i > 1

cFields += ","

cSymbols += ","

ENDIF

oSFS := (SQLfieldSpec)oLTE:Current

cFields += oSFS:FieldName

cSymbols += oSFS:@@Symbol

ENDDO

RETURN

STATIC METHOD SetAddParam(oCommand AS DbCommand , oDbT AS DbType, cSym AS STRING, oV AS OBJECT) AS VOID

LOCAL oParam AS DbParameter

oParam := SQL_L6_funcs.DFactory:CreateParameter()

oParam:ParameterName := cSym

oParam:DbType := oDbT

oParam:Value := oV

oCommand:Parameters:Add(oParam)

RETURN

STATIC METHOD AllParamsSetAdd(oCommand AS DbCommand, aSFS AS List<SQLfieldSpec>) AS VOID

LOCAL IMPLIED oSFS := SQLfieldSpec{}

//LOCAL IMPLIED oLTE := aSFS:GetEnumerator()

LOCAL oLTE AS IEnumerator

oLTE := aSFS:GetEnumerator()

oLTE:Reset()

DO WHILE oLTE:MoveNext()

oSFS := (SQLfieldSpec)oLTE:Current

SQL_L6_funcs.SetAddParam(oCommand, oSFS:DbT, oSFS:@@Symbol, oSFS:Val)

ENDDO

RETURN

STATIC METHOD ReadSerialXmlFile() AS LOGIC

local serial as XmlSerializer

local oSR as StreamReader

local cText, cHead as string

LOCAL IMPLIED lResult := FALSE

lConnectionTestedOK := FALSE

IF File.Exists(cFileXML)

cText := "Existing file will be read!"

cHead := "XML file :"

//----- use my STATIC toggled message system -----

MyMessages(cText,cHead)

//----- read object from XML file -----

serial := XmlSerializer{typeof(ConnectionDetails)}

oSR := StreamReader{cFileXML}

oConnDetails := (ConnectionDetails)serial:Deserialize(oSR)

oSR:Close()

//-------------------------------------

lResult := TRUE

lConnectionTestedOK := TRUE

//----- use my STATIC toggled message system -----

MyMessages(oConnDetails:ToString(),cHead)

ELSE

cHead := "XML file :"

cText := "File can't be found !?"+CRLF

cText += "Go to Connection and create a string."+CRLF

MessageBox.Show(cText,cHead)

ENDIF

RETURN lResult

STATIC METHOD SerializationToFile() AS VOID

local cText, cHead as string

IF oConnDetails = NULL_OBJECT

cText := "NULL object ! - ConnectionDetails ..."

cHead := "My Error :"

//----- use my STATIC toggled message system -----

MyMessages(cText,cHead)

RETURN

ENDIF

LOCAL serial AS XmlSerializer

LOCAL oSW AS StreamWriter

IF File.Exists(cFileXML)

cText := "File exists already!"

cHead := "XML file :"

MessageBox.Show(cText,cHead)

ENDIF

//----- save object to XML file -----

serial := XmlSerializer{typeof(ConnectionDetails)}

oSW := StreamWriter{cFileXML}

serial:Serialize(oSW, oConnDetails)

oSW:Close()

RETURN

STATIC METHOD SerializeOthersToDisk() AS VOID

/*local cFile as string

cFile := "C:\Temp\customers.xml"

local oSW as StreamWriter

oSW := StreamWriter{cFile}

//----- Save object to XML file -----

local serial as XmlSerializer

local aTypes as Type[]

aTypes := Type[]{1}

aTypes[1] := typeof(Customer)

serial := XmlSerializer{typeof(ArrayList),aTypes}

serial:Serialize(oSW,self:alCustomers)

oSW:Close()*/

RETURN

STATIC METHOD MyMessages(cB AS STRING, cH AS STRING) AS VOID

if ShowMessages

MessageBox.Show(cB,cH)

endif

return

//======================== [ basic ADO.net & settings ] ==============================================

STATIC METHOD SetDefaultConnectionString() AS VOID

oSCSB := SqlConnectionStringBuilder{}

oSCSB:DataSource := "BEST-V64QUAD\SQL_DEV_08_R2"

//oSCSB:AttachDBFilename := "C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL_DEV_08_R2\MSSQL\DATA\TestBed.mdf"

//oSCSB["Database"] := "C:\Users\BEST\SmallTrader.mdf"

//oSCSB["Database"] := "C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL_DEV_08_R2\MSSQL\DATA\TestBed.mdf"

oSCSB:IntegratedSecurity := TRUE

oSCSB:ConnectTimeout := 0 //----Willie Moore says that this will hold indefinitely ----

MyMessages(oSCSB:ConnectionString, "String Builder :")

//MessageBox.Show(oSCSB:InitialCatalog:ToString(),"My Catalog")

RETURN

STATIC METHOD ReadConnectionStringSB() AS STRING

RETURN oSCSB:ConnectionString

STATIC METHOD DoSetConnectionStringOld() AS VOID

IF STRING.IsNullOrEmpty(oConnDetails:cProvider)

MessageBox.Show("Provider string NOT available !","WARNING")

RETURN

ENDIF

//====================================================================

IF oConnDetails:cUserName:IndexOf("<") > -1 .OR. oConnDetails:cUserName:IndexOf(">") > -1

oConnDetails:cUserName := ""

ENDIF

IF oConnDetails:cPassWord:IndexOf("<") > -1 .OR. oConnDetails:cPassWord:IndexOf(">") > -1

oConnDetails:cPassWord := ""

ENDIF

//----- get the factory for the provider we require -----

oDFactory := DbProviderFactories.GetFactory("System.Data."+oConnDetails:cProvider)

//----- create a connection STRING ------

oDConn := oDFactory:CreateConnection()

//====================================================

cStrConn := ""

cSource := "Data Source="+oConnDetails:cServerName+";"

//cSource := "Data Source="+Source+";"

cAttach := "AttachDbFilename="+oConnDetails:cFilePath+";;"

//cAttach := "AttachDbFilename="+Attach+";;"

//--------------------------------------------------------------

cDBfilePath := oConnDetails:cFilePath //----- for backup ! -----

//--------------------------------------------------------------

cSecurity := "Integrated Security="+oConnDetails:cSecurity+";"

cTime := "Connect Timeout="+oConnDetails:nTimeOut:ToString()+";"

//----- Willie said this would leave it always open if ZERO is supplied! -----

cInst := ""

cStrConn := cSource + cAttach + cSecurity + cTime +cInst

oDConn:ConnectionString := cStrConn

oConnDetails:cConnectionString := cStrConn

MyMessages(cStrConn,"L6, Connect String :")

RETURN

STATIC METHOD DoSetConnectionString() AS VOID

IF STRING.IsNullOrEmpty(oConnDetails:cProvider)

MessageBox.Show("Provider string NOT available !","WARNING")

RETURN

ENDIF

//====================================================================

/*IF oConnDetails:cUserName:IndexOf("<") > -1 .OR. oConnDetails:cUserName:IndexOf(">") > -1

oConnDetails:cUserName := ""

ENDIF

IF oConnDetails:cPassWord:IndexOf("<") > -1 .OR. oConnDetails:cPassWord:IndexOf(">") > -1

oConnDetails:cPassWord := ""

ENDIF*/

//----- get the factory for the provider we require -----

oDFactory := DbProviderFactories.GetFactory("System.Data."+oConnDetails:cProvider)

//----- create a connection STRING ------

oDConn := oDFactory:CreateConnection()

//====================================================

cStrConn := SQL_L6_funcs.ReadConnectionStringSB()

oDConn:ConnectionString := cStrConn

oConnDetails:cConnectionString := cStrConn

MyMessages(cStrConn,"L6, Connect String :")

RETURN

static METHOD DoTryToConnect() AS LOGIC

LOCAL IMPLIED lResult := FALSE

lConnected := false

LOCAL IMPLIED cText := ""

TRY

oDConn:Open()

cText := "Vulcan.NET : Layer 6 STATIC class: << Connected! >>"

MyMessages(cText,"L6, try to Connect :")

lResult := TRUE

lConnected := TRUE

CATCH DbEx AS DbException

MessageBox.Show(DbEx:Message,"my Error - Connect? :")

END TRY

RETURN lResult

static METHOD DoForceDisconnect() AS VOID

LOCAL cText AS STRING

IF ! oDConn = NULL_OBJECT

oDConn:Close()

cText := oDConn:ConnectionString+" - State = "+ oDConn:State:ToString()

MyMessages(cText,"Disconnecting? :")

lConnected := false

else

cText := "Vulcan.NET : Layer 6 STATIC class : << Disconnected! >>"

MyMessages(cText,"Disconnecting? :")

ENDIF

RETURN

STATIC METHOD FindServerInstances() AS STRING[]

LOCAL oInst AS SqlDataSourceEnumerator

oInst := SqlDataSourceEnumerator.Instance

LOCAL oDT AS DataTable

oDT := oInst:GetDataSources()

LOCAL nCount AS INT

nCount := oDT:Rows:Count

//MessageBox.Show(nCount:ToString(),"Servers :")

LOCAL IMPLIED aSQLinstances := STRING[]{nCount}

FOR LOCAL IMPLIED i := 0 UPTO nCount-1

LOCAL IMPLIED cText := ""

cText := oDT:Rows[i]:Item[1]:ToString():Trim()

IF !STRING.IsNullOrEmpty(cText)

aSQLinstances[i+1] := cText

ENDIF

NEXT

RETURN aSQLinstances

STATIC METHOD FindDatabases2() AS DataTable

LOCAL IMPLIED cScript := "select name from sys.databases where create_date > '2011-01-01' and database_id > 2"

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript, "Test table")

oDT := oDS:Tables[0]

RETURN oDT

STATIC METHOD FindDatabases() AS DataTable

LOCAL IMPLIED cScript := "SELECT name, physical_name "

cScript += "FROM sys.master_files "

cScript += "where type_desc <> 'LOG' "

cScript += "and file_guid is not null "

cScript += "and name <> 'MSDBData' "

cScript += "and LEFT(name,13) <> 'ReportServer$' "

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript, "My DB table")

oDT := oDS:Tables[0]

RETURN oDT

STATIC METHOD FindTables(cDBname AS STRING) AS DataTable

LOCAL IMPLIED cScript := ""

cScript := "SELECT TABLE_NAME, OBJECTPROPERTY(object_id(TABLE_NAME),"

cScript += " N'IsUserTable') AS type FROM "

cScript += cDBname+".INFORMATION_SCHEMA.TABLES"

cScript += " where OBJECTPROPERTY(object_id(TABLE_NAME),N'IsUserTable') = 1"

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript, "My Tables table")

oDT := oDS:Tables["My Tables table"]

RETURN oDT

STATIC METHOD DisplayConnectForm() AS VOID

LOCAL oMyConnForm AS ConnectLayer6

oMyConnForm := ConnectLayer6{}

oMyConnForm:ShowDialog()

RETURN

//========================= [ query ADO.net members ] ================================================

static method KeepOpenSendScalarQueryScript(cScript as string) as Object

LOCAL IMPLIED oResult := OBJECT{}

LOCAL oDComm AS DbCommand

TRY

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH oEx AS SystemException

MessageBox.Show(oEx:message,"my Error (KO) Scalar Query1 :")

END TRY

//----- getting a scalar quanity returned -----

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oResult := oDComm:ExecuteScalar()

//----- need to cast in calling code section! -----

CATCH DbEx AS DbException

//SaveAndDisplayErrorDetails(DbEx,"Keep, Returned Data Object :")

MessageBox.Show(DbEx:Message,"Keep, Returned Data Object :")

END TRY

return oResult

static method KeepOpenSendNonQueryScript(cScript as string) as Boolean

LOCAL IMPLIED lResult := FALSE

LOCAL oDComm AS DbCommand

TRY

SQL_L6_funcs.DoSetConnectionString()

SQL_L6_funcs.DoTryToConnect()

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"Start of Non Query1 :")

END TRY

//----- getting no data returned -----

oDComm:CommandType := CommandType.Text

TRY

LOCAL IMPLIED cStatement := cScript

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cStatement

oDComm:CommandType := CommandType.Text

//----- execute the supplied statement -----

oDComm:ExecuteNonQuery()

lResult := true

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Keep-Non Query2 :")

/* local cText := "" as string

cText += "Error Code :- "+DbEx:ErrorCode:ToString()+CRLF

if !DbEx:InnerException = NULL_object

cText += "Inner Exception :- "+DbEx:InnerException:ToString()+CRLF

endif

cText += "Source :- "+DbEx:Source:ToString()+CRLF

cText += "Message :- "+DbEx:Message+CRLF

//cText += "Stack Trace :- "+DbEx:StackTrace+CRLF

MessageBox.Show(cText,"DbEx Details :")*/

//MessageBox.Show(DbEx:Message,"Non Query :")

END TRY

return lResult

//------------------------------------------------------------------------------------------------

STATIC METHOD SendNonQueryScript(cScript AS STRING) AS Boolean

LOCAL nError := -999 AS INT

cErrorMessage := ".. reset for use .."

//------------------------------------

LOCAL IMPLIED lResult := FALSE

LOCAL oDComm AS DbCommand

TRY

SQL_L6_funcs.DoSetConnectionString()

SQL_L6_funcs.DoTryToConnect()

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"my Error Non Query1 :")

END TRY

//----- getting no data returned -----

TRY

LOCAL cStatement := cScript AS STRING

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cStatement

oDComm:CommandType := CommandType.Text

//----- execute the supplied statement -----

oDComm:ExecuteNonQuery()

nError := 0

lResult := TRUE

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Non Query2 :")

/* nError := GetErrorCodeFromServer()

nErrorCode := nError

//FindErrorMessage()

cErrorMessage := DbEx:Message

MessageBox.Show(nError:ToString(),"Code ?! :")

MessageBox.Show(DbEx:Message,"Non Query :") */

END TRY

SQL_L6_funcs.DoForceDisconnect()

RETURN lResult

static method SendScalarQueryScript(cScript as string) as Object

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

LOCAL IMPLIED oResult := OBJECT{}

LOCAL oDComm AS DbCommand

try //----- to Open a connection & make a command object -----

SQL_L6_funcs.DoSetConnectionString()

SQL_L6_funcs.DoTryToConnect()

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

catch oEx as SystemException

MessageBox.Show(oEx:message,"Start of Scalar Query !")

end try

//----- now try getting a scalar quanity returned -----

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oResult := oDComm:ExecuteScalar()

//----- need to cast in calling code section! -----

nError := 0

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Scalar Retnd. Data Obj :")

END TRY

SQL_L6_funcs.DoForceDisconnect()

return oResult

static method SendDataSetQueryScript(cScript as string, cTableTitle as string) as DataSet

nErrorCode:= -999

cErrorMessage := ".. reset for use .."

//------------------------------------

LOCAL IMPLIED oDS_Result := DataSet{}

LOCAL oDComm AS DbCommand

TRY

SQL_L6_funcs.DoSetConnectionString()

SQL_L6_funcs.DoTryToConnect()

//----------------------------------

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"my Error DataSet1 :")

END TRY

//----- getting a data set returned! -----

oDComm:CommandType := CommandType.Text

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

LOCAL oDA AS DbDataAdapter

oDA := oDFactory:CreateDataAdapter()

oDA:SelectCommand := oDComm

oDS_Result := DataSet{}

//----- execute the select statement -----

oDA:Fill(oDS_Result, cTableTitle)

nErrorCode := 0

cErrorMessage := "No error thrown"

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Error DataSet2 :")

END TRY

SQL_L6_funcs.DoForceDisconnect()

return oDS_Result

static method SendReaderQueryScript(cScript as string) as DbDataReader

local nError := -999 as int

cErrorMessage := ".. reset for use .."

//------------------------------------

//----- using the DataReader approach as an alternative -----

//----- to the 'DataTable' one I like particularly -----

LOCAL oDr AS DbDataReader

LOCAL oDComm AS DbCommand

TRY

SQL_L6_funcs.DoSetConnectionString()

SQL_L6_funcs.DoTryToConnect()

//----------------------------------

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

CATCH DbEx AS DbException

MessageBox.Show(DbEx:message,"my Error DataReader1 :")

RETURN NULL_OBJECT

END TRY

//----- getting a data set returned -----

oDComm:CommandType := CommandType.Text

TRY

//----- use a terminator ; as recommended by Paul Nielsen -----

oDComm:CommandText := cScript

oDComm:CommandType := CommandType.Text

oDr := oDComm:ExecuteReader()

//----- execute the select statement -----

nError := 0

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"my Error Datareader2 :")

/*MessageBox.Show(DbEx:Message,"Error DataReader :")

nError := GetErrorCodeFromServer()

MessageBox.Show(nError:ToString(),"Code ?! :")

nErrorCode := nError*/

//FindErrorMessage()

END TRY

//----- do NOT close the connection - needed Open by DataReader ! -----

return oDr

static method SaveAndDisplayErrorDetails(DbEx AS DbException, cHead as string) as void

cErrorMessage := DbEx:Message //----- CARE with order, must be before next line -----

nErrorCode := GetErrorCodeFromServer()

IF !ShowMessages

LOCAL implied cText := ""

cText += ErrorMessage+CRLF

cText += ErrorCode:ToString()+CRLF

MessageBox.Show(cText, cHead)

ENDIF

#region {.. test code ..}

//---------------------------------------

//----- make oE from properties and fields -----

//as MyError, eMode as RowChangeModeSQL

/*local eMode := RowChangeModeSQL.Insert as RowChangeModeSQL

local oE := MyError{} as MyError*/

//ChangeErrorRecord(oE, eMode)

#endregion

return

//--------------------------------------------------------------------------------------------

static method GetErrorCodeFromServer() as int

local nResult := -999 as int

local cScript := "SELECT @@Error;" as string

nResult := (int)SQL_L6_funcs.KeepOpenSendScalarQueryScript(cScript)

if ShowMessages

if nResult = 0

MessageBox.Show("No apparent error ! ","Get Error Code :")

else

MessageBox.Show("Returned code is :- "+nResult:ToString(),"Error Code :")

endif

endif

RETURN nResult

STATIC METHOD GetAllRows(cTable AS STRING) AS DataTable

LOCAL cScript AS STRING

cScript := "select * FROM ["+cTable+"];"

MessageBox.Show(cScript,"My Script :")

LOCAL oDS AS DataSet

LOCAL oDT AS DataTable

TRY

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript,"All Rows"+cTable+" :")

oDT := oDS:Tables[0]

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error - All Rows :")

end try

RETURN oDT

STATIC METHOD Get_N_rows(cTable AS STRING, n as int) AS DataTable

LOCAL cScript AS STRING

cScript := "select top "+n:ToString()+" * FROM ["+cTable+"];"

MessageBox.Show(cScript,"My Script :")

LOCAL oDS as DataSet

LOCAL oDT AS DataTable

TRY

oDS := SQL_L6_funcs.SendDataSetQueryScript(cScript,"First 'N' Rows"+cTable+" :")

oDT := oDS:Tables[0]

catch oEX as SystemException

MessageBox.Show(oEX:Message+CRLF+cScript,"My Error - N Rows :")

end try

RETURN oDT

STATIC METHOD GetJohanDBs() AS DataTable

LOCAL nError := -999 AS INT

cErrorMessage := ".. reset for use .."

//------------------------------------

LOCAL oDComm AS DbCommand

LOCAL oDT AS DataTable

SQL_L6_funcs.DoSetConnectionString()

SQL_L6_funcs.DoTryToConnect()

oDComm := oDFactory:CreateCommand()

oDComm:Connection := oDConn

TRY

//oDT := oDComm:Connection:GetSchema("Databases")

oDT := oDComm:Connection:GetSchema()

nError := 0

CATCH DbEx AS DbException

SaveAndDisplayErrorDetails(DbEx,"Non Query2 :")

oDT := DataTable{}

END TRY

SQL_L6_funcs.DoForceDisconnect()

RETURN oDT

//================================================================================================

/*static method FindErrorMessage() as void

local cScript as string

cScript := "SELECT text FROM [master].[sys].[messages] where [message_id] = '"

cScript += ErrorCode:ToString() + "' and [language_id] = '"+cErrorLanguage+"' "

cErrorMessage := (string)SQL_L6_funcs.KeepOpenSendScalarQueryScript(cScript)

return*/

STATIC PROPERTY Source AS STRING

GET

RETURN cSource

END GET

/*SET

cSource := VALUE

END SET*/

END PROPERTY

//----------------------------------

STATIC PROPERTY Attach as string

GET

return cAttach

END GET

/*SET

cAttach := VALUE

END SET*/

END PROPERTY

//----------------------------------

STATIC PROPERTY StrConn AS STRING

GET

return cStrConn

END GET

END PROPERTY

//----------------------------------

STATIC PROPERTY DBfilePath as string

GET

return cDBfilePath

END GET

END PROPERTY

//----------------------------------

STATIC PROPERTY DFactory as DbProviderFactory

GET

return oDFactory

END GET

END PROPERTY

//----------------------------------

STATIC PROPERTY DConn as DbConnection

GET

return oDConn

END GET

END PROPERTY

//--------------------------------------

STATIC PROPERTY ShowMessages AS Boolean Auto

//--------------------------

STATIC PROPERTY ErrorCode AS INT

GET

return nErrorCode

END GET

END PROPERTY

//--------------------------------------

STATIC PROPERTY ErrorMessage AS string

GET

return cErrorMessage

END GET

END PROPERTY

//--------------------------------------

END CLASS //----- SQL_L6_funcs -----

//===================================================================================

class SQLfieldSpec

private cFieldName as string

private cSymbol as string

private oDbT as DbType

private oVal as Object

CONSTRUCTOR()

SUPER()

self:Initialize()

RETURN

CONSTRUCTOR(cF as string, cS as string, oD as DbType, oV as object)

SUPER()

self:Initialize(cF,cS,oD,oV)

RETURN

method Initialize() as void

self:FieldName := ""

self:@@Symbol := ""

self:DbT := DbType.String

self:Val := NULL

return

method Initialize(cF as string, cS as string, oD as DbType, oV as object) as void

self:FieldName := cF

self:@@Symbol := cS

self:DbT := oD

self:Val := oV

return

//--------------------------------------

PROPERTY FieldName AS STRING

GET

return self:cFieldName

END GET

SET

self:cFieldName := VALUE

END SET

END PROPERTY

//--------------------------------------

PROPERTY @@Symbol AS STRING

GET

return self:cSymbol

END GET

SET

self:cSymbol := VALUE

END SET

END PROPERTY

//--------------------------------------

PROPERTY DbT as DbType

GET

return self:oDbT

END GET

SET

self:oDbT := VALUE

END SET

END PROPERTY

//--------------------------------------

PROPERTY Val AS OBJECT

GET

return self:oVal

END GET

SET

self:oVal := VALUE

END SET

END PROPERTY

//--------------------------------------

END CLASS

END NAMESPACE //----- TestBedSQL

========================================================================================

// CommentedCode.prg

/*PUBLIC METHOD Convert(oValue AS OBJECT, targetType AS Type, oParam AS OBJECT, culture AS CultureInfo) AS OBJECT

LOCAL eSRT AS TestBedSQL.SQL_ResultType

LOCAL cText AS STRING

cText := ((STRING)oValue):ToLower():Trim()

DO CASE

CASE cText = "nothing"

eSRT := TestBedSQL.SQL_ResultType.Nothing

CASE cText = "table"

eSRT := TestBedSQL.SQL_ResultType.Table

CASE cText = "single"

eSRT := TestBedSQL.SQL_ResultType.Single

CASE cText = "list"

eSRT := TestBedSQL.SQL_ResultType.List

CASE cText = "reader"

eSRT := TestBedSQL.SQL_ResultType.Reader

OTHERWISE

RETURN DependencyProperty.UnsetValue

END CASE

RETURN eSRT*/

/*[ValueConversion(typeof(TestBedSQL.SQL_ResultType), typeof(STRING))];

CLASS EnumResultTypeConverter2 IMPLEMENTS IValueConverter

PUBLIC METHOD Convert(oValue AS OBJECT, targetType AS Type, parameter AS OBJECT, culture AS CultureInfo) AS OBJECT

LOCAL cText AS STRING

LOCAL eMyNum AS TestBedSQL.SQL_ResultType

IF !oValue = NULL_OBJECT

eMyNum := ((TestBedSQL.SQL_ResultType)oValue)

cText := eMyNum:ToString()

cText := "["+cText+"]"

ELSE

cText := ".. unset .."

ENDIF

RETURN cText

//RETURN ".. test .."

PUBLIC METHOD ConvertBack(value AS OBJECT, targetType AS Type, parameter AS OBJECT, culture AS CultureInfo) AS OBJECT

RETURN DependencyProperty.UnsetValue

END CLASS*/

//======================================================================

/*[ValueConversion(typeof(string[]), typeof(STRING))];

CLASS MyThreeMultiConverter IMPLEMENTS IMultiValueConverter

PUBLIC METHOD Convert(values AS OBJECT[], targetType AS Type, parameter AS OBJECT, culture AS CultureInfo) AS OBJECT

LOCAL cText AS STRING

cText := ""

LOCAL cParam AS STRING

cParam := (STRING)parameter

LOCAL cSeparator AS STRING

LOCAL i, n AS INT

n := values:Length

cSeparator := (STRING)values[n]

IF cParam = "123"

FOR i := 1 UPTO n-1

cText += (STRING)values[i]

IF ! i = n-1

cText += " "+cSeparator+" "

ENDIF

NEXT

ENDIF

IF cParam = "321"

FOR i := n-1 DOWNTO 1

cText += (STRING)values[i]

IF ! i = 1

cText += " "+cSeparator+" "

ENDIF

NEXT

ENDIF

RETURN cText

//-------------------------------------------------------

PUBLIC METHOD ConvertBack(value AS OBJECT, targetTypes AS Type[], parameter AS OBJECT, ;

culture AS CultureInfo) AS OBJECT[]

LOCAL splitValues, stringSeparators AS STRING[]

//LOCAL cParam AS STRING

//cParam := (STRING)parameter

stringSeparators := STRING[]{1}

stringSeparators[1] := " | "

splitValues := ((STRING)value):Split(stringSeparators, StringSplitOptions.None)

RETURN splitValues

END CLASS*/

/*[ValueConversion(typeof(string[]), typeof(STRING))];

CLASS MyThreeMultiConverter IMPLEMENTS IMultiValueConverter

PUBLIC METHOD Convert(values AS OBJECT[], targetType AS Type, parameter AS OBJECT, ;

culture AS CultureInfo) AS OBJECT

LOCAL cText := "" AS STRING

LOCAL cSeparator AS STRING

LOCAL i, n AS INT

n := values:Length

cSeparator := (STRING)values[n]

FOR i := 1 UPTO n-1

cText += (STRING)values[i]

IF ! i = n-1

cText += " "+cSeparator+" "

ENDIF

NEXT

RETURN cText

//-------------------------------------------------------

PUBLIC METHOD ConvertBack(value AS OBJECT, targetTypes AS Type[], parameter AS OBJECT, ;

culture AS CultureInfo) AS OBJECT[]

LOCAL splitValues, stringSeparators AS STRING[]

stringSeparators := STRING[]{1}

stringSeparators[1] := " | "

splitValues := ((STRING)value):Split(stringSeparators, StringSplitOptions.None)

LOCAL i, n AS INT

LOCAL cText := "" AS STRING

n := splitValues:Length

FOR I := 1 UPTO n

cText += splitValues[i] + CRLF

NEXT

MessageBox.Show(cText,"Split Vals :")

RETURN splitValues

END CLASS*/

//======================================================================

/*[ValueConversion(typeof(string[]), typeof(STRING))];

CLASS MyFullMultiConverter IMPLEMENTS IMultiValueConverter

PUBLIC METHOD Convert(values AS OBJECT[], targetType AS Type, parameter AS OBJECT, culture AS CultureInfo) AS OBJECT

LOCAL cText AS STRING

cText := (STRING)values[1] + " || " + (STRING)values[2] + " || " + (STRING)values[2] + " || " + (STRING)values[4]

//cText := (STRING)values[1] + " || " + (STRING)values[2]

//cText := (STRING)values[1]

RETURN cText

PUBLIC METHOD ConvertBack(value AS OBJECT, targetTypes AS Type[], parameter AS OBJECT, culture AS CultureInfo) AS OBJECT[]

//----- can flesh-out later ! -----

LOCAL splitValues, stringSeparators AS STRING[]

LOCAL cParam AS STRING

cParam := (STRING)parameter

//stringSeparators := STRING[]{"!!"}

//stringSeparators := STRING[]{cParam}

stringSeparators := STRING[]{1}

stringSeparators[1] := cParam

splitValues := ((string)value):Split(stringSeparators, StringSplitOptions.None)

//LOCAL splitValues AS STRING[]

//splitValues := STRING[]{2}

//splitValues[1] := " 1 "

//splitValues[2] := " 2 "

////splitValues := ((STRING)value):Split(" ")

RETURN splitValues

END CLASS*/

//======================================================================

========================================================================================

//----- OtherClasses.prg -----

//----- by PHH for DSO - 2012 -----

//----- and ClickStart eNotes -----

#using System

#using System.Collections.Generic

#using System.Linq

#using System.Text

#using System.Windows

#using System.Windows.Controls

#using System.Windows.Data

#using System.Windows.Documents

#using System.Windows.Input

#using System.Windows.Media

#using System.Windows.Media.Imaging

#using System.Windows.Navigation

#using System.Windows.Shapes

//==================================

#using System.Data.Common

#using System.Globalization

#using System.Xml.Serialization

#using System.ComponentModel

//----------------------------------------------------------------------------

#command My DEPENDENCY PROPERTY STATIC PUBLIC <DP> AS <Typ> myClass <myClass> =>;

STATIC PUBLIC <DP>Property :=;

DependencyProperty.Register(<"DP">, typeof(<Typ>),;

typeof(TestBedSQL.<myClass>), PropertyMetadata{}) ;

AS DependencyProperty;;

PROPERTY <DP> AS <Typ>;;

GET;;

RETURN (<Typ>)SELF:GetValue(<DP>Property);;

END GET;;

SET;;

SELF:SetValue(<DP>Property, VALUE);;

SELF:OnPropertyChanged(<"DP">);;

END SET;;

END PROPERTY

//----------------------------------------------------------------------------

BEGIN NAMESPACE TestBedSQL

CLASS MyComboBoxTest IMPLEMENTS IMultiValueConverter

PUBLIC METHOD Convert(values AS OBJECT[], targetType AS Type, parameter AS OBJECT, culture AS CultureInfo) AS OBJECT

LOCAL cText := "..empty .." AS STRING

LOCAL cT1, cT2, cT3 AS STRING

TRY

cT1 := ((INT)values[1]):ToString()

//cT2 := ((DateTime)values[2]):ToString()

cT2 := ((DateTime)values[2]):ToLongDateString()

cT3 := ((STRING)values[3])

//----- comes in as a STRING from the DataTable -----

//cT3 := ((SQL_ResultType)values[3]):ToString()

cText := cT1 +" || "+ cT2 +" || "+ cT3

CATCH oEX AS SystemException

MessageBox.Show(oEX:Message, "my Error :")

END TRY

RETURN cText

PUBLIC METHOD ConvertBack(value AS OBJECT, targetTypes AS Type[], parameter AS OBJECT, culture AS CultureInfo) AS OBJECT[]

//----- can flesh-out later ! -----

LOCAL splitValues, stringSeparators AS STRING[]

LOCAL cParam AS STRING

cParam := (STRING)parameter

stringSeparators := STRING[]{1}

stringSeparators[1] := cParam

splitValues := ((string)value):Split(stringSeparators, StringSplitOptions.None)

RETURN splitValues

END CLASS

//==============================================================================

[ValueConversion(typeof(STRING), typeof(TestBedSQL.SQL_ResultType))];

CLASS EnumResultTypeConverter IMPLEMENTS IValueConverter

PUBLIC METHOD Convert(oValue AS OBJECT, targetType AS Type, oParam AS OBJECT, culture AS CultureInfo) AS OBJECT

LOCAL eSRT AS SQL_ResultType

LOCAL cText AS STRING

cText := ((STRING)oValue):ToLower():Trim()

LOCAL aVals AS SQL_ResultType[]

aVals := (SQL_ResultType[])Enum.GetValues(typeof(SQL_ResultType))

LOCAL n, i AS INT

n := aVals:Length

FOR i := 1 UPTO n

eSRT := aVals[i]

IF eSRT:ToString():ToLower():Trim() = cText

RETURN eSRT

ENDIF

NEXT

RETURN DependencyProperty.UnsetValue

PUBLIC METHOD ConvertBack(oValue AS OBJECT, targetType AS Type, oParam AS OBJECT, culture AS CultureInfo) AS OBJECT

LOCAL eSRT AS SQL_ResultType

eSRT := (SQL_ResultType)oValue

LOCAL cResult AS STRING

cResult := eSRT:ToString():Trim()

IF cResult:Length <3

RETURN DependencyProperty.UnsetValue

ENDIF

RETURN cResult

END CLASS

//======================================================================

[ValueConversion(typeof(DateTime), typeof(STRING))];

CLASS EntryStampConverter IMPLEMENTS IValueConverter

PUBLIC METHOD Convert(value AS OBJECT, targetType AS Type, parameter AS OBJECT,;

culture AS CultureInfo) AS OBJECT

LOCAL cText AS STRING

LOCAL cInput AS STRING

cInput := ((STRING)parameter):ToLower()

LOCAL oDT AS DateTime

oDT := (DateTime)value

DO CASE

CASE cInput = "shortdate"

cText := oDT:ToShortDateString()

CASE cInput = "longdate"

cText := oDT:ToLongDateString()

CASE cInput = "shorttime"

cText := oDT:ToShortTimeString()

CASE cInput = "longtime"

cText := oDT:ToLongTimeString()

CASE cInput = "string"

cText := oDT:ToString()

OTHERWISE

cText := "Unknown"

ENDCASE

RETURN cText

PUBLIC METHOD ConvertBack(value AS OBJECT, targetType AS Type, parameter AS OBJECT,;

culture AS CultureInfo) AS OBJECT

//----- can flesh-out later ! -----

//----- may not want a 'Back' facility !? -----

RETURN DependencyProperty.UnsetValue

END CLASS

//======================================================================================

CLASS Attendee

PROPERTY Attendee_ID AS INT Auto

PROPERTY Surname AS STRING Auto

PROPERTY Firstname AS STRING Auto

PROPERTY RegDate AS DateTime Auto

PROPERTY FeePaid AS Double Auto

PROPERTY IsSpeaker AS Boolean Auto

PROPERTY DaysAtDSO AS INT Auto

CONSTRUCTOR()

SUPER()

SELF:Initialize()

RETURN

METHOD Initialize() AS VOID

SELF:DaysAtDSO := -1

SELF:IsSpeaker := FALSE

SELF:FeePaid := 0.0

SELF:Surname := ""

SELF:Firstname := ""

SELF:Attendee_ID := -999

SELF:RegDate := DateTime{1900,01,01}

RETURN

END CLASS

//=============================================================

CLASS Query

PROPERTY Query_ID AS INT Auto

PROPERTY Script AS STRING Auto

PROPERTY EntryStamp AS DateTime Auto

PROPERTY ResultType AS SQL_ResultType Auto

PROPERTY Mode AS SQL_Mode Auto

CONSTRUCTOR()

SUPER()

SELF:Initialize()

RETURN

METHOD Initialize() AS VOID

SELF:Query_ID := -999

RETURN

END CLASS

//===============================================================

[Serializable] CLASS Clerk INHERIT DependencyObject IMPLEMENTS INotifyPropertyChanged

My DEPENDENCY PROPERTY STATIC PUBLIC Booking_Clerk_ID AS INT myClass Clerk

My DEPENDENCY PROPERTY STATIC PUBLIC Surname AS STRING myClass Clerk

My DEPENDENCY PROPERTY STATIC PUBLIC Firstname AS STRING myClass Clerk

My DEPENDENCY PROPERTY STATIC PUBLIC Password AS STRING myClass Clerk

My DEPENDENCY PROPERTY STATIC PUBLIC BC_EntryStamp AS DateTime myClass Clerk

My DEPENDENCY PROPERTY STATIC PUBLIC BC_ModifyStamp AS DateTime myClass Clerk

My DEPENDENCY PROPERTY STATIC PUBLIC Internal_phone AS STRING myClass Clerk

//----- Events -----

EVENT PropertyChanged AS PropertyChangedEventHandler

CONSTRUCTOR()

SUPER()

SELF:Initialize()

RETURN

METHOD Initialize() AS VOID

SELF:Booking_Clerk_ID := -999

RETURN

PROTECTED METHOD OnPropertyChanged(name AS STRING) AS VOID

LOCAL pChanged AS System.ComponentModel.PropertyChangedEventHandler

pChanged := SELF:PropertyChanged

IF (pChanged != NULL)

pChanged:Invoke(SELF, System.ComponentModel.PropertyChangedEventArgs{name})

ENDIF

RETURN

END CLASS

//=================================================

[Serializable] CLASS Customer INHERIT DependencyObject IMPLEMENTS INotifyPropertyChanged

My DEPENDENCY PROPERTY STATIC PUBLIC UnRef AS INT myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Salutation AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Surname AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Firstname AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC KnownBy AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Add1 AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Add2 AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Add3 AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Add4 AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Add5 AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC PostCode AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC PhoneHome AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC PhoneWork AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Mobile AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC Email AS STRING myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC DateTimeStamp AS DateTime myClass Customer

My DEPENDENCY PROPERTY STATIC PUBLIC EditStamp AS DateTime myClass Customer

//----- Events -----

EVENT PropertyChanged AS PropertyChangedEventHandler

CONSTRUCTOR()

SUPER()

SELF:Initialize()

RETURN

METHOD Initialize() AS VOID

SELF:UnRef := -999

RETURN

PROTECTED METHOD OnPropertyChanged(name AS STRING) AS VOID

LOCAL pChanged AS System.ComponentModel.PropertyChangedEventHandler

pChanged := SELF:PropertyChanged

IF (pChanged != NULL)

pChanged:Invoke(SELF, System.ComponentModel.PropertyChangedEventArgs{name})

ENDIF

RETURN

END CLASS

//=================================================

CLASS Party

PROPERTY Party_ID AS INT Auto

PROPERTY Party_Name AS STRING Auto

PROPERTY Salutation AS STRING Auto

PROPERTY Surname AS STRING Auto

PROPERTY Firstname AS STRING Auto

PROPERTY phone_number AS STRING Auto

PROPERTY add1 AS STRING Auto

PROPERTY add2 AS STRING Auto

PROPERTY add3 AS STRING Auto

PROPERTY postcode AS STRING Auto

PROPERTY credit_card AS STRING Auto

PROPERTY Date_Entered AS DateTime Auto

CONSTRUCTOR()

SUPER()

SELF:Initialize()

RETURN

METHOD Initialize() AS VOID

SELF:Party_ID := -999

RETURN

END CLASS

//=================================================

ENUM SQL_ResultType

Nothing := 1

Single := 2

Table := 3

Reader := 4

List := 5

END ENUM

//=================================================

ENUM SQL_Mode

member Data := 1

member @@Object := 2

member System := 3

END ENUM

END NAMESPACE

========================================================================================

<Application x:Class="TestBedSQL.App"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:pearls="clr-namespace:TestBedSQL"

StartupUri="Window1.xaml">

<Application.Resources>

<Style TargetType="ComboBox" x:Key="My3DropLists">

<Setter Property="Height" Value="23" />

<Setter Property="HorizontalAlignment" Value="Left" />

<Setter Property="VerticalAlignment" Value="Top" />

<Setter Property="Width" Value="120" />

</Style>

</Application.Resources>

</Application>

========================================================================================

#using System

#using System.Collections.Generic

#using System.Configuration

#using System.Data

#using System.Linq

#using System.Windows

BEGIN NAMESPACE TestBedSQL

/// <summary>

/// Interaction logic for App.xaml

/// </summary>

PARTIAL CLASS App INHERIT Application

END CLASS

END NAMESPACE

========================================================================================

<Window x:Class="TestBedSQL.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:pearls="clr-namespace:TestBedSQL"

MinHeight="460" MinWidth="550"

Title="Test-Bed SQL :" Height="460" Width="550" Background="BlanchedAlmond">

<Window.Resources>

<pearls:EntryStampConverter x:Key="ESCon"/>

<pearls:EnumResultTypeConverter x:Key="EnumRT"/>

<pearls:MyComboBoxTest x:Key="myComboTest"/>

</Window.Resources>

<Grid>

<!--======================================================================================================-->

<ComboBox Height="23" Margin="83,263,132,0" Name="cbxQuery" VerticalAlignment="Top" ItemsSource="{Binding}"

SelectionChanged="cbxQuery_SelectionChanged" >

<ComboBox.ItemTemplate>

<DataTemplate>

<StackPanel Orientation="Horizontal" >

<StackPanel.ToolTip>

<TextBlock Text="{Binding Path=Script}" Background="SlateGray" Foreground="Gold"/>

</StackPanel.ToolTip>

<TextBlock Name="tbTest2" Text="{Binding Path=Query_ID}"/>

<TextBlock Name="tbTest_2" Text=" | "/>

<TextBlock Name="tbTest3" Text="{Binding Path=EntryStamp,

Converter={StaticResource ESCon}, ConverterParameter=LongDate}"/>

<TextBlock Name="tbTest_4" Text=" | "/>

<TextBlock Name="tbTest5"

Text="{Binding Path=Result_Type, Converter={StaticResource EnumRT}, StringFormat=!!{0}!!}"/>

</StackPanel>

</DataTemplate>

</ComboBox.ItemTemplate>

</ComboBox>

<!--======================================================================================================-->

<TextBlock Height="23" HorizontalAlignment="Left" Margin="92,28,0,0" Name="tblkQueryScript" Text=" .. query script .."

VerticalAlignment="Top" Width="412" Background="Ivory"/>

<TextBlock Height="23" HorizontalAlignment="Left" Margin="92,62,0,0" Name="tblkScalar" Text=" .. return value .."

VerticalAlignment="Top" Width="412" Background="Ivory" />

<Label Content="Script :" Height="28" HorizontalAlignment="Left" Margin="11,28,0,0" Name="label1"

VerticalAlignment="Top" Width="83" Foreground="Red"/>

<Label Content="Return Data : " Height="28" HorizontalAlignment="Left" Margin="12,57,0,0" Name="label2"

VerticalAlignment="Top" Width="82" Foreground="Red"/>

<TextBox Height="130" Margin="12,121,12,0" Name="tbxNewQuery" VerticalAlignment="Top" AcceptsReturn="True"

HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="AliceBlue"/>

<DataGrid Margin="12,292,12,12" AutoGenerateColumns="True" ItemsSource="{Binding}"

Name="dgReturn" />

<Label Content="New Query :" Height="28" HorizontalAlignment="Left" Margin="11,87,0,0" Name="label3"

VerticalAlignment="Top" Width="83" Foreground="Red" FontWeight="Bold" />

<Button Content="Send Query" Height="23" HorizontalAlignment="Left" Margin="198,92,0,0" Name="pbSendQuery"

VerticalAlignment="Top" Width="85" Foreground="SlateBlue" Click="pbSendQuery_Click"

Background="Gold" FontWeight="Bold" />

<Label Content="Old Query :" Height="28" HorizontalAlignment="Left" Margin="12,262,0,0" Name="label4"

VerticalAlignment="Top" Width="82" Foreground="Red" FontWeight="Bold"/>

<Button Content="Save 'Q'" Height="23" HorizontalAlignment="Left" Margin="300,92,0,0" Name="pbSaveQ"

VerticalAlignment="Top" Width="66" Foreground="LightGreen" Background="Black"

Click="pbSaveQ_Click" FontWeight="Bold"/>

<Button Content="Remove Selected" Height="23" HorizontalAlignment="Right" Margin="0,263,12,0" Name="pbRemoveSelected"

VerticalAlignment="Top" Width="114" Background="LightCoral" Foreground="Yellow" FontWeight="Bold"

Click="pbRemoveSelected_Click" />

<Button Content="Connection Form" Height="23" HorizontalAlignment="Right" Margin="0,0,12,0" Name="pbConnectionForm"

VerticalAlignment="Top" Width="117" Click="pbConnectionForm_Click"

Background="Chartreuse">

<Button.ToolTip>

<StackPanel Orientation="Vertical">

<TextBlock Text="Use to find server instances, databases and tables." Background="BlanchedAlmond" Foreground="DarkGreen"/>

<TextBlock Text="[ .. displayed using Data Binding .. ]." Background="Beige" Foreground="Red"/>

<TextBlock Text="{Binding}" Background="BlanchedAlmond" Foreground="DarkGreen"/>

</StackPanel>

</Button.ToolTip>

</Button>

<CheckBox Content="Display Messages :" Height="16" HorizontalAlignment="Left" Margin="17,6,0,0" Name="chkbxDisplayMessages"

VerticalAlignment="Top" Width="auto" Foreground="Red" Click="chkbxDisplayMessages_Click" FontWeight="Bold"/>

<Button Content="Backup Database" Height="23" HorizontalAlignment="Left" Margin="385,92,0,0" Name="pbBackupDatabase"

VerticalAlignment="Top" Width="119" Click="pbBackupDatabase_Click" Background="DarkViolet"

Foreground="Yellow" FontWeight="Bold" />

</Grid>

</Window>

========================================================================================

#using System

#using System.Collections.Generic

#using System.Linq

#using System.Text

#using System.Windows

#using System.Windows.Controls

#using System.Windows.Data

#using System.Windows.Documents

#using System.Windows.Input

#using System.Windows.Media

#using System.Windows.Media.Imaging

#using System.Windows.Navigation

#using System.Windows.Shapes

BEGIN NAMESPACE TestBedSQL

/// <summary>

/// Interaction logic for Window1.xaml

/// </summary>

PARTIAL CLASS Window1 INHERIT Window

CONSTRUCTOR()

SUPER()

InitializeComponent()

TRY

SELF:Initialize()

CATCH oEX as SystemException

MessageBox.Show(oEX:Message,"my Main error :")

END TRY

RETURN

PRIVATE METHOD pbSendQuery_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

TRY

SELF:DoSendAnyQuery()

CATCH oEX AS SystemException

MessageBox.Show(oEX:Message,"my Error Send Q :")

END TRY

RETURN

PRIVATE METHOD cbxQuery_SelectionChanged( sender AS System.Object, e AS System.Windows.Controls.SelectionChangedEventArgs ) AS System.Void

SELF:DoSelectQuery()

RETURN

PRIVATE METHOD pbSaveQ_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoSaveQuery()

RETURN

PRIVATE METHOD pbRemoveSelected_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoRemoveSelected()

RETURN

PRIVATE METHOD pbConnectionForm_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoConnectionForm()

RETURN

PRIVATE METHOD chkbxDisplayMessages_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoChangeMessageDisplaySetting()

RETURN

PRIVATE METHOD pbBackupDatabase_Click( sender AS System.Object, e AS System.Windows.RoutedEventArgs ) AS System.Void

SELF:DoBackupDatabase()

RETURN

END CLASS

END NAMESPACE

========================================================================================

PHH - 27th. April 2012.


Chapter 5 - Regular Expressions and much more !

Quite recently I was reminded of the power and potential of using Regular Expressions.

When it comes to validating string data there is nothing as flexible or as powerful which is so easy

to pick up and use in our own coding.

RegExs, as they are commonly called, are used throughout the IT world today, and also extensively on

the internet.

Let us get motivated by looking at a colourful image of RegExs at work - see below :-

And if we have a little peek behind the images, at the code itself, we see the sort of stuff in the following

illustration - checkout line 329 where we ask if the text string we have in our input box actually matches the

pattern we have instantiated our RegEx object with :-

=========================================================================================================

And if you wish to have a peek at the code to instantiate the RegEx object then check this out below :-

===========================================================================================================================

In case you do not know it, the UK postcode is quite a complex 'thing', and getting a fully correct, and all-encompassing

pattern is NOT an simple matter, or easy task.

The one shown above handles UPPER case only, and so the text string to be check out needs first to be 'forced' into

upper case - which to my mind is better, since it makes the pattern simpler to read, and easier to check.

Still, we had better move on as this section is not about Regular Expressions in particular.

I am sure that many guys are put off using them (RegExs) because they look so complex - BUT - with a handy 'Tester'

tool, and a small handbook from Ben Forta (SAMS Regular Expressions in 10 minutes) this can all change. I know,

it did for me - need to make a little effort however.

One reason I have included some information on the 'RegExTester' is that it uses a simple STATIC class in

which to supply the Ben Forta book examples as a typed List.

And as you know I am currently singing the praises of the STATIC class, as a feature / facility well underused by

Vulcan.NET developer colleagues - I would guess, simply because of the absence of NG messages.

So as well as exploring some aspects of how the Tester Tool is put together, we will look at STATIC classes

in use.

==================================================

PHH - 3rd. May 2012.


the Tester Tool

Before we get into discussing what the 'TestTool' for Regular Expressions allows us to do, let us

take a peek at what it looks like when compiled and run.

Study a;; the details in the following illustration :-

As the 'TesterTool' opens at runtime, the drop list (at top right of Form) is bound to a collection made

of objects of type 'MyRegExTest'. We will look at this class in detail shortly, but for now notice that

this collection can be of two 'flavours', or varieties.

With the default 'app launch' settings at the moment, we get a list of ten items which have been entered

and saved by the user.

These are also saved in XML format to disk at app close - using serialization. When the app is relaunched

then if the file exists it is read and the collection re-made by deserialization.

Let us see what we have when we just launch and open the applet - see below :-

======================================================================================

It does not matter what the content of each RegaEx pattern is, it was entered by me using the on-screen

input controls, tested by using the 'Try to Find' button, and probably a replacement attempt made as well.

After testing the 'Save Test' button add the new pattern to the collection.

We will look in detail about Adding and Removing items later, as also the way that the collection of the

user-entered test patterns are serialized to disk.

For now we need to know that there is a second way to fill the drop list - by binding to a collection - and

that is to use the blue button called 'Ben Forta'.

If we click the 'Ben Forta' button then a current collection of 22 items is bound to the 'ComboBox' control.

This list of items is one which has been made in code from examples in the small but VERY useful text

book by Ben Forta.

Checkout the following image to see some of these in the drop list :-

The details of how we have created the samples list, and how it is then used in code, is included

in subsections following this one.

For the moment let us just look at the following image in which one of the stored samples is selected

from the drop list and populates some of the controls.

After selecting the radio button for 'Replace', the 'Try' button changes to display 'Try to Replace' in place

of the previous test of 'Try to Find'. After clicking the 'Try to Replace' button, other controls are populated

with relevant details.

Checkout the following details CAREFULLY :-

Notice in the above image that any matches are shown in a grid at the foot of the form.

Also the search text string is displayed in a Read Only text block element, and the replacements are

obviously spotted by the keen eyed reader ;-0)

When closing the app while the drop list is the Ben Forta sample collection, the code does not allow the

user XML file to be over-written. Other wise we would end up with the Ben Forta sample collection on

disk as well as in code, and the user made collection would be lost !

This is what you see when the Ben Forta collection is bond to the ComboBox control :-

======================================================

Okay then, enough of all that, time to move on.

See you in the next subsection where we look at the 'MyRegExTest' class .............................

PHH - 3rd. May 2012.


The custom class 'MyRegExTest'

When I started to create the 'TesterTool' I designed it to be driven by 'Data Binding' and so based on the most

suitable custom class of objects, as well as the most suitable collection type.

By this I mean that I chose the collection type to be an 'ObservableCollection' as this handles binding best, and

also that our custom object ( 'MyRegExTest' ) was created as a Dependency object, with suitable dependency

properties.

I can't stress enough to guys new to Data Binding, that the whole process needs to be a top down approach. If you

wish to have data binding then design it in from the start, and things will just work, and nicely too.

If you try to code things as you have always done, and then try adding bits to make data binding happen - there will

always be things that seem not to work as they should! And then the whole process becomes long and frustrating,

when it need not be like that.

So lets start out right, let us start with a fully compliant Data Binding class, see below for details.

*** Note *** only the top three properties matter to us, the rest are for test and demo purposes.

=======================================================================================================================

Study the above image details thoroughly. They provide us with a class of object which will fully fit in with the .NET 4.0 WPF

technologies - here we are talking in particular of the Dependency System which WPF runs.

Objects of this class need to notify the system when changes to their data take place.

So if we reduce the above class to its minimum code required for the app to run correctly, we get the following :-

=====================================================================================================================

As you can now see above, this code is quite short and easy to follow / understand.

However, it is because of Johan's method of removing the verbose coding for the Properties that this is now so.

Having written a lot of successful data binding with WPF the long and tedious way, I think this new approach is terrific.

Or should I say 'brilliant' !?

Now then, what do we get serialized to disk from the two classes when they are used?

First of all lets look at the data from the properties of one such object, how it looks in the XML script - see below :-

=========================================================================================================================================

Yes, I think you will find the above easy enough to follow, and you can clearly see that this is from a class

where the full eight properties existed and were in use.

See if you can identify the property names in the above image.

Now then, when we use the 'reduced' class, with only the three necessary properties, we see the following

after the collections has been serialized - see below :-

=======================================================================================================================================

Notice that we see in the above image only three properties with data. Which is enough to make the app work as it should.

In the image following we see that we have three such objects, and so we must be looking at a collection which is saved to

disk file - check this out :-

============================================================================================================================

This is sufficient for now, but if you would like to see how the collection is serialized then

move on to read the next subsection.

See you there ...............................

PHH - 5th. May 2012.


Serializing the Collection

Okay then guys, we saw in the last subsection how we ended up with a group of XML scripted objects

on disk file.

So how did we actually code things such as to make this happen?

Well, first of all we need a suitable class which may be serialized.

So we need a class as shown below :-

===================================================================

Yes, a very simple class, and one with one property only.

Obviously the property is a typed List, and the type being 'MyRegExTest', the class we have just discussed

in great detail in the last subsection.

How to use the new class :

Well, as you may imagine, we need to use this class to do the job of storing data to disk, as well as reading

it back in from the XML disk file.

Let us start by see how we may take our collection, and then put this to disk. I often call this the 'Dump' process.

So we will now examine how to 'dump to disk'.

Serialization to disk :

I am not sure how many other Vulcan guys use serialization much, or at all. However, I have to say that I have found

it VERY useful, on a regular basis, ever since Paul Piko introduced me to it some years back.

It is really quite easy to make happen, you just need start with the correct object class.

Study the following image, and in particular line 178 - see below :-

===============================================================================================

The above code does need the correct #using statement at the top of the PRG file and my example

is shown below :-

=======================================================

Let us look in detail at the code on line 178.

Notice that we can take a collection of type 'ObservableCollection' and use this directly in an instantiation.

Some of you may remember that we can do the 'reverse' where we use a typed List to create, or instantiate

an 'ObservableCollection' object.

Both of these are VERY useful to know ! So don't forget them !!

The rest of the code for serializing should be recognisable, and understandable to you. Basically, the serializable

object of class 'MySaveTestSet4Dump' is used to instantiate an 'XmlSerializer' object, which in turn is then used

to provide text streamed data for the stream writing process.

Once you have done this a time or two you really get the hang of it. the trick is to make sure you have defined your

serializable object class correctly - and tested it too.

Now lets move on to the reverse process, how to get our data back from disk file.

De-serialization - from disk :

Okay then guys, time to go the other way - time to retrieve our data from an XML file stored on disk.

In many ways this is similar to the process of 'Serializing', which is not surprising really ;-0)

It is probably best if we just view the whole 'Scoop' code as it stands - if we use the term 'Scoop' to mean the

reverse of 'Dump'. So we can talk of 'data dumping' and 'data scooping', or I will for my own purposes.

Study CAREFULLY the following code :-

=================================================================================================

Once again we see (in the image above) the use of the XmlSerializer class to act as intermediary between

our stream reader and the 'MySaveTestSet4Dump' class object.

In line 200 shown above, the local 'collection' object is 'made' from the streamed text data, via the XmlSerializer

object.

In reality, there are only three lines of code which do anything 'serious', the rest is message and set-up stuff.

lets now move on to the next stage.

So what do we do with it ? :

Okay, we are doing fine, so we now have our retrieved collection, so how do we display this using the code above ?

Let us see how we use the 'ReadSerialXmlFile' method in our Form initialization section. Yes, this is how we 'Scoop' !

Checkout the details in the following image :-

=====================================================================================

Obviously, it is line 74 above which retrieves the data from disk, but the association with the drop list is done in

line 81 where we call upon the 'LoadUpTheDropList' method.

Lets see what this has to offer us - see below :-

==============================================================================================

In the image shown above we have an interesting development. Note that we are using a 'View'.

Actually, without the developer realising it, there is always a 'View' when we are dealing with a collection.

However, the default view can go unnoticed by us if we do not realise it.

I prefer to make my own View as seen in line 119. We then associate the 'view' with the binding mode of

the 'ComboBox'.

Another thing to point out is that it is the 'view' with which we can do our item 'navigation', AND, that once

we have our 'view' in place we no longer must try to set selected items and indexes directly on the 'ComboBox'

control - we MUST use the navigation methods of the View itself.

Oh! I have just noticed that we need to see the start of our main class to see the declaration of the View itself.

Check this out :-

=============================================================================

And while we are discussing 'View' we need to see the relevant properties and methods for handling the navigation.

See below for class members related to navigation - as well as filtering and sorting :-

================================================================================================================================

Okay then guys, time to move on now that you know about persisting objects as XML text on disk.

See you in the next subsection ............................

PHH - 5th. May 2012.


STATIC classes for RegEx use

After playing around with RegExs for a short time it would appear that the general topic is quite suitable

for constructing and using at least a couple of different STATIC classes.

[ .. it maybe as well for readers to go and look at the Appendix 'B' section on 'escaping' .. ]

Simple - Data :

Library of Usable Patterns :

Patters with Match / No Match strings :

PHH - 3rd. May 2012.


Simple data aggregation class

Okay then guys, here is a really good example of how to make and use a simple STATIC class.

The class with have no STATIC 'Constructor' and will only a bunch of STATIC methods. All methods except one

will be similar.

The singular STATIC method will return a typed List, the type being of class 'MyRegExTest'.

Let us start this subsection by seeing how we can best use the STATIC class once we have created it.

In the image shown below we see line 139 on which we are associating the returned typed List with the class

variable 'aTests' - check this out :-

==================================================================================================================

Obviously we need to see what intellisense has to offer when we type out line 139 so here we have it

shown below :-

======================================================================================================================================================================

So as we can see we have a whole bunch of methods, but only the top ones is a List, the rest are where

separate objects of type 'MyRegExTest' are instantiated.

So having retrieved our data using the STATIC method call, how do we use it ?

Check out the following image where we use the type List to instantiate our collection :-

============================================================================================

That is fine, we can see how we can use the returned data List but how are things set-up in the STATIC class itself ?

Before we delve into details, let us look at the overall 'shape' of our STATIC class, with regions collapsed so we can

fit the whole class onto one manageable image / page.

Study this carefully and then we can start to expand some parts of it for our further understanding :-

=====================================================================

Now then, before we move off to study details, let us see what the typed List looks like when

bound to the 'ComboBox' control - see below :-

========================================================

Now then, if we try to find the highlighted object being instantiated in our STATIC code we see the following :-

======================================================================================================================================================================================

The shape of the above STATIC method is similar to all the others except the 'AllBenForta' one.

In each of the bulk of these methods we are instantiating a new 'MyRegExTest' object and setting its properties

with the example text values supplied in his book.

To keep a track on which ones we have used we use the lesson number, along with a running number for the lesson example.

So if we now move towards the end of those examples I have typed and used, we see the following :-

=============================================================================================================

I think at this stage you ought to look at Appendix B where we study some details about using 'escaping' in .NET

and how we represent the 'escaped' RegEx characters.

For the present, in this subsection, let us now see how all the bulk of the STATIC methods are used to make our

singularly 'special' one.

It is probably best if we study what the working code looks like - see below :-

==================================================================

Yes, with only up to lesson five being fully entered we can see the expanded STATIC method, but will need

to use our 'Region' facilities when things get very long !

So what do we see above ? Well, since each of the 'bulk' methods returns a single object of type 'MyRegExTest'

we can simply add them, one by one, into the growing typed List.

Nothing too clever, not even 'magic' ! However, it is extremely useful.

So now we had better move on. See you in the next subsection ..............................

PHH - 3rd. May 2012.


Best example 'patterns'

One thing you will soon learn about using RegEx 'patterns' is that they are sometimes easy to come by on

the web, but hard to know whether they are are good.

I mean by this that lots of guys rush to print and publish articles and short snippets to show that they can use

RegExs, but since many of their published patterns vary with one another, it is sometimes hard to know which

is good, which poor, and which damned right wrong or misleading.

For this reason I think you will see experienced developers storing and guarding their pattern 'finds'. I am no

different to other developer guys, and so I want a way easily find, review, and use my tried and tested patterns.

A good example of just how a RegEx pattern can vary is that required for 'matching' UK postcodes.

And remember, while we discuss this issue, that a pattern is only as good as the problem it is trying to solve.

Or should I say, as good at the rules for the type of pattern it is to 'match'.

Some pattern found by 'Googling' are limited to Upper or Lower case, some do both and as a consequence are

much more complex to read and checkout / correct.

What is the point of having a pattern which can do both lower and upper case if the text to be searched can first

be forced to UPPER case ? Under these circumstances we may as well keep the RegEx pattern simpler, and

use our dotNet code to force the text string to be search into UPPER case, before we try a matching.

And, just to prove this 'accuracy' / 'suitability' issues exists, I will show you a pattern from the appendices of Ben

Forta's book itself - it is simple, and also incorrect / incomplete.

Before we get drawn into too much detail on any particular pattern, let us see the overall shape of such a STATIC

class - one for storing tried and tested patterns - see below :-

=================================================================================================

Note that the UK postcode patterns are to be found in the static property at line 36. We will explore this in just

a moment, but for now lets see a simpler one - study that shown below, for VISA :-

=======================================================================================

So after mentally translating the escaped string of the .NET world on line 49 you will see that this

property ought to return the following text string - check this out :-

======================================================================================

[ ** NOTE ** the double back slashes have been reduced to singles after 'escaping' has been applied / removed. ]

Okay then guys, are we up to looking at the UK postcode stuff ?

Well, at least we do not need to try and understand it, or interpret the pattern, just to see what a variance we can get

when we attempt to get hold of something which works, or does the job !!

Notice that the simple but incomplete pattern of Ben Forta is shown on line 40.

Also notice in the following image that I have recorded a few reasonable attempts, but currently favour that on line 45.

Checkout all the details shown below :-

=================================================================================================================================================================================================================

Now then, just to prove that American guys can't get their heads around how complex our postal code is, lets try my

TesterTool applet to test Ben's pattern with my postcode - yes, the one I have had for years, and the one that seems

to work with other folds postcode related software - as in Google Maps.

First, here is the failed attempt at a match :-

=======================================================================================

And here is proof that my postcode works OK - see below :-

======================================================================================================================================

And now if I use the pattern on line 43 and remove the double 'escaped' slashes we see this matching below :-

And quite honestly the pattern on line 45 is much better still - even if it only does UPPER case.

The one shown above does do both upper and lower case - see the following TesterTool output :-

Fine then guys, all we need do to build up this STATIC class for general use is to find lots of 'quality' patterns, for lots

of useful search types, enter them as new properties in our STATIC class, and then try and test them, before

allowing them to be available for use generally.

This is a far BIGGER task than it sounds ;-0)

So this topic will need to be revisited a few times yet I feel - in the future !!!

See you in the next subsection ...............................

PHH - 3rd. May 2012.


Suitable test data for RegEx testing

Well folks this subsection is VERY fresh, or new.

So much so that I am just putting the first ideas together, and doing some tests.

The basic idea behind this is that if we are to have patterns saved and ready for use from within a

STATIC class, then must also have some test data which has been tried and tested as well.

Often when searching the net we find a suggested RegEx pattern along with search text which should

match and also some which should not match.

Just as important as it is to keep our patterns 'tuned' and up to date etc., it is also as important to keep

text string which should find a match, and those that should NOT find a match as well.

If we are then ever to find a pattern which is thought to be better than the existing one, we have suitable

and consistent test data to work with.

An Example to Prove the Point :

Let us explore one way of doing this, as well as use a suitable example to demonstrate what we are doing.

At its simplest level we could include in our STATIC class test strings which should match and those which

should not. Let us take the UK postcode example to illustrate this.

Look at the following image details :-

==============================================================================

I have named a couple of properties in such a way as we can then identify that they go along with the one for the 'pattern'.

The 'YesTest_UK_Postcode_01' produces, or returns a value which ought to give a match for any correct postcode pattern.

Likewise the 'NoTest_UK_Postcode_01' property returns a string which ought NOT to give a match with any correct pattern.

These two I tested with Ben Forta's pattern, and used the 'YesTest' to show his pattern was not returning a match when it should have.

A Better way to do this :

However, there is a better way to do this so that we keep together the pattern, and the yes/no test strings.

First we need to create an 'Enum' as shown below :-

==============================

Now that we can use our 'Enum' to determine which mode we require, we can use a simple switch with one single STATIC method.

Check out the following image details :-

==========================================================================================================================================================

And just to remind you of the beauty of using Enums here is an illustration to remind you :-

===========================================================================

By coding our STATIC class as shown above, we keep all the relevant parts together.

I certainly feel this approach is better than our earlier one - I hope you agree.

There will not be time before 'DSO' to try this, BUT, I fee that we can change the 'TesterTool' so that we can

incorporate this class and in some way or another call up the pattern we require and select either the Yes or No string to test.

I will have to look into this after returning from Shrewton, Wiltshire, UK, in a week's time.

Okay then guys, time to move on, let us look at something a little different.

See you in the next subsection ................

PHH - 3rd. May 2012.


Some underlying code developments

I am currently using Vulcan.Net 2, version we call 210.

This latest version has some exciting new additions to the syntax we can use in our general coding.

One particular step forward I like a great deal is the easy way we can declare a PROPERTY.

I have to say that right form the early times of Access / Assign I have thought that declaring properties was

a verbose and boring business.

However, it is crucially important to get our property declarations 100% correct. So any copy and paste

procedure to try and save typing was always fraught with danger in respect of making mistakes etc..

Now then, study the new way of doing this as shown below :-

====================================================

Yes, with one line we can fully declare a new PROPERTY in version 210.

If we study the following illustration we can see in the highlighted area just what code is effectively rolled

up into a single line for us developers. the hard work is all done behind the scenes - check this out :-

======================================================

usually I am not concerned with what the field may be called, all I require is a short and easy way to declare a

property and have my resulting code look clean and tidy - then I can concentrate on the more difficult parts.

Taking this further - the Preprocessor :

PHH - 3rd. May 2012.


Tidying up Dependency code

========================================================================================================

====================================================================================================================

PHH - 3rd. May 2012.


Chapter 6 - Validation of Bound Data

In my recent exploration of WPF features and functionality I have come across another good example

of the use of a user defined STATIC class.

There is a nice way to validate input data on a WPF form and it involves 'Data Binding' obviously. I say

this since just about everything visual and on a WPF 'Form' revolves around 'Binding'.

However, like so many other situation in which we may identify that a specific new class is required, it

soon grows to be quite large (or extensive) as we add more and more properties and methods to it.

I suggest that it is a rule of 'nature' ( God made !? ) that new classes are a 'work in progress' for quite

a while after their 'conception'. This identified need for a Visual and/or Logical Element class is just as

all the others, and you can see from the following two images how in a short time we can move from a

simple beginning to a much more extensive set of members.

Checkout the next two images below [ .. don't expect to understand many of the member names as yet ! .. ] :-

===================================================================================================================

Notice above we have only one property and four methods - and it does the job originally identified.

However, below is what I soon migrated to when I found I could add useful features, facilities, and bits and pieces

to the original 'idea' :-

===========================================================================================================================

Yes, we have twenty four (24) methods (and extra 20 that is) and still our single property.

And I have to say I can't remember making a decision to try and enlarge the class, it just grew that way ;-0)

I seems like a good idea that before you get bamboozled by any more members and names, that we move

on to the next subsection and start simple - explaining what this STATIC class is all about.

See you in the next subsection .........................

PHH - 6th. May 2012.


STATIC class - finding WPF GUI elements

Okay you guys, what is this all about ? What is a Visual Tree, and why do we wish to access it, as well

as how do we use it once we can access it ?

Let us start out very simple. Let us look at an image of a typical WPF form. Well, it is sort of typical,

except I have added a further couple of levels of Grids within another Grid etc..

Study the image shown below :-

You can see in the above illustration that basically we have a standard type (or sort) of data dialog form.

WPF 'TextBox' controls (GUI elements) allow users to input and edit the fields data as they wish.

In the Visual Studio 'shell' the DO (document outline) pane looks like that shown below :-

As you can see in the above image there are lots of elements making up this Form.

Moving on, we can see that we also have a Form which is 'aware' of some of the data values supplied

to the 'TextBox' input elements.

Checkout carefully the next illustration below :-

It is very obvious that four of the controls have red borders and as such we assume that something is 'wrong' !

If we study the following illustrations we see XAML script which applies a dotNet coded validation rule to the

'Height' related 'TextBox'. Checkout the details below :-

========================================================================================================

Notice that the prefix 'pearls' is required for us to instantiate an object of the .NET class 'MyIntegerVR', simply

because the namespace declaration at the head of the XAML file looks like this shown below :-

=======================================================================================================================

And I suppose we are all keen to see what the dotNet class called 'MyIntegerVR' looks like !

Well, wait no longer, here it is below, a little 'collapsed', but only in regions where there is nothing new, or extra, to

be shown. Checkout the details thoroughly :-

=========================================================================================================

Notice that it is within the Validation Rule that we can make all our 'custom' comparisons - or put

another way, basically do what we want to.

However, by writing it as we have, we can at least set the lower and upper comparison limits on the one single VR class.

So at least it is 'general' in some ways.

Now lets take an overall 'glance' at what the STATIC class looks like, at the stage of development it has reached at this moment.

I must stress that this is very much a 'work in progress' project, so there will be much to be changed and added too.

For now just get an overall 'look / feel' of its :-

==========================================================================================================================

As you can see in the image above, we have STATIC methods, some STATIC fields (a mix of public and private), one property

(with more to follow) and also a STATIC constructor.

So if one of the public fields, properties or methods gets called during execution, then the 'Constructor' is run just once, for the

first call to one of these.

Note, as you would expect, only public fields are visible for use outside of the class itself.

Now lets move on a step or two, the following illustration shows a list of GUI elements, and this has come

from our new STATIC class.

Check them out, and compare the names with the image of the application form above - at run time.

We can see in the above image list that the Grids and TextBoxes of the nested elements have clearly

been found by our code.

However, at this point we are not really interested in any element which can't be edited by the user.

Moving on a little, we see in the following image that we have reduced our list to only eleven ( 11 ) elements

in total. These are in fact the seven on the main dialog form itself, and the two nested, and two more

nested even deeper.

Study the details shown in the following illustration :-

What we really need is to show the names of the 'TextBox' elements - this will come shortly.

First, we need to see that we can in fact quite easily identify those which have invalid contents - see below for details :-

And tidying up the identified - invalid - list we see the following :-

Ahaaaar ! That looks better.

So lets track the code to see what is going the 'important' bits.

Behind the push button click event we have this code :-

==============================================================================================

As you can, see line 183 is a call to a method in our STATIC class, called 'GetMyVisualElements'. To those newish

to Vulcan (from VO ?) the dot gives us the clue.

For any object that we instantiate in VN we have to use a colon ( : ) for our 'action' symbol.

Let us see what this STATIC method looks like - checkout below :-

========================================================================================================

Line 84 in the above image points us to where the important 'bit' of the work is done.

We appear to have another method which selects from a found list of elements, only those which are of type 'TextBox'.

Study carefully the details in the following code :-

==================================================================================================================

Once again we have a single line which seems to be the 'heart' of the matter - here it is line 123.

We appear to be using in line 123, a System STATIC class called 'Validation' and it is useful to see the SDK documentation

which is shown below :-

======================================================================================================================================================

There is not a public constructor to be seen !

However, we do have a couple of Properties and an event - see below :-

==============================================================================================================================

It would appear that as long as we supply a genuine 'FrameworkElement' object to the 'GetHasError' method we get back

a logical (Boolean) value.

Yes, it is easy to use, AND, it works too ;-0)

So far we have not looked in detail as to how we find the 'VisualTree' elements - lets do that now.

Finding the elements of the 'VisualTree' :

To put things simply, 'Visual' elements in the tree describing the WPF form, are created and placed in the Tree

when the form has been displayed at least once.

These elements are all of those possible from the elements placed on the Form, as well as all those that may also

arise from applying 'Templates'. At this moment in time I am not sure if 'Styles' can also do similar.

Anyway, we get many more 'Visual' elements than we often assume, by us focusing on just those GUI controls we

manually placed on the WPF designer surface.

Keeping this in mind we can now look at the small amount of code we need to make a 'Recursive' method for

our STATIC class.

Checkout below the 'FindVisualTree' method code - note commented lines are due to the project being not completed

as yet :-

===============================================================================================

In the above code line 164 was commented because it caused duplicate entries in the final long list. This was not at

first obvious because of the overall complexity.

Line 169 is the one doing the clever stuff - it uses yet another (System) STATIC class, and that called 'VisualTreeHelper'.

The SDK documentation is shown below - and once again no 'Constructor' is obvious to us at a 'Public' level.

The following illustration shows just methods, there are no properties in this class :-

================================================================================================================

In fact for our own STATIC class we are only going to use two of the methods - those shown above in red.

These have been used in lines 165 and 168 in our code shown in the image two above.

In fact line 165 finishes the recursion, as when the list of children to an element is zero, or empty, then no further

calls to 'GetChild' take place. The 'chain' is then completed because an 'end' has been found - the code can then

work back up the chain to the top, where the first recursive call was made.

A recursive method can always be spotted by the fact that somewhere in it, it calls a method of the same name as itself.

If you are even cleverer, you can spot a bit of code which terminates recursion, and this has to come in the method

before any subsequent calls to the same method. Ours is shown below :-

===========================================================================================

Once we have an empty list, that is, no more children of the Tree, then we do not execute the 'GetChild' code any

more times.

So what of the other members of the class ? - let's move on.

What of the Other Bits 'n' Bobs ? :

Well, it is quite simple to see that the purpose of the whole class revolves around the ability to take a 'FrameworkElement',

or indeed a GUI control - as we usually think of these - and find out if there are any children further down the Tree.

As we can now see with the aid of intellisense we do have quite a few supporting bits and pieces - see below :-

==============================================================================================================================

Any other properties and methods (and fields) are supplied / provided in the class to support the processing of the Visual

Tree. So we see facilities to include certain elements, and exclude others as well.

Obviously, we see methods related to selecting ONLY 'TextBox' elements, and finding if there are any Invalid cases, and if

so how many, and also a list of them.

Depending on your requirements we could add other members to the class, like stuff to do with 'levels'. As yet the level of

what I have found has not been of particular interest to me, so there are no members for that present.

No doubt the STATIC class will grow in time as I find more needs for it.

Time to move on, see you in the next subsection ...................

PHH - 6th. May 2012.


Supporting members - 'VT' Static Class

Like with many classes, STATIC and non-static, as well as the few 'main' methods to do the job in hand,

there seems to be a lot of others needed to 'support' those main methods.

We can see this in the 'Visual Tree' STATIC class we have so far been building up. If we are to allow certain

GUI elements to be added to the lists for 'Including' and 'Excluding', then we need methods to allow us to be

able to do this.

We also need methods to tidy the list for 'Include / Exclude', as well as re-setting the default lists.

In fact, by allowing the 'Include / Exclude' lists to be changed we add quite a few methods to our STATIC class.

To some of you who have not gotten their head into this class as much as me, you may think this all a bit

'over the top', or unnecessary, but you will find it does need to be done like this - once you get going.

If you are NOT convinced then take this as an opportunity to get more familiar with the methods and properties

of the 'List<T>' ( or in C# syntax - List(T) ) class.

The strongly typed List class is in the System.Collections.Generic namespace. Note the SDK docs say List<T>

represents a strongly typed list of objects.

Take a look at the SDK documentation details - some of which are shown below :-

=====================================================================================================================

Within this subsection we will take a chance to look into using some of these methods, some which carry out really important

and useful functions.

Those like 'Add', 'Remove', 'Contains', 'ForEach', and a few more as well.

Let us start to see how to use strongly typed lists to help us with our STATIC class supporting methods. It is a good

idea to start out with the 'AddTypeForInclude' method.

The 'AddTypeForInclude()' method :

Let us first see what our code does in the STATIC constructor, and what the default Types are - see below :-

==================================================================

So the very first time a STATIC method or property (or public field) is used from this class, the 'Constructor' is called

and run. This means the strongly typed list for Include and Exclude will be set.

Lines 38 through 45 show just which WPF GUI element types these are. Note line 44 is commented and only there

to help do the necessary testing, to show all is working OK - more on testing later.

So we can see that our 'Include' list has three types of element by default - TextBox, ComboBox and ListBox.

What if in the course of running our application we need to add a further GUI element ?

Study the code shown below :-

==================================================================================

*** Note *** line 106 is crucial to get right, as we need to be comparing the correct types of object. I had a bit

of a struggle with this line - to get it right !

As you can see from the above image we only add the supplied Type if it is an object, and also not already in the

list we have.

As this code stands we have not done any checks to see that it is a suitable Framework Element, but first things first!

Let us now see how to use this STATIC method in our application code - see below :-

==========================================================================================

When we run the few lines of code shown above, we get the following screen results, although I have used a little

bit of 'screen capture' magic to show it all in one go !

Checkout the following :-

So it would appear from the above 'composite' image that the 'Border' Type has now been added to our list.

The following code is that run by line 187 in the 'above but one' image - study this carefully below :-

==================================================================

So we see that we now have four list items and the fourth one is 'Border'.

=========================================================================================================================================

Notice that our test shows that we do not add multiple 'similar' items to our list, just the one. This is as it should be.

Moving on, what if we now wish to remove an item ?

The 'RemoveTypeForInclude()' method :

We shall see in the following code for the 'Remove' form of our method, that we use 'Contains' and 'Remove' form the

available List<T> class methods. 'Contains' is particularly useful.

Checkout the following image details :-

=================================================================================================

You need to realise that in the above code (as with other existing methods at the moment) I have included

some lines of test code, for printing useful messages and the likes. These will be got rid of in the final version

of the STATIC class.

Let us now run a couple of lines to test out the 'Remove' form of our method - see below :-

==================================================================================================================================

In the image above we are attempting to remove the 'ComboBox' object 'Type' from our default list of three items.

Note that the 'ListBox' and 'TextBox' remain after the running of the method code.

Time to move on - see you in the next subsection .....................

PHH - 6th. May 2012.


Appendix A - Finding Instances of SQL Server

The information shown in this subsection was first placed into a standard method in my main Window class.

However, when it became apparent that it was nothing to do with any connection detail, and would works

'as is' I decided to place it into the STATIC class for 'Layer6'.

First we will see the code as it was in the main class - study carefully :-

=====================================================================================================

In the above image we see code which produces a simple DataTable of all SQL data sources.

Obviously, once we have this data table we can extra the parts of the information we wish to see and use.

Below we see how we can extract and show the names of the sources, those names which are used in

the building of the Connection string, either manually or using the string builder .NET class.

Check this out below :-

============================================================================================================

And now if we compare our list with that we see when we launch Management Studio we get quite a favourable

match between the two list - study the following image carefully :-

========================================================================================================

[ .. ** Note ** the order in the drop list is that of the most recently used instances at the top .. ]

As we can see above the MS list is more comprehensive and I know holds some very old ones which I have

not seemed to use for YEARS.

In fact when I try to connect to some of these (like 'W7-BIG-T' ) I find I get an error as seen in the following

image - check it out thoroughly :-

==============================================================================================================

I have done some tests just to see 'what is what' and have found at least three which do not open, or put another

way, do not seem to be currently available on my system.

These are :-

W7-BIG-T

BEST-V64QUAD

BEST-V64QUAD\BEST

BEST-V64QUAD\SQLEXPRESS13

There does not appear to be any obvious / easy way to remove these listed instances which may or may not have

previously been found on a networked PC other than my development one being used here and now.

Also I can't figure out why some names appear twice in the MS dialog screen drop list.

What I can show you is an image of which ones I have been able to connect to using the MS front screen on this PC.

Check out the following it is most interesting - even had five different MS SQL Servers open at once ? :-

====================================================================================

The highlighted server is the one I currently use for all my new development work. Quite soon I will be adding 'Denali'

to my list, and use it as my preferred option. ('Denali' is the soon to be released 2012 version of Microsoft's SQL Server)

OK - think I have it ! :

What is confusing is that the drop list in the opening 'Connect to Server' dialog screen of MS is a 'saved' item list.

It is saved, and ordered it the order of which item was last used for an attempted opening. The item placed at the

top of the list goes there irrespective of whether it opened successfully.

And, just like in similar lists within VS itself, items are displayed whether they are 'active' or not. My term 'Active'

meaning that they can still be reloaded or re-used.

So this means that our .NET way of finding things out is most probably the most correct, and will give the most

up-to-date list of both Servers and Instances.

We ought to explore the dotNet approach in more detail.

Let us Continue with what we have :

In the following image we see that when we use the STATIC Layer6 method built around the same .NET code

as we saw above, we get the shorter, and more correct list of the current server instances - see below :-

==============================================================================================

However, if we look at another image, one which I captured when I first made the trials with the .NET code,

we see in that image (now shown below) that we are provided with three VERY important pieces of data.

Checkout the details in this image :-

============================================================================================

As you can see in the above image, it does not take much coding effort to allow us to display the 'DataTable' details

within a 'DataGrid' control, where we can view all of these important data items.

Now that I have modified the 'Connection for STATIC Layer6' form, we see the following, fuller details :-

In the above image we see that we have both Server name as well as Instance name for all of the meaningful 'local'

servers.

I say this because my wife has just turned on my old laptop downstairs and the 'THEDOC-PC' is on that machine.

Likewise the 'W7-BIG-T' is due to my email desktop system being on in my office.

Oh! should also say that 'Server 2005' is version 9.00.xxx and flavour '2008' is 10.0xxx with '2008 R2' being 10.50xxx

Obviously 'Denali' (Server 2012) will be 11.00xxx but my trial install has expired and so I can't show any images.

In order to make our 'Data Source' string for the SQL Connection string builder, we need to combine these first two

columns.

In fact it may be a better idea to do this within the drop list itself - we will have to see later. For now just notice that the

figure alongside the 'Server Instances' drop list shows eight ( ' 8 ' ) when we could really do with six, and removing

the two which fail to work as we would wish.

Okay then guys, I have made a few code adjustments and here is version '99' - see below :-

As you can see in the above image we now get only six entries in the drop list and all should be working

data sources as far as making a successful connection is concerned.

Let us leave our STATIC class and method for a moment, and look in some detail at the classes within the

Server namespace - see below :-

==========================================================================================================================================

However, when we study the above image we see no inclusion of 'SqlDataSourceEnumerator' class. The one we have just

used so successfully to find out the current instances and servers.

This is because it is in the System.Data.Sql namespace - checkout this below :-

================================================================================================================================

I think we all should read carefully, and take notice of, the 'Note' seem in the image above.

I also think that it is because of the Microsoft 'worry' about time to execute that they designed their Management Studio front

dialog screen to NOT go and make a fresh list from what it finds, but to keep a past 'used' list instead.

Now that machines are so much more powerful, and all attached hardware so much faster, it seems a better approach to actually

make an active search.

I have just run the live search on this mature PC development machine, with many old instances and server versions, and it took

no longer than eight seconds - yes, 8. And that is with a few machines on the networks as well.

Before we move on, let us look at the 'System.Data.Sql' namespace :-

=======================================================================================================================

I suggest you find time to look at the 'See Also - Reference' suggestion of 'System.Data.SqlClient'. there is some VERY good

and useful 'stuff' there !

Let us now look at Management Studio, and in particular the 'Connect to Server' dialog screen.

Management Studio - 'Browsing for more' :

As we 'guessed' above, the MS dialog form for choosing a server connection is designed to operate

in a 'dead' mode. By this we mean that it does not go out looking for 'live' or 'active' server details.

The drop list (as shown below) provides a 'last used' sorted list order, however, the final list entry,

that which say '<Browse for more ... >' gives us a further clue as to what is happening - check this out :-

When we select the 'Browse' option we find the software goes away and does lots of 'looking'.

Sometimes it takes a LONG time for the OK button to become activated, so be VERY patient

when using this option.

Note also that we have two tabs, one for Local, and the other for Network. However, even this is not

that obvious in its meaning.

It would appear to me that Local means on the PC in front of me, AND, not set for available use over

the network by any other user.

See the limited entries on my development machine - check image below :-

However, when I use the Network tab we see the following, and by now we will recognise

quite a few.

I would guess that these data sources shown above are locally situated on my development PC, but when

installed a setting was chosen to make them available on other network PCs.

Finally, here is an image captured on another PC on the network, the email machine.

See how in the network tab we get only the Server names, no instance names.

The three names are actually from the Windows names for the PCs themselves. Two in my office are

desktop PCs, and one a laptop downstairs.

'THEDOC-PC' is the name of the laptop, and it has working local server instances, as I have used them

at both VOUG meetings in Bristol, as well as VODC 2009/2010.

Fine, we have learned a lot doing this appendix, and it is time to move on to see if we can learn more

about the attachment of databases - this too is a bit of a mystery area ! ;-0)

See you in the next subsection ....................

PHH - 7th. May 2011.

PHH - Friday 13th. April 2012.


Appendix B - a note about 'escaping' in .NET

This appendix ought to be named "Escaped characters in regular Expressions and how to code them in .NET".

Yes, it is getting awkward in the first line of explanation, but that is the nature of the beast it would seem.

What we need to do is gradually move through this topic 'step by step' starting at the very simple. Then I feel

everyone will see what we are 'getting at'.

The Situation in Vulcan.NET - [ Literal Strings ] :

What may cause new users to Vulcan.NET some possible confusion is that when we wish to specify 'Literal strings' we have one way

which is consistent with VO (Visula Objects) and a second way which is support for "escaped" strings, which is the dotNet way apparently.

Because we are living in the .NET world (and when I use VN2 I like to use .NET all of the time) I like to use the 'e' or "escaped" way when

there is more than just a simple character string involved.

When we have to represent Regular Expression patterns in Literals, this is a good time to stick with the .NET approach I feel. We will see

text and information relevant to C# in many places, and so know how to read this stuff, and then write it in VN2 seems sensible to me ;-0)

So let us see what the Vulcan documentation has to say about "escaping" Literals - see below :-

===========================================================================================================================================

I have ignored the parts on Hexadecimal representation, as it seems less relevant to what we are discussing here for RegExs.

Using Foresight - or aiming at 'RegEx' pattern syntax :

This part I will do with the knowledge of what it is in RegEx (RE) patterns we have to 'get right' ;-0)

So if we look down the second column we see that the ' \ ' (or backslash) is the common character between RE and literal

"escaping" in VN2.

By this I mean that when we need to have a single ' \ ' for our RE patterns we will need to "escape" it in our dotNet code.

Time to show some examples I feel. Study the example shown below :-

=============================================================================

And now the case when we use ' e ' to force the "escape" syntax interpretation within the compiler - study this below :-

================================================================================

On line 59 shown above, the green character string within the double quotes is what we may see in C# books.

Note that the backslashes are 'doubled' for the 'escaped' string, but come out as single when used.

For some of us (me included) this clears up the mystery of why we were reading C# books and seeing filepath

strings in that 'strange' way.

If we look at a text book sample of a RE pattern we may see the following :-

[A-Z]{1,2}\d[A-Z\d]? \d[ABD-HJLNP-UW-Z]{2}

This is from the appendices of Ben Forta's book.

However, if we are to put it into an "escaped" format we need to double the backslashes when they are part of the RE syntax as

shown in the following documentation in my .NET 4.0 help file - see below, particularly the 'Note' at the foot :-

===============================================================================================================================

Okay then, lets take another example from the STATIC class for Ben's examples.

Check this out below :-

======================================================================================================================================================================

Notice that on line 79 the series of eight ' \n ' groups of characters signify a newline, and are not part of the RE pattern.

See the following image to see how we get a list displayed, so we must be getting newlines - check this out :-

=======================================================================================

However, when we need the RE pattern to be '\n' itself, we then need to 'escape' this as shown in the next image :-

=====================================================================================================

The backslashes in the 'Text2Search' mean we need a .NET newline, but the stuff in line 191 means

we wish the character string to be passed on as "\r\n\r\n" - don't worry, you will get used to it.

AND, if in doubt with any of this then just find good and working examples to copy and adapt, as well as

doing plenty of your own testing - there is no substitute for thorough testing.

Now see what this produces at run time - checkout the image shown below :-

Okay then guys, I have given you enough warnings and also suitable material to set you on your way.

So good luck, and don't forget to TEST !!

PHH - 6th. May 2012.


Character Classes

You need to read this if you are going to do any Regular Expression work :-

=================================================================================================================================

PHH - 6th. May 2012.


Appendix C - Attach - Tips and Tricks

 Top Previous

Okay then guys, we have made some progress on this topic since I wrote the Chapter 4 eNotes.

I can now successfully select and open each of the nine separate databases attached to the Server.

Yes, I can open all without any error box messages showing.

The Server is the 'SQL_DEV_08_R2' instance of the 'BEST-V64QUAD', which is just one of the six

available on this PC.

In the end it proved to be something relatively simple or straightforward - BUT - you had better know

about the situation, so that you don't waste hours going around and around.

Anyway, let us now look at the image of success - see below where one previous DB is being

accessed, and the tables found, while one table the 'Customer' one has its columns displayed.

Check this out :-

Yes, in the above image we are able to do what alluded me for some long, and I have manually checked through

all of the others as well.

Have a look at the OE pane in Management Studio to see if you get a clue as to what I have done to change things :-

==========================================================================================================================

Yep! For each of the databases I had previously renamed, with a short and friendly name, I have reverted to the original

full file-path name, and all seems to work fine.

PHH - 7th. May 2012.