TheChaseMan's Frenetic SoapBox

Always looking for better ways to do things...

Tuesday, November 27, 2007 #

I'm sure you've heard by now the Visual Studio 2008 is RTM. If you haven't already, play around a bit with object/collection initializers and LINQ. One interesting thing  I've learned today is the “let” clause which allows you to define a subexpression in a LINQ query. Also you can generate XML fairly easily in your LINQ queries (or anything else you want to transform for that matter).

class Program

{

    static void Main(string[] args)

    {

        List<Person> people = new List<Person>(){

            new Person { Name = "Jack", Age = 22, FavoriteColors = new string[] { "Green", "Red" } },

            new Person { Name = "Jill", Age = 33, FavoriteColors = new string[] { "Black", "Blue" } }

        };

 

        var xmlSource = new XElement("People",

        from person in people

        let colors = String.Format("{0},{1}", person.FavoriteColors[0], person.FavoriteColors[1])

        select new XElement("Person",

                   new XElement("Name", person.Name),

                   new XElement("Age", person.Age),

                   new XElement("FavoriteColors", colors)

                )

            );

 

        Console.WriteLine(xmlSource);

 

        /* output:

            <People>

              <Person>

                <Name>Jack</Name>

                <Age>22</Age>

                <FavoriteColors>Green,Red</FavoriteColors>

              </Person>

              <Person>

                <Name>Jill</Name>

                <Age>33</Age>

                <FavoriteColors>Black,Blue</FavoriteColors>

              </Person>

            </People>

         */

    }

}

posted @ 2:48 PM | Feedback (0)

Thursday, October 25, 2007 #

Just FYI - if you are using the System.Net.OracleClient namespace with 10g it is possible you will run into this error. Now before you start getting too excited about being able to set the ReturnProviderSpecificTypes property on an adapter, you can run into this problem with output parameters on an ExecuteNonQuery so it's probably a safer bet to use the OleDb provider with 10g.

Flame on if I'm wrong.

posted @ 12:31 PM | Feedback (0)

I'm not going to bash Oracle...it does a good job of bashing itself. For those of us used to being productive using SQL Server (I've heard it said that Oracle sells its products to managers, Microsoft sells to developers), here's some interesting info on calling an Oracle proc.

OK, so you are used to SQL Server and you want to create a proc that ultimately (at some point) returns some results in the form of...well, a bunch of records!

create procedure foo as
    ...
    select a, b, c
    from bar
    where blah = 1

You jump into Oracle and try the same thing you'll get this nice error message:

PLS-00428: an INTO clause is expected in this SELECT statement. Also we are better than you M$ noobcakes and Bill Gates is a jerk. Windoze is teh suxxorz. ZOMG barrens chat.

The way around this is to use a ref cursor. It's silly and it's juvenile and it's like going back in time, but it works. http://support.microsoft.com/?id=309361

But before you get to pissed off about having to create an Oracle package and define a cursor type, with 9i and 10g you can use sys_refcursor and save the trouble.

CREATE OR REPLACE PROCEDURE Foo (io_cursor IN OUT sys_refcursor) AS

v_cursor sys_refcursor;

...
begin
    open v_cursor for
    select a, b, c
    where blah = 1;

    io_cursor := v_cursor;
end;

That's it, you're done! Yay!
 
Oh wait no you're not!!! Because if you want to test it you're probably thinking "Hey I can just type EXEC Foo in Query Analyzer." Sorry there's more pig $#!+ you have to go through than that. Isn't DBArtisan a great tool?!?! *vomits*

declare
   
v_cursor sys_refcursor;
    a number;
    b number;
    c number

begin
   
Foo(io_cursor => v_cursor);

    loop
        fetch v_cursor into a, b, c; 
        exit when v_cursor%NOTFOUND;
        dbms_output.put_line(a || b || c);
     end loop;
     close v_cursor;
end;

Hopefully you're not stuck in Oracle hell (god I miss SQL Server 2005), but if you've had your nose jammed in it like a bad dog and you're not getting help from the elitist oracle buff community, and you run across this problem...hope it helps. :-)

posted @ 12:13 PM | Feedback (0)

Friday, February 23, 2007 #

