Sunday, September 6, 2009
NO MORE BLOGGER
I am now using WORDPRESS for my hamletcode blogging activity.
Both of you, please update you RSS READERS to
http://hamletcode.wordpress.com
Thank you!
Code Test
I’m switching to Wordpress.
Trying a code sample….
1: namespace YaddaYadda
2: {
3: public class Whatever
4: {
5: public void DoSomething()
6: {
7: }
8: }
9: }
Wednesday, September 2, 2009
So what’s the deal with the thread blocking in vs2010/.net4?
This was weird. I didn’t say anything in last night’s post because I wanted to make sure I wasn’t insane. I’m pretty sure I’m not.
Anyhoo, I created a stupid little windows form application to host my Wcf Service and to also to create a client.
The PAGE_LOAD calls a StartServer() method, which creates the service host.
The form has a TEST button that creates a WCF client to the same service.
That’s all pretty common stuff; start a server, create a client. But, the client kept reporting a Wcf timeout after 1 minute. I was able to connect to the service from another process, but not from within the windows form. It seemed like maybe a threading issue.
So, I changed the client to run in another thread, and it worked. Why’s that?
Injecting into a Wcf Service
Update 9/2/2009: I forgot to include the service contract. Added it.
Overview
When you setup a Service Host, WCF manages the creation of your services. It requires there to be a default constructor so that it can just instantiate the thing and be done with it.
I often find myself wishing to be able to pass something into the object when its created. For example: a unity container. I’d like to inject the unity container.
Lacking that ability, we end up with a static container somewhere that the service calls.
public class TestService : ITestService{public void DoSomething(){IWhatever whatever = StaticHelper.Container.Resolve<IWhatever>();}}
What’s wrong with that? Statics are evil. Whenever you use a static, there should be a big red flag. Sure, its convenient, but its not very testable or flexible.
Sometime we can somewhat counter the testability limitation as follows
public class TestService : ITestService{private readonly IUnityContainer _unityContainer;public TestService(){// wcf will use this constructor_unityContainer = StaticHelper.Container;}// tests use this constructorpublic TestService(IUnityContainer container){_unityContainer = container;}public void DoSomething(){IWhatever whatever = _unityContainer.Resolve<IWhatever>();}}
That works, but still smells kind of funny.
Tonight’s adventure was to figure out how to pass a unity container to the test service when instantiated. I didn’t really know where to start on this, so started at the ServiceHost, and googled the night away.
There are a bunch of examples out there to use unity to determine the implementation type of the service. Two examples that I looked at rely on a static IOC container in order to determine the implementation type. In those cases, the statics can be easily eliminated just by passing the container through the layers as I do here. (Their goal’s were different. My goal is to get rid of the statics.)
UPDATE: Now that I’ve gotten it all to work, and I’ve spent some time thinking about the conclusion, the fore mentioned approach is the best way. Rather than pulling objects from unity and assign it to properties, just define the service itself in the container. Then unity can handle all of the injection. I had to write this blog to come to that conclusion, so I’m not going to throw it all away.
Basic Steps
- Create a service host
- Create a behavior
- Create an instance provider – this is what we’re really interested in. This creates the service object. We need to get the IUnityContainer to this guy.
- Connect the host to the behavior
- Connect the behavior to the instance provider
I created a subclass of ServiceHost and added an IUnityContainer parameter to both of the constructors. The parameter value is saved to a local variable.
The service behavior has no knowledge of the IUnityContainer. It just gives us the bridge between the instance provider.
The instance provider has access to the service host. As you may recall, the service host has the IUnityContainer property. If the service object (implementation) has a IUnityContainer property, then we assign it.
The Code
DISCLAIMER: This code is just enough to get it to work. Its not tied up nice and neat.
An Interface for Services that require an IUnityContainer
The instance provider, way down at the bottom, will see if the service implements this interface. If so, it assigns the property.
namespace AllardWorks.AwBus.Contracts{public interface IUnityWcfService{IUnityContainer UnityContainer { get; set; }}}
The Service Host
public class AwBusServiceHost : ServiceHost{public AwBusServiceHost(IUnityContainer container): base(){_container = container;}public AwBusServiceHost(IUnityContainer container, Type serviceType, params Uri[] baseAddresses): base(serviceType, baseAddresses){_container = container;}protected override void OnOpening(){Description.Behaviors.Add(new AwBusServiceBehavior());base.OnOpening();}public readonly IUnityContainer _container;public IUnityContainer UnityContainer{get { return _container; }}}
The Behavior
We’re only interested in the ApplyDispatchBehavior method.
This loops through all of the endpoint dispatchers and sets the instance provider to one of our own design. We pass it the type that is to be instantiated.
public class AwBusServiceBehavior : IServiceBehavior{public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase){foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers){ChannelDispatcher cd = cdb as ChannelDispatcher;if (cd == null){continue;}foreach (EndpointDispatcher ed in cd.Endpoints){ed.DispatchRuntime.InstanceProvider = new AwBusInstanceProvider(serviceDescription.ServiceType);}}}#region Not Usedpublic void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase){}public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters){}#endregion}
The Instance Provider
At last, we get to create an object. Yay.
The interesting part here is the IUnityWcfService. If the new object implements that interface, then we set the UnityContainer property that the interface provides.
public class AwBusInstanceProvider : IInstanceProvider{private readonly Type _type;public AwBusInstanceProvider(Type type){_type = type;}public object GetInstance(InstanceContext instanceContext){return GetInstance(instanceContext, null);}public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message){object service = Activator.CreateInstance(_type);IUnityWcfService unityService = service as IUnityWcfService;if (unityService != null){unityService.UnityContainer = ((AwBusServiceHost)instanceContext.Host).UnityContainer;}return service;}public void ReleaseInstance(InstanceContext instanceContext, object instance){}}
Take it for a Test Drive
A Test Service. (Hand coded on the spot. Should be mostly correct though)
[ServiceContract]
public interface IDoSomething
{
[OperationContract]
bool HasContainer();
}
public class DoSomething : IDoSomething, IUnityWcfService{
public IUnityContainer UnityContainer { get; set; }
public bool HasContainer()
{
return UnityContainer != null;
}
}
The Server
private IUnityContainer _unity;private ServiceHost _host;private void StartServer(){_unity = new UnityContainer();_host = new AwBusServiceHost(_unity, typeof(TestService), new Uri[] { });#region MexBindingElement mexBindingElement = new TcpTransportBindingElement();CustomBinding mexBinding = new CustomBinding(mexBindingElement);ServiceMetadataBehavior metadataBehavior = _host.Description.Behaviors.Find<ServiceMetadataBehavior>();if (metadataBehavior == null){metadataBehavior = new ServiceMetadataBehavior();_host.Description.Behaviors.Add(metadataBehavior);}_host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, ServiceUri + "/MEX");#endregion
#region NetTcp Endpointc.Binding binding = new NetTcpBinding(SecurityMode.None);_host.AddServiceEndpoint(typeof(ITestService), binding, new Uri(ServiceUri));#endregion_host.Open();}
The Client
c.Binding binding = new NetTcpBinding(SecurityMode.None);EndpointAddress address = new EndpointAddress(ServiceUri);using (ChannelFactory<ITestService> client = new ChannelFactory<ITestService>(binding, address)){ITestService proxy = client.CreateChannel();MessageBox.Show(proxy.HasContainer().ToString());}
Conclusion
This is pretty neat. This solves an inconvenience we’ve had at work, though at work we need a different object. We have this thing we call “the factory”, which is our own IOC container. We wrote it before unity was released and before we realized that it already a name (IOC).
Once you understand that you can pass anything you want to the service host, and then eventually pass it down to the service object, there are a lot of other injection possibilities. Maybe rather than pass the entire container to the service, you just pull out the objects the service needs an inject them. There are a few ways to do that, but after a few seconds of thought, I think that using unity to construct the service object itself is the best idea. (I found a few examples of this, so its an established approach.)
Tuesday, June 2, 2009
Something more secure than Notepad
But, the notepad file is inconvenient. I keep it on an external drive that's plugged into my home computer. I hardly use my home computer. So, when I need to lookup a password, its a pain. (I have most of them memorized, but Bank of America, for example, is just a bunch of numbers.)
Last night, I started looking for an online notepad service where I can type in all of my information and save it on someone else's server preferably using the honor system. Surprisingly, such sites are not abundant, but I stumbled across the concept of "online password storage". There are several sites out there, obviously managed by paranoid maniacs, that think simply storing passwords in clear text is not the way to go. So, they do things like "encrypt" and require you to "login". Its pretty bizarre. These guys are taking security way too seriously. Honestly, what is someone really going to do if they get a list of all of my credit card numbers and pins and social security number?
I looked at a few options, but ended up trying http://passpack.com. You can store 100 passwords for free. Each password record gives you plenty of fields, including a free form note field, to put in anything you need. This is good for the banks since I usually track more info than just the site's id and password. (Ie: cc number, expiration date, ccv, etc.). It gives you quick links to copy information from the record into the clipboard.
You login to the site using your Id and password. But, before you can get to your needlessly encrypted highly personal data, you have to type in your UNPACK password. It seems that they're using this to hash the data. If you lose the UNPACK password, you're done. They can't provide you a new once (hence my speculation that it must be hashed.)
The site is clean and makes good use of ajax. Pretty much every time you click a link, though, you get a fancy shmancy progress bar. I don't like that. The next page shouldn't take so long to load that it needs a progress bar.
So far, I really like it. I have entered 4 passwords, which means I have less than 100 free passwords remaining because I already used four of them as I already stated at the beginning of this major run-on sentence. I'm not going to go nuts and figure out, exactly, how many more that leave me, but I have at least 20 more to go of the 100, which is enough for me.
It offers many advanced features, such as sharing and messaging, but I haven't played with those. I'm really only interested in using it for personal uses.
Check it out: http://passpack.com
Friday, May 29, 2009
Serious memory leak with TRUE in .Net
Greetings
At work, we’ve been running PSR tests against our primary application. Last night, the application (which runs as a windows service), was on an obvious spiral into the drain of despair. The memory was steadily increasing. When it reached 1.5 gigs, the application stopped responding altogether.
That’s not good.
We were going to just not say anything and hope for the best, but the more level headed of us thought that maybe we should take a look. I was completely against it and wanted to sacrifice a goat instead, but I was overruled. (I seem to get overruled every time I want to sacrifice animals.)
I started by reviewing the code. I found some connection objects that weren’t being properly cleaned up. I ordered 50 verbal lashes for the offending perps, but moved on. It wasn’t significant enough to be the memory leak, but did sprout an emotional leak in the bowels of my soul. (Please, just use the using clause. That’s all I ask.)
But I digress. Today, we ran ANTS against it to see where all that memory was all going. It didn’t help, which is unusual.
So, we did it the old fashion way. We started commenting things out of the main method to find which one was the problem. The object does:
- Receives an xml message
- Removes duplicate nodes
- Calls a stored procedure to get more information for each of the nodes
- Does an Xslt Transform
- Publishes the message via WCF
Of all the things there, I was most suspicious of the WCF client and least suspicious of XSLT. Imagine my chagrin when it turned out to be the XSLT method.
// XSLT XslCompiledTransform transform = new XslCompiledTransform(true); using (XmlReader xmlReader = new XmlTextReader(xsltFile)){
transform.Load(xmlReader);
}
The problem is the TRUE parameter when the object is instantiated. If you remove it, or change it to false, then everything is stable.
This is a surprisingly large bug. How can something as fundamental as the boolean value TRUE take down an application like that? Shouldn’t someone have tested that the booleans work properly before shipping? The very foundations of computing are based on boolean values; bits are either on or off… yes or no… 1 or 0. This isn’t rocket science! How can Microsoft ship a product that doesn’t fully support the word true?
I haven’t been this bothered about the .Net framework since I learned that the number 0 is also broken. Every time I try to divide any number by it, it breaks with some illogical math error (can’t divide by zero?). I can’t say that I’ve tried to divide every number, but I did get the vast majority. .. let’s just say that I’ve tested the theory enough times to be convinced that it is a problem.
I really love .NET and will continue to use it without reservation. But, the tough lesson is that you can’t take it for granted, especially if your logic is based on evaluations of some sort. Now that we’ve identified its limitations, we can use it more effectively.
If the problem was elsewhere… let’s say, if the problem were in the XslCompiledTransform object rather than the word true, that would be more understandable. Then we might be able to conclude that “XslCompiledTransform in debug mode leaks more than the Bush administration”. That’s something we could get our head around and come to terms with... But a broken true!? Dissapointing.
Monday, May 25, 2009
SpamArrest - You Got Me
Here is a big related post from April 2008: http://hamletcode.blogspot.com/2008/04/email-decision.html
I was experiencing email turmoil at the time, and settled on SpamArrest. I would've liked to use Gmail, but could not (at least not the way intended).
Since then, 2 significant things have happened with spam arrest:
1 - I bought the lifetime subscription. At the time, I was still a fan. I've been using it for years and planned to use it for years more.
2 - They introduced a javascript bug. I reported the bug to them on October 27th 2008 after waiting serveral weeks to see if they would correct it themselves.
My Email to them
Greetings
This has been happening for quite a while now, but I waited incase it was going to be fixed, but it hasn't.
There's a bug on the login screen. It attempts to attach an event to the REMEMBER ME checkbox. But, if you're already logged in (because it remembered you), then the control doesn't exist, so it can't attach the event. This results in an error dialog:
A runtime error has occurred.
Do you wish to debug?
Line 1726
Error: 'addEventListener' is null or not an object.
Its not a big deal, but after a few weeks its getting a little annoying. (I'm a developer, so I can't disable the alerts.)
Jay
Rather than trying the steps listed, they instead suggested that my browser must be "acting up", and suggested that I install firefox or chrome. That's great... thanks. Great advice. I responded pointing out exactly where the javascript was failing and why, and showed that if fails in firefox 3 too. I listed 3 bulleted steps to reproduce. They responded saying "thank you, we'll forward that to our development team".
That was nearly 7 months ago. They still haven't fixed it. Its going to fail in every browser because it's just bad logic. When you're already logged in, there isn't a "REMEMBER ME" checkbox. The javascript is looking for it anyway, then fails when it can't find it.
Another of my favorite problems is REPLY ALL. If you click REPLY ALL, and forget to remove your own name from the address list, you get an onslaught of spam messages. Once you delete the messages, you don't get any more, so its not like it auto-approved them. Its just that you get a whole bunch that you have to cleanup before you're back to normal.
In my previous blog post, I mentioned the searches I conducted in their help system to find simple things like IMAP and DISK QUOTA. To find DISK QUOTA information, you have to search for "SPACE". To find IMAP information, you have to read an article entitled "WHAT IS A POP SERVER?" Well, I already know what a pop server is. If I'm looking for IMAP server information, why would I click that?
I strongly regret the life time subscription. I asked for a refund on it, and they responded saying no, but then asked me what the problems were. I didn't answer. How many times do I need to tell them what the problems are? At this point, I continue to use SPAM ARREST only out of laziness, despite the life time subscription. Maybe I can sell it on EBAY or something.
Now, the webmail client has a REFER FRIEND tab on which they ask me to refer business to them for savings. I already have a strongly regretted life time subscription... how does refering a friend to spam arrest help me? Furthermore, even if I was still a fan, then that tab would be useless anyway. I've been referring people to SpamArrest for years, not because I want some type of credit, but because it's a good service. Only a few of those referrals have actually started using it, but that wasn't due to a lack of effort on my part. (Most people find GMAIL junk filters to be sufficient.)
My official stance has changed. I will no longer recommend Spam Arrest. Infact, I will actively express dissapointment. Software is supposed to evolve; their web client continues to be stagnant (and broken) where it counts. The pages still say COPYRIGHT 2006!!! Has anyone looked at a calendar recently?
Its ok to have flaws. Its not ok to never fix them. I'm dissapointed in them for not evolving, and I'm really annoyed that I fell for the "lifetime subscription" scam.
Monday, May 11, 2009
Chewing
In order to chew, you need teeth.
Jack now has one that finally broke the gum line. This means that he can chew very small, very localized foods!
Tuesday, May 5, 2009
Linq, Attribute and Reflection, all in one
Greetings
I was goofing around with some reflection stuff, and wanted to use LINQ to build an xml document for me. It started off as three different steps, and whittled its way down to one.
- Gets a list of all of the properties in the current class that have the [ServiceProperty] attribute
- Creates an XDocument containing information about the property including the value, and all of the meta-data added by the [ServiceProperty] attribute
The only thing I don’t like is that it builds the entire wrapper, even if attribute is null. When the attribute is null, we don’t care about anything, and should just continue. But, as is, property.GetValue() executes even when we don’t need it.
1: XDocument doc = new XDocument(
2: new XDeclaration("1.0", "utf-8", "yes"),
3: new XElement("properties",
4: (
5: from property in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
6: let wrapper = new
7: {
8: Property = property,
9: Attribute =Attribute.GetCustomAttribute(property, typeof(ServicePropertyAttribute)) as ServicePropertyAttribute,
10: Value = property.GetValue(this, BindingFlags.Public | BindingFlags.Instance, null, null,
11: CultureInfo.InvariantCulture)
12: }
13: where wrapper.Attribute != null
14: select
15: new XElement("property",
16: new XAttribute("name", wrapper.Property.Name),
17: new XElement("display-name", wrapper.Attribute.DisplayName),
18: new XElement("default-value", wrapper.Attribute.DefaultValue),
19: new XElement("description", wrapper.Attribute.Description),
20: new XElement("required", wrapper.Attribute.Required),
21: new XElement("value", wrapper.Value),
22: new XElement("data-type", wrapper.Property.PropertyType.FullName)
23: )
24: )
25: )
26: );
Saturday, April 11, 2009
Jts 3 Development Wiki
I found an excellent free WIKI from screwturn. I was up and running in about 10 minutes. Excellent.
Anyway, I created a wiki to document the Jts 3 development effort.
Tuesday, March 31, 2009
Jack’s First SQL Query
Jack is just about 6 months old now. As anticipated, he has expressed an interest in programming. He was sitting on my lap yesterday as I was tweaking a query. He started to pound on the keyboard, so I opened up a fresh window for him so that he could express his programming desires uninhibited by my existing work.
Here is what he came up with
“ ."
That’s 2 spaces, a period, and another space. Not only should you appreciate the query itself, but you should appreciate the manner in which it was written. Some people have a vague idea of what they are doing and they sit down to figure it out through a cycle of research/trial-and-error. Others know what has to be done and simply do it; Solving the problem takes exactly as long as it takes to type in the solution. I am of the latter classification, and it would seem that whichever gene enables that skill has been passed to the offspring. Jack was Picasso and the keyboard was his tapestry; There was no delay or thought, simply action. It was so natural that a casual observer may have perceived it as nothing more than the random flailing of his 2 topmost limbs.
As a dad I proudly exclaim that this is a tremendous victory. While not completely void of issues, it is an excellent first step in solving many well known SQL puzzles. I love the initiative, the attitude, and the overall spirit of the effort. It takes more than skill to be a programmer; you need to love it. For that he gets an A+.
But, as a software architect and a mentor, I must be fair and point out the very rare opportunities for improvement.
- Excessive white spaces – surely we don’t need spaces on both sides of the period. Perhaps he should consider a tab rather than consecutive leading spaces. I was going to suggest this to him, but he chose that moment to engage in massive crap. He was concentrating very hard on the pushing exercise, with his brow furrowed and his face turning red from the effort. Relative to his intestinal action and diaper trauma, the tab issue seemed trivial so was left unsaid.
- The period – in this context, it doesn’t actually do anything, which is OK. I see that the period is a solution; its just that we don’t understand the problem because we're dumb. I only mention it here because he didn’t comment it.
Cleary Jack is on the road to programming greatness.
Sunday, March 29, 2009
XQuery – How ye disappoint
I’m updating a legacy app from VB6 to ASP and then to .NET. The ASP is a transitional step so that I can stop dealing with COM+ objects.
Anyhoo… I picked one of the common pages. It prints a grid, basically.
- page calls a vbscript function
- the vbscript function executes a query and gets back a recordset
- a series of nested loops covert the recordset to xml
- the xml is returned to the page
- the page uses XSLT to convert the XML to HTML
Sweet.
Now that I’m converting it to .NET, though, I wanted to try exciting new possibilities. The application is on SQL 2000, but updating to 2005 is a reasonable expectation. (I won’t push my luck with 2008).
The Intent
The idea is to transform this data
into this xml
In the current app, that transformation is done in ASP VbScript.
SQL XML
I’ve dabbled with some of the XML capabilities in 2005. I’ve used it to join to tables and to shred the xml. I’ve also used it to create xml documents without that pesky !TAG! syntax. But, they were all meager efforts.
I started by hoping that such meagerness would be sufficient.
1: declare @startDate datetime
2: declare @endDate datetime
3: declare @theaterId int
4:
5: select
6: @startDate = '10/31/2003',
7: @endDate = '11/6/2003',
8: @theaterId = 170
9:
10: select
11: v.FilmId "film/@film-id",
12: v.FilmName "film/@film-name",
13: v.PrintId "film/print/@print-id",
14: dates.[Date] "film/print/date/@date",
15: a.AuditoriumName "film/print/date/auditorium/@auditorium-name",
16: a.AuditoriumId "film/print/date/auditorium/@auditorium-id"
17: from
But its not. That built the the hierarchy, but it repeats itself over and over. It doesn’t group itself the way I need. If there was/is a way to do everything I need by specifying the paths like that, then it would be a good day.
SQL XQUERY
Next, I started dabbling with XQUERY. My only XQUERY experience has been via SQL Server 2005, and in its simplest form.
I went Google-Crazy and read up on some stuff. I was able to write a query (albeit a crappy one) that does the job.
1: select @output.query('
2: <theater>
3: {
4: for $filmId in distinct-values(/theater/film/@film-id)
5: return
6: <film>
7: { attribute id { $filmId }}
8: { attribute name { /theater/film [@film-id = $filmId][1]/@film-name }}
9: {
10: for $printId in distinct-values(/theater/film [@film-id=$filmId]/@print-id)
11: order by $printId
12: return
13: <print>
14: { attribute id { $printId }}
15: {
16: for $date in distinct-values(/theater/film [@film-id=$filmId and @print-id=$printId]/@date)
17: order by $date
18: return
19: <date>
20: {attribute date { $date }}
21: {
22: for $auditoriumId in distinct-values(/theater/film [@film-id=$filmId and @print-id = $printId and @date=$date]/@auditorium-id)
23: return
24: <auditorium>
25: { attribute id { $auditoriumId }}
26: { attribute name { /theater/film [@film-id=$filmId and @print-id = $printId and @date=$date and @auditorium-id=$auditoriumId]/@auditorium-name }}
27: </auditorium>
28: }
29: </date>
30: }
31: </print>
32: }
33: </film>
34: }
35: </theater>
36: ')
How does this suck? Let me count the ways
- 4 levels of nesting. Its not pretty. But, the VbScript has the same layers. (The logic is different, but its just as nested)
- Each layer has to go back to the top and work its way back down based on the key information collected thus far
- In the loops, I can only order by the loop indexer. For example: Auditorium. I don’t want to sort on “auditorium id”. I want to sort on display order. I can’t, because “auditorium id” is a value, not a node. If it was a node, I’d be able to get to a sibling attribute.
- It offers a handy distinct-values, but does not offer a handy distinct-nodes. (there are example how to do distinct-nodes, but the few I’ve seen use the LET statement, which you can’t do in SQL 2005)
What doesn’t suck
Obviously I’m having problems with it, but that may just be due to my staggering 45 minutes of inexperience with it.
- I like the syntax of specifying the attributes (shown) and elements (not shown) through the {} syntax
- I like that the comments are smiley faces (not shown). (: this is an xquery comment :)
- In principle, I like how you can do the layering.
The Problem
I got the XML that I want, but its slow. The SQL XQUERY consists of 2 parts: the query to get the data as xml, and the xquery to transform it the way I’d like it.
The first part comes back instantaneously. The 2nd part takes anywhere from 2 to 16 seconds. One time, it took a minute and 54 seconds?!. Its really inconsistent. I looked at the execution plan multiple times. Every time it says that the first query accounts for 0% of the time, and the 2nd query accounts for 100% of the time.
The legacy app does all it needs to do, including rendering it on the page, in a 1/2 second or less. You don’t even see it happen; you just click the link and the page renders.
I know that my xquery is amateur. If I can rewrite it the way it should be written and try again, maybe the results will be drastically improved. (At least I hope they are.)
Things that Would Help
- SQL 2008 supports the LET statement. If I had that in 2005, then I could assign node sets at the various levels, and treat that as the root for that level. Then it wouldn’t have to go to the top of the document every time. (At least, it seems like I’d be able to do that)
- If I could do a distinct-nodes instead distinct-values, then as I loop through, I can get the other stuff I need relative to the attribute. IE: $film-id/../@film-name.
- Knowledge of XQuery would sure be helpful.
Next Steps / Conclusions
I wanted the source doc to be hierarchical so that its would be an accurate representation of the data. Since the XQuery didn’t work, I may end up doing it in C#. Then, the page will use an XSLT to render it. (I’ll look into using XQUERY to render it, but I don’t think that’s a viable option yet).
I developed the original application starting in 2001. Over the first few years, I spent a lot of time performance testing the quickest way to get the data out of the database and onto a page. I always lean towards XML and XSLT so that you can easily render it different ways. I want to keep it transformable.
Of all the things I tried, the quickest thing has always been:
- Run the query and get back a flat dataset
- Use code to convert the dataset to xml
Despite the repeating data, and despite the manual conversion, it wins every time.
Things I may try
- Convert the SQL XML to my XML via XSLT
- Convert the SQL XML to my XML via C# code (the old fashioned way with a new language)
- Read more about XQuery to determine how off-target my query really is