views:

78

answers:

2

I have very little experience designing databases, and now I want to create a very simple database that does the same thing I have previously had in xml. Here's the xml:

<services>
  <service type="writing">
    <small>125</small>
    <medium>100</medium>
    <large>60</large>
    <xlarge>30</xlarge>
  </service>
  <service type="analysis">
    <small>56</small>
    <medium>104</medium>
    <large>200</large>
    <xlarge>250</xlarge>
  </service>
</services>

Now, I wanted to create the same thing in a SQL database, and started doing this ( hope this formats ok, but you'll get the gist, four columns and two rows):

> ServiceType Small Medium Large
> 
> Writing       125    100    60
> 
> Analysis       56    104   200

This didn't work too well, since I then wanted to use LINQ to select, say, the Large value for Writing (60). But I couldn't use LINQ for this (as far as I know) and use a variable for the size (see parameters in the method below). I could only do that if I had a column like "Size" where Small, Medium, and Large would be the values. But that doesn't feel right either, because then I would get several rows with ServiceType = Writing (3 in this case, one for each size), and the same for Analysis. And if I were to add more servicetypes I would have to do the same. Simply repetitive... Is there any smart way to do this using relationships or something?

Using the second design above (although not good), I could use the following LINQ to select a value with parameters sent to the method:

protected int GetHourRateDB(string serviceType, Size size)
{
  CalculatorLinqDataContext context = new CalculatorLinqDataContext();
  var data = (from calculatorData in context.CalculatorDatas
        where calculatorData.Service == serviceType && calculatorData.Size == size.ToString()
        select calculatorData).Single();
  return data.Hours;
}

But if there is another better design, could you please also describe how to do the same selection using LINQ with that design?

Please keep in mind that I am a rookie at database design, so please be as explicit and pedagogical as possible :-)

Thanks!

Anders

+2  A: 

You can use your first method and do something like this:

protected int GetHourRateDB(string serviceType, Size size)
{
    using (CalculatorLinqDataContext context = new CalculatorLinqDataContext())
    {
        var data =
            (from calculatorData in context.CalculatorDatas
            where calculatorData.Service == serviceType
            select calculatorData).Single();

        switch (size) {
            case Small:
                return data.Small;
            case Medium:
                return data.Medium;
            case Large:
                return data.Large;
            default:
                // Error handling
         }
     }
}

Your second method would also work, but if you know that there will always be exactly three sizes for all items, and that they will always be called Small, Medium and Large, then by using the first methods you can hardcode that into the schema and get the database to enforce it for you.

You might also want to consider if it would be better to use an auto-incrementing primary key instead of using the service name as the primary key. Names have an unfortunate problem that they often change for reasons outside of your control (fix typos, marketing, translations, etc.). You might want to avoid having to recompile your program every time a name changes.

Mark Byers
Right, thanks. But unfortunately I already knew that, I probably didn't explain well enough. The thing is, I want to avoid if or switch statements (this whole problem originates in another thread for avoiding them by using strategy classes etc, see: http://stackoverflow.com/questions/2792136/replace-conditional-with-polymorphism-refactoring-or-similar ). So I wanted to be able to select by using the parameter, as I can in xml: string result = doc.SelectSingleNode(string.Format("//*[@serviceType='{0}']/{1}", serviceType, size)).InnerText; But how?
Anders Svensson
BTW, you say the second method would also work, the reason I felt the second variant didn't feel good was having to repeat rows with servicetype "writing" for each size etc... Wouldn't you agree something feels wrong then? And BTW 2: ServiceType wasn't meant to be the primary key, I just didn't include the primary key in this simple example of how the db looks :-)
Anders Svensson
Actually, this worked well when I looked at it again, I did have to use an if statement or switch somewhere in the code of course, I just wanted it to be in only one place. And I only had to change WHERE it was, and then this worked like a charm. Thanks.
Anders Svensson
+1  A: 

If you want to use an enumeration to select you desired field, go for Mark Byers answer. I just wanted to show up another way to do what you want: just use a lambda for selecting the field, like you are doing in many other Linq methods:

protected int GetHourRateDB(string serviceType, 
                            Func<CalculatorData, int> fieldSelector)
{
    using(CalculatorLinqDataContext context = new CalculatorLinqDataContext())
    {
        return fieldSelector(
                  (from calculatorData in context.CalculatorDatas
                   where calculatorData.Service == serviceType
                   select calculatorData).Single()
               );
    }
}

You would use it like:

int someSmallValue = GetHourRateDB("Writing", x => x.Small);
int someMediumValue = GetHourRateDB("Writing", x => x.Medium);

Be aware that Single() throws an InvalidOperationException if there is no element in the database with the given serviceType. You have to handle that some way.

Philip Daubmeier
Interesting, I'll have to try it out and see if it works, and I'll get back.
Anders Svensson
Thanks, this worked nicely too, and it was interesting to see another take on it. Unfortunately my grasp of Lambda is rather weak at best, but I'm getting more and more indications that I should look more into it!
Anders Svensson
Just a correction though, there was a missing parenthesis in your code, there should be one more at the beginning of the linq statement, before "from" (I don't have enough points to edit it myself). After that it worked fine!
Anders Svensson
@Anders: thanks, just corrected that. And yes, you should really have a closer look at lambdas :) They may look strange at first sight, but are really easy to use and come in very handy in many situations.
Philip Daubmeier