Just a heads up if you use the AutoCompleteExtender Ajax control. I'm not sure if this is because I'm using Visual Studio SP1 (using Web Application Projects), but I could not get this control to work with an ASMX, but rather a WebMethod in the page's code-behind. Also, make sure your ServiceMethod attribute declaration is static!

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:TextBox runat="server" ID="myTextBox" Width="300" autocomplete="off" />
<ajaxToolkit:AutoCompleteExtender runat="server"
                                  ID="autoComplete1"
                                  TargetControlID="myTextBox"
                                  ServiceMethod="GetCompletionList"
                                  MinimumPrefixLength="1"
                                  CompletionInterval="1000"
                                  EnableCaching="true"
                                  CompletionSetCount="12" />
</form>

 

public partial class _Default : System.Web.UI.Page {
    [WebMethod]
    public static string[] GetCompletionList(string prefixText, int count) {
        string sql = String.Format("select companyName from customers where companyname like @companyname + '%'");

        List<string> companyList = new List<string>();
        using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=Northwind;Data Source=."))
        using (SqlCommand command = new SqlCommand(sql, connection)) {
            connection.Open();
            command.Parameters.AddWithValue("@companyname", prefixText);
            using (SqlDataReader reader = command.ExecuteReader()) {
                while (reader.Read()) {
                    companyList.Add(reader.GetString(0));
                }
            }
        }

        return companyList.ToArray();
    }
}

 

 

posted @ 2:20 PM | Feedback (3)

Thursday, February 15, 2007 #

Just wanted to share this in case anyone has issues with very slow build and debug time in Visual Studio for ASP.NET applications. This solution might sound a lot like holding tin foil over your head, but....try removing all of your breakpoints. "Clear All Breakpoints" can actually make good things happen.

posted @ 3:18 PM | Feedback (1)

This sums it up nicely.  :-)

posted @ 3:12 PM | Feedback (1)

Monday, December 18, 2006 #

