Sunday, June 3, 2012

I've stopped using RoboForm

I stopped using RoboForm at version 6.9.

Don't get me wrong...  I loved RoboForm.  It was almost seamless in it's integration with the browser.  I had hundreds of passwords stored and negligible delay.  I could generate a new password in a couple of clicks.  I even ignored websites that wouldn't work with RoboForm.

But then RoboForm decided that my passwords were their data, to store on their servers.  So when Apple or WalMart bought them, then they owned my passwords. Or when Russia or China or Houston hacked them, then I'm pwned.

So now I use KeePass.  The browser responsiveness suffers. But my security -- the whole point of passwords -- is my responsibility again.

Thursday, September 4, 2008

Lazy Loading Can Cause Performance Problems

A customer rushed into LINQ to SQL without fully understanding the consequences, and was having performance problems with their app. The problem: not being cognizant of what Lazy Loading was doing.

So here is a simple example, using the Northwind database.

Suppose you have a DTO like this:

public class DTO
{
public string CustomerID { get; set; }
public int OrderID { get; set; }
public DateTime? OrderDate { get; set; }
public int NumItems { get; set; }

public DTO (string customerID, int orderID, DateTime? orderDate, int numItems)
{
CustomerID = customerID;
OrderID = orderID;
OrderDate = orderDate;
NumItems = numItems;
}

public static ICollection GetDTO(string customerID)
{
ICollection dtos = new Collection();
using (NorthwindDataContext context = new NorthwindDataContext())
{
var query = from o in context.Orders
where o.CustomerID == customerID
orderby o.OrderDate descending
select o;

foreach (var q in query) dtos.Add(new DTO(q.CustomerID,
q.OrderID, q.OrderDate, q.Order_Details.Count));
}
return dtos;
}
}

Never mind the select o for the moment. Some history: the requirement for NumItems was a change to the original code. A second programmer added the NumItems property, added the numItems parameter to the constructor, and added the q.Order_Details.Count to the foreach statement. Why should that matter?

Because now the app is going to the database multiple times. Before the change, he was going to the database once, when evaluating var q in query:

SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate],
[t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight],
[t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion],
[t0].[ShipPostalCode], [t0].[ShipCountry]
FROM [dbo].[Orders] AS [t0]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[OrderDate] DESC
-- @p0: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [BOLID]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8


but now the app goes to the database once for the var q in query and once each time in the loop

SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate],
[t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight],
[t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion],
[t0].[ShipPostalCode], [t0].[ShipCountry]
FROM [dbo].[Orders] AS [t0]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[OrderDate] DESC
-- @p0: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [BOLID]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
FROM [dbo].[Order Details] AS [t0]
WHERE [t0].[OrderID] = @p0
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [10970]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
FROM [dbo].[Order Details] AS [t0]
WHERE [t0].[OrderID] = @p0
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [10801]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
FROM [dbo].[Order Details] AS [t0]
WHERE [t0].[OrderID] = @p0
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [10326]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

and the app, unlike this example, was looping thousands of times.


Agreed, the first programmer shouldn't have coded select o -- it's the LINQ equivalent of SQL's SELECT *. But although he was returning many more columns than he needed to, he only did it once.


How should the second programmer have coded his query? Specify the count in the select clause, like this:


var query = from o in context.Orders
where o.CustomerID == customerID
orderby o.OrderDate descending
select new {o.CustomerID, o.OrderID, o.OrderDate,
o.Order_Details.Count};

foreach (var q in query) dtos.Add(new DTO(q.CustomerID,
q.OrderID, q.OrderDate, q.Count));


Then you only go to the database once.


SELECT [t0].[CustomerID], [t0].[OrderID], [t0].[OrderDate], (
SELECT COUNT(*)
FROM [dbo].[Order Details] AS [t1]
WHERE [t1].[OrderID] = [t0].[OrderID]
) AS [Count]
FROM [dbo].[Orders] AS [t0]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[OrderDate] DESC
-- @p0: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [BOLID]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Thursday, August 16, 2007

Debugging Web Services on Your Local Machine

Scenario: Visual Studio 2005, one solution, two projects; one project is your Web Service project, the other your client. IIS is not on your client.
  • Clicking "Properties" of the Web Services project, click the "Web" tab. Change the entry under "Use Visual Studio Development Server" from "Auto-assign Port" to "Specific port".
  • Clicking "Set Startup Projects" of the solution, change the entry from "Current selection" to "Multiple startup projects." Change the Action from "None" to "Start."
  • Make sure your client project is set up as the Startup Project.
  • If you haven't already, add the Web Reference in your client project, choosing "Web Services in this solution."
  • Make sure that your dependencies are set so that your client project is dependent upon your Web Services project.
Now when you debug, each time you press F5 you will start up the Cassini web server at the port you hard coded in Step 1; then your client project will run and connect to the service. Terminating the debugging session will terminate both your client and the Web Services projects.

This could eliminate your getting the "System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it" exception in the Reference.cs file.

Monday, May 21, 2007

CruiseControl.NET VSS SSDIR

Switched from a test SourceSafe database to production, which was on a separate machine. The ccnet.config file used to have a line that specified the srcsafe.ini file, saying
<ssdir>C:\local\srcsafe.ini</ssdir>
which worked fine.


I switched it to
<ssdir>\\remotemach\vssdb\srcsafe.ini</ssdir>
and started getting the message
ThoughtWorks.CruiseControl.Core.CruiseControlException: Source control operation failed: No VSS database (srcsafe.ini) found. Use the SSDIR environment variable or run netsetup.

I stumbled around for a while, but finally changed the <ssdir> tag to just the directory, removing the srcsafe.ini file, as in
<ssdir>\\remotemach\vssdb\</ssdir>
and that got it working.

Friday, May 18, 2007

Batch FxCop and Visual Studio's Website Project

Using Continuous Integration, you want to run FxCop against the build. Using Visual Studio's new "Website" project (Rick Strahl has some commentary about it), the aspnet_compiler creates DLLs with random names embedded. This is good, because it's unlikely that that DLL is cached anywhere. But it's bad, because FxCop wants to know what the DLL names are.

There's probably a better way to handle it, but we simply run aspnet_compiler twice: the first time with the -fixednames parameter, running FxCop against that build, then the second time without the -fixednames parameter generating uncached DLLs.

Monday, May 14, 2007

NAnt String Concatenation

I'm not familiar with Ant, but in NAnt, the plus sign can act as a string concatenation operator, so you can say something like

<mkdir dir="${work-dir}\Svcs" if="${not directory::exists(work-dir+'\Svcs')}">

You can also use the Concat function. Both are documented.

Sunday, May 13, 2007

Thunderbird / SpamPal / SSL / STunnel / 2 ISPs

Lots of good info setting up SpamPal with STunnel to communicate with ISPs requiring SSL for email. But recently, my ISP also required SSL, and I was already using the STunnel connection for Google.

Not a problem. Just make two entries in your stunnel.conf file, like so:

; incoming email from GMail
[pop3gm]
accept = 127.0.0.1:1110
connect = pop.gmail.com:995

; incoming email from ISP
[pop3is]
accept = 127.0.0.1:1111
connect = pop.myisp.com:995

In Thunderbird, the "User Name" in the Server Settings of Account Settings is My.Name@gmail.com@localhost:1110 for the GMail account, and My.Name@myisp.com@localhost:1111 for the ISP.