Thursday, April 29, 2010

Microsoft Enterprise Library Caching Gotcha

Recently, a developer where I work added caching to some methods that retrieve contacts from a table in our database. Afterwords, a tester complained that when he went to one screen that allows selecting a contact, he saw the text "[Select a contact]" repeated. Not only that, every time he returned to that screen, the problem became worse.

Before caching was added, the method that retrieves the list of contacts got them from the database, then it added the text "[Select a contact]" to the beginning of the list. When caching was added, the list was retrieved from the cache (if available), and then the text "[Select a contact]" was added to the beginning.

It turns out that when a list is added to the cache, and then later the list is modified, that modified list will be returned the next time the cache is accessed. Here is some sample code to make it more clear:

private void btnTestCaching_Click(object sender, EventArgs e)
{
List<string> testList = TestCaching(1);
PrintList(testList);
testList = TestCaching(2);
PrintList(testList);
}

private void PrintList(List<string> testList)
{
foreach (string s in testList)
{
Debug.WriteLine(s);
}
}

private List<string> TestCaching(int id)
{
ICacheManager cache = CacheFactory.GetCacheManager();
List<string> testList = cache["TestList"] as List<string>;
if (testList == null)
{
testList = new List<string>();
testList.Add("Test 1");
testList.Add("Test 2");
testList.Add("Test 3");

AbsoluteTime expiry = new AbsoluteTime(new TimeSpan(2, 0, 0, 0));
cache.Add("TestList", testList, CacheItemPriority.High, null, new ICacheItemExpiration[] { expiry });
}
testList.Add("[Select a contact] " + id.ToString());
return testList;
}
I added a counter to make it slightly more clear what's happening. Here's the output from Visual Studio:

Test 1
Test 2
Test 3
[Select a contact] 1
Test 1
Test 2
Test 3
[Select a contact] 1
[Select a contact] 2


I was surprised by the outcome. I thought that the list would be persisted only when cache.Add() is called, apparently that's not the case.

Thursday, April 15, 2010

Viewing GAC Assemblies in an Explorer Window

I saw a neat trick somewhere on the internet...at the moment I can't find the original source. I like to give attributions where possible, but oh well.

As many developers probably know, it's possible to navigate to the GAC by using the command line to go to C:\Windows\Assembly (this is on Windows Server 2003).

However, it's also possible to get Explorer to show items in the GAC. All of the assemblies I have created with Visual Studio end up in the GAC_MSIL folder. I can take the following steps to view them: click Start / Run, then key in "%SYSTEMROOT%\assembly\gac_msil", and click OK.

An Explorer window will pop up showing all of the assemblies in that part of the GAC:



Why would you want to do this? This trick allows you to debug a DLL directly from the GAC. You can now navigate to the exact location of the DLL, and then drop the .PDB in the same folder. If you then have the project for the DLL open in Visual Studio, you can attach to the process calling your DLL and set breakpoints, etc.

This doesn't always work smoothly, but there are some situations where it was the only way to get the job done. In particular, if the client for the DLL is a program like BizTalk or SharePoint, it's the only way I could debug the DLL.

I have encountered at least one time when I had to reboot to be able to set breakpoints, but it beat the hell out of my alternatives.

Remember to remove the PDB from the GAC before you try to UN-install your DLL.

Thursday, February 11, 2010

T-SQL UPDATE with FROM

I'm putting the following query here where I can find it again...it basically shows how the T-SQL "UPDATE" statement works with the FROM option. It's from a response to an old newsgroup post of mine -- thanks to David Pendleton!

UPDATE dest
SET accountNumber = src.ACCT
FROM Vendor dest
INNER JOIN zCustomer src on dest.[name] = src.CUNA

Monday, April 27, 2009

Today I was working with a pipeline component which encrypted fields in an XML message, for sensitive information. I realized that I needed to use it with a Flat File disassembler, which means that the original message was not XML, so I had to put my component after the Flat File disassembler. For various reasons, I wanted to use my component no later than the Dissasembler stage for this scenario.

So first, I added IDisassemblerComponent to the list of interfaces being implemented in my pipeline component class.

Then, I added the following code:


private bool beginMessage = false;
private IPipelineContext pContext;
private IBaseMessage pInMsg;

void IDisassemblerComponent.Disassemble(IPipelineContext pContext, IBaseMessage pInMsg)
{
beginMessage = true;
this.pContext = pContext;
this.pInMsg = pInMsg;
}

IBaseMessage IDisassemblerComponent.GetNext(IPipelineContext pContext)
{
if (beginMessage)
{
beginMessage = false;
return Execute(pContext, pInMsg);
}
else
{
return null;
}
}


This will only work if the previous disassembler is supposed to produce one message.




