Here I will post problems I and my colleagues met and solutions we found.

Friday, February 25, 2005

Designing .NET Class Libraries: Designing Progressive APIs

I found this lecture interesting.

http://msdn.microsoft.com/netframework/programming/classlibraries/designingprogressiveAPIs/

Favorite parts:

OO design methodologies are not good for API design. It should be started not in Visio, but by writing samples.

Aggregate components. This means that for particular task we can define particular component as part of bigger one. Like StreamReader can be part of file. Once all options of file is set all we need is to read data.

API should be progressive. This means new user should be able to use it, and while he is becoming more advanced, he will discover new features.

And classification of programmers is funny.

TabControl with hidden page buttons

Sometime we used TPageControl in Delphi to switch between views in the same form. We did not want to show any tabs or buttons it this scenario.

To apply same technique in .NET, different set of properties should be used. Here they are:
TabControl.SizeMode = Fixed
TabControl.ItemSize = (0, 1);
TabConrol.Appearence = Buttons;

Thursday, February 24, 2005

Insert into dependent parents II

So, since I had to insert data into table contacts first, insert data into table contact_addresses next and update table contacts after that, why not to try to do it just in application itself, not in SQL statements.

I read help files and found that all I had to do is to use GetChanges() method get new rows from table contast, use DataAdapter to update them. Then Update rows from table addresses and then update rows from contacts again, even if they were inserted before.

Whell, it did not work. Here is why.

What I did was:

DataTable newContacts = contactsTabe.GetChanges(DataRowState.Add);
contactsDataAdapter.Update(newContacts);
addressesDataAdapter.Update(addressesTable);
contactDataAdapter.Update(contacsTable);

Now what GetChanges() method does? It creates Clone for table, and this table does not belong to DataSet!!! All rows are copies of rows from contactsTable. It had two effects.
1) When new Id values were returned from server, there were no information about any DataRelations, so these Id values were not propagated to child records.
2) These values were not returned to contactTable.

Then I used different approach which is the last one. At least so far.

1. Update
SetInsertOnly(true);
try
{

contactsDataAdapter.InsertCommand =
contactsInsertCommand;

contactsDataAdapter.Update(dataSet, "contacts");
}
finally
{

SetInsertOnly(false);
}
addressDataAdapter.Update(dataSet, "contact_addresses");
// know update the rest
contactsDataAdapter.InsertCommand = contactsUpdateCommand;
contactsDataAdapter.Update(dataSet, "contacts");


2.In RowUpdating event handler

if (insertOnly)
{

if (e.StatementType != StatementType.Insert)
e.Status = UpdateStatus.SkipCurrentRow;
}
Using this flag I indicated that only new rows would be affected in insert only mode. SQL stamtement in this case was very simple, just creating record in table contacts and getting id.
3.in RowUpdated event handler in InsertOnly mode I set

if (e.StatementType == StatementType.Insert)
e.Status = UpdateStatus.SkipCurrentRow;

This way row was not marked as updated because AcceptChanges was not called.

4. For adresses RowUpdated event handler
if (e.StatementType == StatementType.Insert)
{
if (e.Row.HasVersion(DataRowVersion.Original))
{

DataRow r = e.Row.GetParentRow(e.Row.Table.ParentRelations[0]);
if (r != null)
{
object val = r["primary_address_id"];
if (!Convert.IsDBNull(val))
{
if (Convert.ToInt32(val) == Convert.ToInt32(e.Row["contact_address_id",
DataRowVersion.Original]))
r["primary_address_id"] =
e.Row["contact_address_id", DataRowVersion.Current];
}
}
}
}


This was to get new primary_id values
from contact_addresses table to contact table.

BTW, cannot save formatting for source code. This blogs just clears all spaces at the beginning of lines :(

Insert into dependent parents I

In previous Master/Detail topic I described how to use relations to populate details with values from masters. However, what if master has reference to details too? For example:
contacts table has field contact_id (PK) and primary_address_id (FK), which references to contact_addresses table with primary key (contact_id, contact_address_id).

If we have new records in table contacts and table contact_addresses, how would we post them using DataAdapter components?

Before starting some notes. DataSet was set to have all id values generated on client side to be negative. This way there will no be conflicts with server generated value and I can always tell where this value was generated.

My first approach was this one:
Insert or Update statements for contacts looked like:

if @primary_address_id < 0
begin
@old_primary_address_id = @primary_address_id
insert into into contacts (...) // except field primary_address_id;
set @contact_id = @@identity;
insert into contact_addresses (...) // for primary_
set @primary_address_id = @@identity;
end;
update contacs set primary_address_id = @primary_address_id;

So, besically I tried to insert fake record into contact_addresses table.

Later in RowUpdated event for contacts table I got old_primary_address_id and primary_address_id values and corrected recotd in contact_addresses table;

After that for contact_addresses table insert looked like
if @primary_addres_id < 0)
insert into contact_addresses(...)
else
update contact_addresses ...

