Real dates
We changed our datetime format from seconds-since-1970 to a real datetime. So now we can search for dateparts, all the databases support it.Using an
ArgumentFunction means that the return field no longer has a name (it’s enclosed by the function). Therefore we use the Alias.Name to give it a name that we can then use when calling Reader.GetInt32(). You could always use a position index instead, but that makes the code much more fragile in the face of changes.DateTime - new from SuperOffice v.7
Up to version 7, we stored date as an integer, 1-1-1970 + n seconds, giving us the “end of the world” in 19.1.2038 at 03:14:07. Now use the built-in datetime datatype in the database which makes things a lot more legible, and makes it possible to search for things like dateparts (year). NetServer has new low-level functions to do this, not yet used in our own code. One problem: How do you say “unknown” without saying “NULL”? The one good thing about the old posix datetime was that there was an obvious “beginning of time” value: 0. Datetime has no such natural starting point; not least because the different databases do not agree. SQL Server says 1.1.1753; Sybase and DB/2 say 1.1.0001; Oracle says 1.1.4712BC 1.1.1760 is a good compromise, and not likely to conflict with a real date, this corresponds to “0” from the date-as-int past 31.12.9999 is DateTime.MaxValue and means “forever”. Anything in between is a real date - but the Windows code still works with posix times internally so this means 1970 -> 2038 still applies. In the code, we convert from 1.1.1760 to 1.1.1970 (c++) or DateTime.MinValue (C#) automatically. The code and applications dependent on it see no change from what things were like earlier. Since the C++ still works with posixdate_t internally, we still have the same limitations. Overcoming these is a big project that relates to all our C++ and Windows GUI code; and it just was not worth the risk and effort in this round.
User-defined fields are not being changed in 7.0 and here Datetime is still mapped to an int.
UnlimitedDate also stays the same, as a string internally (YYYYMMDD).
Aliasing on ArgumentFunctions
You can now set an alias name on an ArgumentFunction Such as Day, Count, …- Earlier, you could say Count(fieldInfo) with an alias on the fieldInfo.
- Now you can set the alias on the function itself, making it possible to use the same fieldInfo in multiple functions - such as dateparts
QueryExecutionHelper
A utility class that handles connections and commands.- Exception-safe cleanup through using clause
- Nice clean code (saves 2 nesting levels and some try-catch noise)
- Many overloads allow customized behavior
-
Can be used for executing non-query commands
QueryExecutionHelper.ExecuteNonQuery(myInsert) -
Can be used for fetching scalar values
int i = .ExecuteTypedScalar<int>(mySql)
OSQL meets Sentry
The SQL we generate may not be what you expected. The previous example code generated… what?AppointmentSentry adds join to VisibleFor, and fetches the fields it needs.
GroupBy and Sentry
Remember that in a grouped query, all return fields must either be aggregates or GROUP BY. Your query adheres to this, but once the Sentry system modifies it, it may break. You can avoid this by adding the return fields and join yourself.- Sentry will pick up the fields; it only adds when it has to
- But this may change the meaning of your query
IgnoreSentry turns off Sentry, so your query is generated “untouched”
NewSelect
The code saysSoftTriggers
NetServer has its own OSQL interception system. Used by Sentry, travel transaction logging, free-text index, … You can have one too - on insert/update/delete. SoftTrigger defines a way to set a pre- or post-execution callback, for a specific table.-
“Before” is after
TravelTransactionLogger,SoundexUpdater, Sentry, Registered/LastUpdated, and TimeZone have been there. You can change the OSQL (very carefully) - “After” is, well, after. That would probably be just to log it.
FieldInfo comments
Row improvements
Get functions
For each unique index, we generate a .GetFromIdx( … ) method on the Row. For each non-unique index, we generate a .GetFromIdx( … ) on the Rows collection. Use them if you can, the indexes guarantee a quick lookup.Row objects from reader
-
ReturnFields.Addhas an overload that takes aTableInfo[], so you can add all fields in a table by one parameter. You can of course add any other odd fields you want as well (in a separate .Add) - Select (and many other places that have restrictions) has a RestrictionAnd(r), which is short form for if( Restriction == null ) Restriction = r else Restriction = Restriction.And(r). This again eases flow and makes the code easier to read
- You can construct Row objects from a reader, just as long as you have all the fields available. This will also transfer all Sentry info
- You can also GetXXX any fields at any time, of course
Row documentation
We have improved the documentation on the Row/Rows objectsCollectionOps
A collection of static methods for manipulation of collections-
To/from
Dictionary<> - NamedValue strings
- Compare and massage arrays
Arrays
[2, 1, 4] is equiv to [1, 2, 4]
ConvertArrayapplies a converter delegate to each element. But nowadays you can use LINQ to do thatAddToArraymakes a new, longer array- Consider using
List<>;but sometimes you’re stuck with arrays…
Dictionary of Lists
I quite often find myself using a dictionary, where the value is a listDictionaries
CreateDictionaryFromXXX( … ) - many overloads- Take some kind of collection of items
- Apply a delegate to each item, to extract/make a key
- Add the key and the item to a dictionary
ToDictionary<> extension method, but ours was first.
ParameterBuilder
A class that will take n string items and concatenate them with front, middle, and end delimiters.- Eliminates all those pesky if( !first ) AddComma(); first = false; constructions
- Has a static method that does everything at once
- Use it to make useful error messages!
How to build an error message
Restrictions is anArchiveRestrictionInfo[]
ArchiveRestrictionInfo has an overridden ToString method, meant for debugging, this is easy and useful. So easy that you should really do it.
Making a tooltip
CultureDataFormatter
What does “1,000” mean?- “one point zero zero zero”?
- “one thousand”?
CultureDataFormatter, in SoCore, is your friend when faced with such a string
The parsing methods are quite format-tolerant. In addition to strings like “[I:1234]”, they will also accept simply “1234”.
LocalizeXXX methods return formatted strings, using the current culture. You can selectively encode datetime, date, or time
ArchiveProviders use this format for the DisplayText return value. If you make your own archive provider components, you should do so too
Multi-threading
In the general case, multi-threading/parallel programming is hard. Why?- Almost always because of synchronization issues
- And because the syntax for working with threads tends to be complicated NetServer uses multi-threading internally, and we have some useful infrastructure
- .NET 4.0 and the Task Parallel Library are also very nice
Easy Multi-threading
If your problem can be partitioned into independent parts, then you’re home-free.- Put each part in a method
- Call the methods in parallel using the ThreadManager
- Use the results
Reading two files
- The two lambdas (statements) are done simultaneously, each in its own thread
- The main thread waits until both complete
- ThreadManager.Invoke will then return
Why use NetServer ThreadManager
- SoContext, SoDatabase, and other variables that are in the environment are managed for you.
- If you Impersonate inside a thread, we keep track of that.
- If multi-threading is disabled in the config file, your code will be nicely executed in sequence.
- We perform throttling to avoid killing the machine with a gazillion threads.
When to single-thread
Starting and stopping threads is not free. You should expect significant savings before resorting to multi-threading. Synchronization is hard. If your problem is in any way dependent on who finishes first, or there are shared data structures… then you are in the sync world Do you have multiple cores, or are you waiting for external data? If not, then there is no point in multi-threading. Again, it all depends on your problem. Multi-threading is a powerful tool and you should know about it, and about what NetServer can do to help you. Beyond that, it’s your call.Multi-thread database reading
ThreadManager.Invoke call that does the magic. Its parameter is an array of Actions, where an Action is a delegate that takes no parameters and returns void (a method that simply does something). Then we use the lamda syntax to eliminate the syntactical hassle of saying new Action( MyMethod ) and having to write the methods elsewhere: () => simply means “I declare a parameterless block of code, which is as follows”.
The
firstSearch and lastSearch local variables are “captured” into the lambda, and become part of the code that is sent off to the Invoke method for execution. This is a very powerful mechanism, called scope capture, which you can use to pass along all kinds of values. But beware of one thing: Never, ever update such values from inside the parallel code, if they are shared between multiple methods. That would break the initial assumption, that your parallel tasks are independent. As soon as they share any kind of variable, that is no longer true and YOU become responsible for synchronizing access.Alternative approach
ArchiveRestrictions
There is an implicit “AND” between archive restrictions New in 7: It doesn’t have to be.- You can say “OR”
- You can add parentheses:
Dynamic archive provider
The dynamic archive provider supports searches across the relationships defined in the dictionary without having to do any programming. Each field name specifies the query - the table relationships to traverse to read the field. You can fetch the name and department properties on the contact table like this:business_idx field on contact: