views:

93

answers:

2

I'm trying to define a new type and have not had much luck finding any information about using lists within them. Basically my new type will contain two lists, lets say x and y of type SqlSingle (the user defined type is written in C#) is this even possible?

If not how are you supposed to go about simulating a two lists of an arbitary length in an SQL Server 2008 column?

I'm possibly going about this the wrong way but it is the best approach I can think of at the moment. Any help is very much appreciated.

+1  A: 

Lists as you describe are usually normalized - that is, stored in separate tables with one row per item - rather than trying to cram them into a single column. If you can share more info on what you are trying to accomplish, maybe we can offer more assistance.

Edit - suggested table structure:

-- route table--
route_id      int   (PK)
route_length  int (or whatever)
route_info    <other fields as needed>

-- waypoint table --
route_id      int      (PK)
sequence      tinyint  (PK)
lat           decimal(9,6)
lon           decimal(9,6)
waypoint_info <other fields as needed>
Ray
Basically the type I am trying to create is a route. Each route can consist of an arbitary number of waypoints. Each waypoint is a latitude and longitude.A route should be considered a discrete type and must be associated with a given user ID.
Simon
I don't know much about routing 'n' stuff. But typically in a normalized db, you would have a route table with a route id (or user id, or whatever you need). Then a waypoints table which would have the id from the route table, plus a waypoint id or sequence number, and the lat-lon data for the point. Each waypoint would have a separate records in this table.
Ray
The problem with that approach is it would require a separate column to track the length of each route, the start waypoint ID of each route and the end waypoint ID of each route in order to ensure that you don't accidently read data from the following or preceding route in the table. Which is why I wanted to use a list as all of that is taken care of. I have no use for individual way points other than using them within the confines of each route and I would want all the other waypoints associated with that route at the same time too anyway.
Simon
I edited my answer to show you what my suggestion looks like in detail. You will not accidentally read anything if you use the route_id when querying the tables. All waypoints are attached to their routes. The start waypoint is sequence #1 within the route and the end is the highest sequence within the route.
Ray
Ah, thank you. That is much clearer now.
Simon
I hope it helps. If you still need to keep things in a single field, then Aaronaught's answer seems like a good approach. The right solution depends on how the stored data will be used.
Ray
+1  A: 

You can use a List<T> in a CLR UDT - although CLR types are structs, which should be immutable, so a ReadOnlyCollection<T> would be a better choice if you don't have a very compelling reason for the mutability. What you need to know in either case is that SQL won't know how to use the list itself; you can't simply expose the list type as a public IList<T> or IEnumerable<T> and be on your merry way, like you would be able to do in pure .NET.

Typically the way to get around this would be to expose a Count property and some methods to get at the individual list items.

Also, in this case, instead of maintaining two separate lists of SqlSingle instances, I would create an additional type to represent a single point, so you can manage it independently and pass it around in SQL if you need to:

[Serializable]
[SqlUserDefinedType(Format.Native)]
public struct MyPoint
{
    private SqlSingle x;
    private SqlSingle y;

    public MyPoint()
    {
    }

    public MyPoint(SqlSingle x, SqlSingle y) : this()
    {
        this.x = x;
        this.y = y;
    }

    // You need this method because SQL can't use the ctors
    [SqlFunction(Name = "CreateMyPoint")]
    public static MyPoint Create(SqlSingle x, SqlSingle y)
    {
        return new MyPoint(x, y);
    }

    // Snip Parse method, Null property, etc.
}

The main type would look something like this:

[Serializable]
[SqlUserDefinedType(Format.UserDefined, IsByteOrdered = true, MaxByteSize = ...)]
public struct MyUdt
{
    // Make sure to initialize this in any constructors/builders
    private IList<MyPoint> points;

    [SqlMethod(OnNullCall = false, IsDeterministic = true, IsPrecise = true)]
    public MyPoint GetPoint(int index)
    {
        if ((index >= 0) && (index < points.Count))
        {
            return points[index];
        }
        return MyPoint.Null;
    }

    public int Count
    {
        get { return points.Count; }
    }
}

If you need SQL to be able to get a sequence of all the points, then you can add an enumerable method to the sequence type as well:

[SqlFunction(FillRowMethodName = "FillPointRow",
    TableDefinition = "[X] real, [Y] real")]
public static IEnumerable GetPoints(MyUdt obj)
{
    return obj.Points;
}

public static void FillPointRow(object obj, out SqlSingle x, out SqlSingle y)
{
    MyPoint point = (MyPoint)obj;
    x = point.X;
    y = point.Y;
}

You might think that it's possible to use an IEnumerable<T> and/or use an instance method instead of a static one, but don't even bother trying, it doesn't work.

So the way you can use the resulting type in SQL Server is:

DECLARE @UDT MyUdt
SET @UDT = <whatever>

-- Will show the number of points
SELECT @UDT.Count

-- Will show the binary representation of the second point
SELECT @UDT.GetPoint(1) AS [Point]

-- Will show the X and Y values for the second point
SELECT @UDT.GetPoint(1).X AS [X], @UDT.GetPoint(1).Y AS [Y]

-- Will show all the points
SELECT * FROM dbo.GetPoints(@UDT)

Hope this helps get you on the right track. UDTs can get pretty complicated to manage when they're dealing with list/sequence data.

Also note that you'll obviously need to add serialization methods, builder methods, aggregate methods, and so on. It can be quite an ordeal; make sure that this is actually the direction you want to go in, because once you start adding UDT columns it can be very difficult to make changes if you realize that you made the wrong choice.

Aaronaught
Thank you, that was very helpful.
Simon