Best way to save a ordered List to the Database while keeping the ordering

C#DatabaseNhibernateSql Order-By

C# Problem Overview


I was wondering if anyone has a good solution to a problem I've encountered numerous times during the last years.

I have a shopping cart and my customer explicitly requests that it's order is significant. So I need to persist the order to the DB.

The obvious way would be to simply insert some OrderField where I would assign the number 0 to N and sort it that way.

But doing so would make reordering harder and I somehow feel that this solution is kinda fragile and will come back at me some day.

(I use C# 3,5 with NHibernate and SQL Server 2005)

Thank you

C# Solutions


Solution 1 - C#

Ok here is my solution to make programming this easier for anyone that happens along to this thread. the trick is being able to update all the order indexes above or below an insert / deletion in one update.

Using a numeric (integer) column in your table, supported by the SQL queries

CREATE TABLE myitems (Myitem TEXT, id INTEGER PRIMARY KEY, orderindex NUMERIC);

To delete the item at orderindex 6:

DELETE FROM myitems WHERE orderindex=6;    
UPDATE myitems SET orderindex = (orderindex - 1) WHERE orderindex > 6;

To swap two items (4 and 7):

UPDATE myitems SET orderindex = 0 WHERE orderindex = 4;
UPDATE myitems SET orderindex = 4 WHERE orderindex = 7;
UPDATE myitems SET orderindex = 7 WHERE orderindex = 0;

i.e. 0 is not used, so use a it as a dummy to avoid having an ambiguous item.

To insert at 3:

 UPDATE myitems SET orderindex = (orderindex + 1) WHERE orderindex > 2;
 INSERT INTO myitems (Myitem,orderindex) values ("MytxtitemHere",3)

Solution 2 - C#

Best solution is a Doubly Linked list. O(1) for all operations except indexing. Nothing can index SQL quickly though except a where clause on the item you want.

0,10,20 types fail. Sequence column ones fail. Float sequence column fails at group moves.

Doubly Linked list is same operations for addition, removal, group deletion, group addition, group move. Single linked list works ok too. Double linked is better with SQL in my opinion though. Single linked list requires you to have the entire list.

Solution 3 - C#

FWIW, I think the way you suggest (i.e. committing the order to the database) is not a bad solution to your problem. I also think it's probably the safest/most reliable way.

Solution 4 - C#

How about using a linked list implementation? Having one column the will hold the value (order number) of the next item. I think it's by far the easiest to use when doing insertion of orders in between. No need to renumber.

Solution 5 - C#

Unfortunately there is no magic bullet for this. You cannot guarentee the order of any SELECT statement WITHOUT an order by clause. You need to add the column and program around it.

I don't know that I'd recommend adding gaps in the order sequence, depending on the size of your lists and the hits on the site, you might gain very little for the over head of handling the logic (you'd still need to cater for the occasion where all the gaps have been used up). I'd take a close look to see what benifits this would give you in your situation.

Sorry I can't offer anything better, Hope this helped.

Solution 6 - C#

I wouldn't recommend the A, AA, B, BA, BB approach at all. There's a lot of extra processing involved to determine hierarchy and inserting entries in between is not fun at all.

Just add an OrderField, integer. Don't use gaps, because then you have to either work with a non-standard 'step' on your next middle insert, or you will have to resynchronize your list first, then add a new entry.

Having 0...N is easy to reorder, and if you can use Array methods or List methods outside of SQL to re-order the collection as a whole, then update each entry, or you can figure out where you are inserting into, and +1 or -1 each entry after or before it accordingly.

Once you have a little library written for it, it'll be a piece of cake.

Solution 7 - C#

I would just insert an order field. Its the simplest way. If the customer can reorder the fields or you need to insert in the middle then just rewrite the order fields for all items in that batch.

If down the line you find this limiting due to poor performance on inserts and updates then it is possible to use a varchar field rather than an integer. This allows for quite a high level of precision when inserting. eg to insert between items 'A' and 'B' you can insert an item ordered as 'AA'. This is almost certainly overkill for a shopping cart though.

Solution 8 - C#

On a level of abstraction above the cart Items let's say CartOrder (that has 1-n with CartItem) you can maintain a field called itemOrder which could be just a comma - separated list of id(PK) of cartItem records relevant . It will be at application layer that you require to parse that and arrange your item models accordingly . The big plus for this approach will be in case of order reshufflings , there might not be changes on individual objects but since order is persisted as an index field inside the order item table rows you will have to issue an update command for each one of the rows updating their index field. Please let me know your criticisms on this approach, i am curious to know in which ways this might fail.

Solution 9 - C#

I solved it pragmatically like this:

  1. The order is defined in the UI.

  2. The backend gets a POST request that contains the IDs and the corresponding Position of every item in the list.

  3. I start a transaction and update the position for every ID.

Done.

So ordering is expensive but reading the ordered list is super cheap.

Solution 10 - C#

I would recommend keeping gaps in the order number, so instead of 1,2,3 etc, use 10,20,30... If you need to just insert one more item, you could put it at 15, rather than reordering everything at that point.

Solution 11 - C#

Well, I would say the short answer is:

Create a primary key of autoidentity in the cartcontents table, then insert rows in the correct top-down order. Then by selecting from the table with order by the primary key autoidentity column would give you the same list. By doing this you have to delete all items and reinsert then in case of alterations to the cart contents. (But that is still quite a clean way of doing it) If that's not feasible, then go with the order column like suggested by others.

Solution 12 - C#

When I use Hibernate, and need to save the order of a @OneToMany, I use a Map and not a List.

@OneToMany(fetch = FetchType.EAGER, mappedBy = "rule", cascade = CascadeType.ALL)
@MapKey(name = "position")
@OrderBy("position")
private Map<Integer, RuleAction>	actions				= LazyMap.decorate(new LinkedHashMap<>(), FactoryUtils.instantiateFactory(RuleAction.class, new Class[] { Rule.class }, new Object[] { this }));

In this Java example, position is an Integer property of RuleAction so the order is persisted that way. I guess in C# this would look rather similar.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTigraineView Question on Stackoverflow
Solution 1 - C#KickahaView Answer on Stackoverflow
Solution 2 - C#sean wagnerView Answer on Stackoverflow
Solution 3 - C#GalwegianView Answer on Stackoverflow
Solution 4 - C#Js.View Answer on Stackoverflow
Solution 5 - C#Binary WorrierView Answer on Stackoverflow
Solution 6 - C#AdamView Answer on Stackoverflow
Solution 7 - C#Jack RyanView Answer on Stackoverflow
Solution 8 - C#redzediView Answer on Stackoverflow
Solution 9 - C#BijanView Answer on Stackoverflow
Solution 10 - C#harriyottView Answer on Stackoverflow
Solution 11 - C#sindre jView Answer on Stackoverflow
Solution 12 - C#yglodtView Answer on Stackoverflow