So, I could easily use
contactsDataAdapter.Update(contactTable);
addressesDataAdapter.Update(addressesTable);

But later I decided to try different approach and I liked it better...

Wednesday, February 23, 2005

Feed

I finally understood how to use RSS and Atom feeds. There is "Atom side feed" link http://discoveringdotnet.blogspot.com/atom.xml on side bar that you can use. Checked it in RSSReader (www.rssreader.com), it works.

Application.EnableVisualStyles

There is method Application.EnableVisualStyles to support themes in your .NET windows application. However, when we tried to create logon dialog and show it before activating main form, this method did nothing.

Solution was found here http://blogs.msdn.com/rprabhu/archive/2003/09/28/56540.aspx#280809

The problem was that this method does nothing but set some flags and actual processing happens with windows message processing. If some windows handle were created by this time, it may be to late. Application.DoEvents() method may help.

Actually, I still see that sometimes comboxes (and only comboboxes) are drawn without theme support, ant no pattern was identified yet. This problem is waiting to be solved.

In this link I found one interesting comment about System.Runtime.InteropServices.SEHException. Be aware.

Numbers like objects

Sometime it is very convinient that object can be container for anything, including number. But...

I understand how why it happens but I still don't like it. Here is what I am talking about:

int i16 = 2;
int i32 = 2;
object obj16 = 2;
object obj32 = 2;
bool b;
b = (i16 == i32);
MessageBox.Show("i16 == i32 " + b.ToString()); // result is true
b = (obj16 == obj32);
MessageBox.Show("obj16 == obj32 " + b.ToString()); // result is false

It made me think I am getting crazy once. DataRow returns your result as object. And actual type may depend on type of column in table in database. In my case one column was declared as int, another one as shortint. In DevExpress (www.devexpress.com) grid I tried to apply particular style depending on data value. And value to compare was stored as object. Well, you figured out what happened then...

Tuesday, February 22, 2005

Master/detail part II

When first problem was resolved I had another one. This one took much time for me to figure out workaround, although, I had to find it faster.

Anyway. The problem was that after inserting from previous topic was done and all data were posted to the server, grid with addresses became empty. I checked data in DataView, in DataTable - data were correct. Later I found that if position was changed in master, details were updated.

I tried to call BindingContext[dataSource, dataMember].Refresh(), where dataMember was the name of my relation, it did not help. So, I did not find anything better but to write something like this

m = BindingContext[dataSource];
int p = m.Position;
m.Position = 0;
m.Position = p;

BTW, for some reasons this code made my data in Edit mode again, so I added
m.CancelCurrentEdit(), because I was sure I did not edit anything.

But later I found very good article that described this problem and recommended replace it the
m.Refresh().
m.CancelCurrentEdit(). // still need this one.

Now this looks obvious for me and I don't know how could I not to try it myself. Anyway, it looks like bug in .NET, I would prefer not to do anything.

Here is the article I was talking about, recommend it very much:
http://msdn.microsoft.com/library/en-us/dnwinforms/html/databinding_winforms10_11.asp?frame=true&_r=1

This problem is discussed in Chapter When to Call CurrencyManager.Refresh, but I recommend to read whole article.

Master/detail part I

My first problems, or better to say question, began with master detail. At the beginning, everything looked very simple from documentation. The key word is DataRelation, everything seemed obvious.

However, in my case I had two tables, contacts with primary key contact_id, which was autoident field and contact_addresses with primary key contact_id and comtact_address_id, where contact_address_id was autoident field. Both tables were created in one dataset and relation was set.

To get id values from server, my insert SQL command followed by "select contact_id, .... from contacts where contact_id = @@identity";
Same for contact_addresses.

My first mistake was that I cleared Update rule to None. In result, when new contact_id value was obtained from server, it remained old one in details.

You may find detail description of this problem and solution here:
http://www.dotnet247.com/247reference/msgs/47/237357.aspx

Monday, February 21, 2005

Introduction

It has been two months since I started programming using .NET. I would like to keep my experience written. It may be useful for myself or others.

My background is several years of programming in Delphi prior to .NET versions, including database applications and component writing. So, sometimes I may compare .NET with Delphi.