4/29/2010 - Changed the code to reflect changes suggested by Johan Rex...

Wednesday, October 29, 2008

Find a Specific Value Anywhere in a SQL Server Database

I can't tell you how often in the past I could have used this script from David Yardy:
http://blog.davidyardy.com/archive/2008/08/16/sql-server-ndash-find-field-value-in-database.aspx
I can find a string value anywhere it exists in a field in my database. As he says, it can easily be modified to search for values that are not strings.

Thursday, October 09, 2008

ASP.Net Enabling the Session Object

Today I was attempting to use the Session object to exchange information between two ASP .Net pages. I received the following exception when I tried to assign something to it:

System.Web.HttpException was unhandled by user code
Message="Session state can only be used when enableSessionState is set to true, either in a configuration file or in the Page directive. Please also make sure that System.Web.SessionStateModule or a custom session state module is included in the \\\\ section in the application configuration."
Source="System.Web"
ErrorCode=-2147467259
StackTrace:
at System.Web.UI.Page.get_Session()
at AddToCart.SaveCustomFields() in c:\Inetpub\wwwroot\StarterSite\AddToCart.aspx.cs:line 485
at AddToCart.AddItemToCart() in c:\Inetpub\wwwroot\StarterSite\AddToCart.aspx.cs:line 490
at AddToCart.btnAddToCart_Click(Object sender, EventArgs e) in c:\Inetpub\wwwroot\StarterSite\AddToCart.aspx.cs:line 300
at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
at System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
at System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)


Based on information I found on the web, I first tried to set EnableSessionState="True" on every page that needed to use the session, and that didn't cure the issue.

Next, I tried setting <pages enableSessionState="true"> in the Web.config file under configuration/system.web. That didn't fix it either.

Finally, what worked was to put this into the Web.config:
<sessionState mode="InProc"></sessionState>
(also under configuration/system.web).

Thursday, September 04, 2008

SQL Server 2000 - Finding a Field in a Database

Today I was looking for a field in a SQL Server 2000 database which had lots of tables. I knew the name of the field, but I didn't know the table name. So I modified the code contained in master.dbo.sp_columns to find it. It turns out that in my case, I was looking for a field that didn't exist. Hopefully you'll have better luck.

To use this, replace the words your field here in the next-to-last line.

SELECT
TABLE_QUALIFIER = convert(sysname,DB_NAME()),
TABLE_OWNER = convert(sysname,USER_NAME(o.uid)),
TABLE_NAME = convert(sysname,o.name),
COLUMN_NAME = convert(sysname,c.name),
d.DATA_TYPE,
convert (sysname,case
when t.xusertype > 255 then t.name
else d.TYPE_NAME collate database_default
end) TYPE_NAME,
convert(int,case
when d.DATA_TYPE in (6,7) then d.data_precision /* FLOAT/REAL */
else OdbcPrec(c.xtype,c.length,c.xprec)
end) "PRECISION",
convert(int,case
when type_name(d.ss_dtype) IN ('numeric','decimal') then /* decimal/numeric types */
OdbcPrec(c.xtype,c.length,c.xprec)+2
else
isnull(d.length, c.length)
end) LENGTH,
SCALE = convert(smallint, OdbcScale(c.xtype,c.xscale)),
d.RADIX,
NULLABLE = convert(smallint, ColumnProperty (c.id, c.name, 'AllowsNull')),
REMARKS = convert(varchar(254),null), /* Remarks are NULL */
COLUMN_DEF = text,
d.SQL_DATA_TYPE,
d.SQL_DATETIME_SUB,
CHAR_OCTET_LENGTH = isnull(d.length, c.length)+d.charbin,
ORDINAL_POSITION = convert(int,
(
select count(*)
from syscolumns sc
where sc.id = c.id
AND sc.number = c.number
AND sc.colid <= c.colid
)),
IS_NULLABLE = convert(varchar(254),
substring('NO YES',(ColumnProperty (c.id, c.name, 'AllowsNull')*3)+1,3)),
SS_DATA_TYPE = c.type
FROM
sysobjects o,
master.dbo.spt_datatype_info d,
systypes t,
syscolumns c
LEFT OUTER JOIN syscomments m on c.cdefault = m.id
AND m.colid = 1
WHERE
-- o.id = @table_id
c.id = o.id
AND t.xtype = d.ss_dtype
AND c.length = isnull(d.fixlen, c.length)
AND (o.type not in ('P', 'FN', 'TF', 'IF') OR (o.type in ('TF', 'IF') and c.number = 0))
AND isnull(d.AUTO_INCREMENT,0) = isnull(ColumnProperty (c.id, c.name, 'IsIdentity'),0)
AND c.xusertype = t.xusertype
AND c.name like '%your field here%'