We've had some interesting discussions about Web Services around the office lately. One of the major challenges are people that want to consume Web Services that transfer large amounts of data rather than a single operation per record or having to deal with filter constraints. The major problem with this is "ballooning" of data on the server - meaning you build up a huge result set to send back to the client which can take up a ton of resources. This can increase exponentially as the number of requests increases. One solution is to stream the data to the clients in "chunks." Yasser Shohoud has a really good example of streaming songs in this manner; however, many of us are dealing with data from a database rather than songs so I wanted to blog about how this can be done. First I'd like to thank someone who wishes to remain anonymous (we'll call him Homer) for helping me understand conceptually what was happening during the most challenging part of this example: ReadXml().

This example is architected with three assemblies: Client, Web Service, and a Common library. Let's start with the Common library where our main functionality will be a class named CustomerStream. CustomerStream simply implements IXmlSerializable. This requires us to implement GetSchema, ReadXml, and WriteXml methods. CustomerStream will be designed so that we can pass it a SqlDataReader in the constructor so we can use that to write the data in chunks. First, we'll implement GetSchema which is very simple, and WriteXml. WriteXml will use our SqlDataReader to write out our data chunks (in this case we'll use Customer records from the Northwind example database).

 

namespace StreamService.Common {
    public class CustomerStream : IXmlSerializable {
        private const string ns = "http://tempuri.org";
        private SqlDataReader _reader;
 
        public CustomerStream() { }
 
        public CustomerStream(SqlDataReader reader) {
            _reader = reader;
        }
 
        public System.Xml.Schema.XmlSchema GetSchema() {
            string schema = @"<xs:schema id=""Customer"" targetNamespace=""http://tempuri.org/Customer.xsd"" elementFormDefault=""qualified"" xmlns=""http://tempuri.org/Customer.xsd"" xmlns:mstns=""http://tempuri.org/Customer.xsd"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
                              <xs:element name=""Customer"">
                                <xs:complexType>
                                  <xs:sequence>
                                    <xs:element name=""CustomerID"" type=""xs:string"" />
                                    <xs:element name=""CompanyName"" type=""xs:string"" />
                                    <xs:element name=""ContactName"" type=""xs:string"" />
                                    <xs:element name=""Country"" type=""xs:string"" />
                                    <xs:element name=""Phone"" type=""xs:string"" />
                                  </xs:sequence>
                                </xs:complexType>
                              </xs:element>
                            </xs:schema>";
 
            XmlSchema xs = XmlSchema.Read(new StringReader(schema), null);
            return xs;
        }
 
        public void WriteXml(System.Xml.XmlWriter writer) {
            if (_reader == null) throw new InvalidOperationException("SqlDataReader expected");
 
            using (_reader) {
                while (_reader.Read()) {
                    writer.WriteStartElement("Customer", ns);
                    for (int fieldIndex = 0; fieldIndex < _reader.FieldCount; fieldIndex++) {
                        writer.WriteElementString(_reader.GetName(fieldIndex), ns, _reader.GetValue(fieldIndex).ToString());
                    }
                    writer.WriteEndElement();
                }
            }
        }
    }
}

The next class we'll want to implement is some sort of custom reader for iterating the results. Because this is a dependency of the ReadXml() method which we haven't implemented at this point, we'll do that first. This is a very simplistic design that consists mostly of throwing NotImplementedException (haha), but gets the job done for this example and you could certainly extend this concept. Basically the custructor takes a file path where the results are stored in an XML document and operates similiar to a datareader using GetCustomer().

 

namespace StreamService.Common {
    public class CustomerDataReader : DbDataReader, IDataReader, IDisposable, IDataRecord {
        private Dictionary<int, string> _ordinals;
        private XmlNodeList _nodes;
        private XmlDocument _document;
        private Customer _current;
        private int _count;
 
        public CustomerDataReader(string path) {
            _document = new XmlDocument();
            _document.Load(path);
            _nodes = _document.SelectNodes("//Customers/*");
 
            _ordinals = new Dictionary<int, string>();
            _ordinals.Add(0, "CustomerID");
            _ordinals.Add(1, "CompanyName");
            _ordinals.Add(2, "ContactName");
            _ordinals.Add(3, "Country");
            _ordinals.Add(4, "Phone");
        }
 
        public override int GetOrdinal(string name) {
            foreach (KeyValuePair<int, string> entry in _ordinals) {
                if (entry.Value == name) return entry.Key;
            }
            throw new ArgumentException("Name does not exist.");
        }
 
        public override bool Read() {
            if (_count < _nodes.Count) {
                XmlNode currentNode = _nodes[_count++];
 
                _current = new Customer();
                foreach (XmlNode child in currentNode.ChildNodes) {
                    typeof(Customer).GetProperty(child.Name).SetValue(_current, child.InnerText, null);
                }
                return true;
            }
            return false;
        }
 
        public Customer GetCustomer() {
            return _current;
        }
 
        public override void Close() {
            // ...
        }
        // NOTE: rest of the methods throw not implemented exceptions
    }
}

Now we are set to implement the ReadXml() method that is missing from our CustomerStream class as well as a method that allows the calling client to implement our custom datareader class...

 

//CustomerStream class methods...
public CustomerDataReader ExecuteCustomerDataReader() {
    CustomerDataReader reader = new CustomerDataReader(Environment.CurrentDirectory + "\\Customers.xml");
    return reader;
}
 
public void ReadXml(System.Xml.XmlReader reader) {
    using (XmlTextWriter xwriter = new XmlTextWriter(Environment.CurrentDirectory + "\\Customers.xml", Encoding.UTF8)) {
        xwriter.WriteRaw(@"<?xml version=""1.0"" encoding=""utf-8""?>");
        xwriter.WriteStartElement("Customers");
 
        while (reader.Read()) {
            if (reader.LocalName == "Customer") {
                xwriter.WriteStartElement("Customer");
                reader.ReadStartElement();
 
                while (reader.NodeType != XmlNodeType.EndElement) {
                    xwriter.WriteElementString(reader.LocalName, reader.ReadElementString());
                }
                xwriter.WriteEndElement();
            }
        }
        xwriter.WriteEndElement();
    }
}

Since our custom datareader class exposes a class type named Customer, here is the code for that...
 

namespace StreamService.Common {
    public class Customer {
        private string _phone;
        private string _country;
        private string _contactName;
        private string _companyName;
        private string _customerID;
 
        public Customer() { }
 
        public Customer(string customerID, string companyName, string contactName, string country, string phone) {
            _customerID = customerID;
            _companyName = companyName;
            _contactName = contactName;
            _country = country;
            _phone = phone;
        }
 
        public string CustomerID {
            get { return _customerID; }
            set { _customerID = value; }
        }
 
        public string CompanyName {
            get { return _companyName; }
            set { _companyName = value; }
        }
 
        public string ContactName {
            get { return _contactName; }
            set { _contactName = value; }
        }
 
        public string Country {
            get { return _country; }
            set { _country = value; }
        }
 
        public string Phone {
            get { return _phone; }
            set { _phone = value; }
        }
    }
}

The next step is to write our Web Service. This is very straight forward - all we have to do is turn off buffering and create a SqlDataReader to query the Customers table in the Northwind database. Note that the responsibility for closing the SqlDataReader and the SqlConnection lies within the CustomerStream class. This is so that we do not get disconnected after the GetCustomers WebMethod finishes executing and we are within the context of the server streaming down the data chunks (customer records) to the client...
 

namespace StreamService.Server {
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.DesignerCategory("Code")]
    [ToolboxItem(false)]
 
    public class TheWebService : System.Web.Services.WebService {
        [WebMethod]
        public CustomerStream GetCustomers() {
            //turn off buffering
            HttpContext.Current.Response.Buffer = false;
 
            //do *not* dispose of connection or datareader - our CustomerStream class does this
            SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString);
            using (SqlCommand command = new SqlCommand("SELECT CustomerID, CompanyName, ContactName, Country, Phone FROM Customers", connection)) {
                connection.Open();
                SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
                CustomerStream stream = new CustomerStream(reader);
                return stream;
            }
        }
    }
}

Next, we need to create a client application to call the Web Service...
 

namespace StreamService.Client {
    class Program {
        static void Main(string[] args) {
            TheWebService ws = new TheWebService();
            CustomerStream stream = ws.GetCustomers();
 
            XmlSerializer ser = new XmlSerializer(typeof(Customer));
 
            using(CustomerDataReader reader = stream.ExecuteCustomerDataReader()){
                while (reader.Read()) {
                    Customer customer = reader.GetCustomer();
                    ser.Serialize(Console.Out, customer);
                }
            }
        }
    }
}

Lastly, we'll need to make a small tweak to the client's Reference.cs code (our proxy class generated by Visual Studio when we reference the Web Service) so that the type casted and returned is a CustomerStream object..
 

/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/GetCustomers", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public CustomerStream GetCustomers() {
    object[] results = this.Invoke("GetCustomers", new object[0]);
    return ((CustomerStream)(results[0]));
}

I hope you found this interesting and informative. Merry Christmas, Happy Holidays, etc!

posted @ 1:19 AM | Feedback (4)

Thursday, December 14, 2006 #

Did you know that you can wrap a variable in a using statement without having to set its reference? Someone showed me this today and it of course makes sense, I've just never tried it before and have never seen anyone do it in examples or otherwise...

//datareader variable
using (_reader) {
    while (_reader.Read()) {
        writer.WriteStartElement("Customer", ns);
        for (int fieldIndex = 0; fieldIndex < _reader.FieldCount; fieldIndex++) {
            writer.WriteElementString(_reader.GetName(fieldIndex), ns, _reader.GetValue(fieldIndex).ToString());
        }
        writer.WriteEndElement();
    }
}

I learned something new today, guess I get to go home.  :-) 

posted @ 12:56 PM | Feedback (7)

Tuesday, December 12, 2006 #

I think the real problem is that "we" (that is, we software developers) are in a permanent state of emergency, grasping at straws to get our work done. We perform many minor miracles through trial and error, excessive use of brute force, and lots and lots of testing, but--so often--it's not enough.

Software developers have become adept at the difficult art of building reasonably reliable systems out of unreliable parts. The snag is that often we do not know exactly how we did it: a system just "sort of evolved" into something minimally acceptable.

The idea of programming as a semiskilled task, practiced by people with a few months' training, is dangerous. We wouldn't tolerate plumbers or accountants that poorly educated. We don't have as an aim that architecture (of buildings) and engineering (of bridges and trains) should become more accessible to people with progressively less training. Indeed, one serious problem is that currently, too many software developers are undereducated and undertrained.

http://www.technologyreview.com/InfoTech/17868/page2/ 

posted @ 6:08 AM | Feedback (4)

Wednesday, December 06, 2006 #