Returning incorrect results

Mar 23, 2011 at 10:27 AM

I have a problem where some objects that are shared between 2 different serialized classes are showing up in the wrong place. 

The classes look like this 

    public class Item
    {
        public Guid Guid { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
    }

    public class ItemGroup
    {
        public Guid Guid { get; set; }
        public ObservableCollection<Item> ItemsGroup { get; set; }
    }

    public class SomeCollection
    {
        public Guid Guid { get; set; }
        public ObservableCollection<ItemGroup> Collection { get; set; }
    }
}

I'm saving SomeCollecion and a separate collection of Items. After i save SomeCollection the when i try to load a list of Items using this query

        private void _LoadItems()
        {
            Items.Clear();
            var savedItems = (from key in App.Database.Query<Item, Guid>()
                              select key.LazyValue.Value);

            foreach (var item in savedItems)
            {
                Items.Add(item);
            }
        }

What i don't get is my table definitions look like this. 

        protected override System.Collections.Generic.List<ITableDefinition> _RegisterTables()
        {
            return new List<ITableDefinition>
                       {
                             CreateTableDefinition<Item, Guid>(item=> item.Guid),
                             CreateTableDefinition<SomeCollection, Guid>(collection=> collection.Guid)
                       };
        }
So i don't get why saving a class that contains Item's objects ends up saving it the other table. more importantly how do i get around this?

Coordinator
Mar 23, 2011 at 10:58 AM

I'm not quite following the issue. What's the problem? If you have some collection, and you populate a list of item groups, and you populate items in each item group, then save the "some collection" you should be able to now query the items directly. Is that not happening? Your table definitions imply that you can have duplicate copies of "itemgroup" stored in different somecollection objects, but that items will always have their own unique entries. I guess can you elaborate further on what is or is not happening?

Again, expected behavior with Sterling would be if you have created a somecollection (call it "a"), then added groups a & b, and group a had items 1,2,and 3 and group b had items 4, 5, and 6, then you save somecollection "a", you'll end up with an entry for somecollection that you can load, as well as 6 items that you can load directly. Is that not happening?

Mar 23, 2011 at 11:39 AM
Edited Mar 23, 2011 at 11:41 AM

Sorry if its hard to explain, basically i have a list of Item's that is the primary source of all items. The Some Collection contains more that one group and each of those groups can contain items. I create some sample data for the Some Collection groups but when i save them the items for each of those groups start to show in the primary items collection. 

so the Hierachy looks like

PrimaryItemsCollection
      Item a
      Item b

SomeCollection
     Group1
           Item c
           Item d
     Group2 

What i tought would happen is when you save PrimaryItemCollection it saves it into the Item Table and when you save the SomeCollection it would save any groups and items of those groups into the SomeCollection Table

But the result i get when saving SomeCollection is that "Item c" and "Item d" begin to show in the PrimaryItemsCollection like this 

PrimaryItemsCollection
      Item a
      Item b
      Item c
      Item d 

SomeCollection
     Group1
           Item c
           Item d
     Group2

I think what your saying is thats expected but logically i need to keep them separated. 

Coordinator
Mar 23, 2011 at 1:57 PM

If you can create a standalone test that reproduces the problem I'll look into it - right now I'm still not getting the issue, and all of the tests are passing - not saying it's not broken, but I need to be able to reproduce the problem to fix it. Thanks!

Mar 24, 2011 at 1:32 PM
Edited Mar 24, 2011 at 1:36 PM

Here is a standalone test that recreates the issue, sorry if its a bit long i wanted to keep as much structure the same to be able to reproduce the issue. Basically in this test the items in the library should be kept separate from the available items (ignoring the AddItemToLibrary method i know i didn't removed the item from the AvailableItems when i added it but i realized its not needed to see the issue, the Library sample data shows it)

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wintellect.Sterling;
using Wintellect.Sterling.Database;

namespace WindowsPhoneApplication1
{
    #region Models
    public class Item
    {
        public Item( )
        {
           Guid = Guid.NewGuid();
        }
        public Guid Guid { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
    }

    public class ItemCatalog
    {
        public ItemCatalog( )
        {
            ItemsGroup = new ObservableCollection<Item>();
            Guid = Guid.NewGuid();
        }
        public Guid Guid { get; set; }
        public string Name { get; set; }
        public ObservableCollection<Item> ItemsGroup { get; set; }
    }

    public class Library
    {
        public Library( )
        {
            Catagories = new ObservableCollection<ItemCatalog>();
            Guid = Guid.NewGuid();
        }
        public Guid Guid { get; set; }
        public ObservableCollection<ItemCatalog> Catagories { get; set; }
    }
    #endregion
    #region Database
    public class MyDatabase : BaseDatabaseInstance
    {
        public override string Name
        {
            get { return "MyDatabase"; }
        }

        protected override System.Collections.Generic.List<ITableDefinition> _RegisterTables()
        {
            return new List<ITableDefinition>
                       {
                             CreateTableDefinition<Item, Guid>(backlogItem => backlogItem.Guid),
                             CreateTableDefinition<Library, Guid>(weeklyJobs => weeklyJobs.Guid)
                       };
        }
    }
    #endregion
    [TestClass]
    public class LibraryTests
    {
        #region Database Setup and Cleanup
        private static ISterlingDatabaseInstance _databaseInstance = null;
        private static SterlingEngine _engine = null;
        private static SterlingDefaultLogger _logger = null;

        public static ISterlingDatabaseInstance Database
        {
            get
            {
                return _databaseInstance;
            }
        }

        private void _ActivateEngine()
        {
            _engine = new SterlingEngine();
            _engine.Activate();
            _databaseInstance = _engine.SterlingDatabase.RegisterDatabase<MyDatabase>();
        }

        private void _DeactivateEngine()
        {
            _engine.Dispose();
            _databaseInstance = null;
        }
        #endregion

        #region Available Items Methods
        public static void SaveSampleItems()
        {
            var itemCount = (from i in Database.Query<Item, Guid>()
                             select i).Count();
            if (itemCount == 0)
            {
                List<Item> items = new List<Item>();

                items.Add(new Item() { Title = "New Book1", Description = "Once upon a time..." });
                items.Add(new Item() { Title = "New Book 2", Description = "Children's book..." });
                items.Add(new Item() { Title = "New Book 3", Description = "Biography of ..." });
                foreach (var item in items)
                {
                    Database.Save(typeof(Item), item);
                }
                Database.Flush();
            }
        }

        private void LoadAvailableItems()
        {
            AvailableItems.Clear();
            var savedItems = (from key in Database.Query<Item, Guid>()
                              select key.LazyValue.Value);

            foreach (var item in savedItems)
            {
                AvailableItems.Add(item);
            }
        }
        #endregion

        #region Library Methods
        private void InitalizeLibrary()
        {
            if (Library.Catagories.Count != 0) return;
            Library.Catagories.Add(new ItemCatalog() { Name = "Personal" });
            Library.Catagories.Add(new ItemCatalog() { Name = "Week" });

            for (int i = 0; i < Library.Catagories.Count; i++)
            {
                Library.Catagories[i].ItemsGroup.Add(new Item() { Title = "My Book", Description = "Book owned by me" });
            }
            SaveLibrary(Library);

        }

        public void AddItemToLibrary()
        {
            var itemSelected = (from i in AvailableItems
                                where i.Title == "New Book1" 
                                select i).FirstOrDefault();

            var itemCatagory = (from ig in Library.Catagories
                             where ig.Name == "Personal"
                             select ig).FirstOrDefault();

            itemCatagory.ItemsGroup.Add(itemSelected);
            SaveLibrary(Library);
        }
        public void SaveLibrary(Library library)
        {
            Database.Save(library);
            Database.Flush();
        }
        #endregion

        #region Test Methods
        [TestInitialize]
        public void InitializeTest()
        {
            _ActivateEngine();
            Library = new Library();
            AvailableItems = new ObservableCollection<Item>();
            SaveSampleItems();
            InitalizeLibrary();
            LoadAvailableItems();
        }

        [TestMethod]
        public void SaveLibraryAndLoadItems()
        {
          
            AddItemToLibrary();
            SaveLibrary(Library);
            var ownedItem = (from c in Library.Catagories
                             where c.Name == "Personal"
                             from i in c.ItemsGroup
                             where i.Title == "My Book"
                             select i).FirstOrDefault();

            foreach (var item in AvailableItems)
            {
                Assert.IsFalse(item.Title == ownedItem.Title);
            }


        }
        [TestCleanup]
        public void Cleanup()
        {
            _DeactivateEngine();
        }
        #endregion

        public ObservableCollection<Item> AvailableItems { get; set; }
        public Library Library { get; set; }
    }
}
Coordinator
Mar 24, 2011 at 1:43 PM

Thanks for taking the time to do this! I'll merge it in and check it out over the next few days.

Coordinator
Mar 27, 2011 at 5:32 PM

OK took a look at that and I see the problem.

So Sterling specifically looks at each defined type as a separate table. Because you define the item as a type, it does not matter how nested the item is, any save will result in the "master" item table from being saved as well.

It's no different than in a SQL database. If you have an items table with foreign keys, then all of your tables will point to those items with the foreign key - adding it to another table doesn't remove it from the "master" table.

There are two ways you could segregate this. One would be to add a flag to the item that indicates whether it is available. When it gets added to a library, set the flag to false. Your "global" items are the items where the flag is "true."

Another way would be to create an "available" category, and always move available items from this category to the other category.

It's not a Sterling defect, but a challenge of how you want to use Sterling to accommodate your design.

Mar 27, 2011 at 11:39 PM

Thanks, after writing the test thats what i thought it might be doing. Unfortunately when i think of objects in c# they are normally hierarchical where as when i think about sql they are often flat. Its unfortunate that it adds that extra layer of complexity to your application though.  I Guess i'll great an enum that represents its state and have to modify this anytime something happens.

In your response you said the second way would be to create an available category. when you say category do you mean the class type in my example? if so you would still get the same issue because the category class contains a collection of items as well. 

Coordinator
Mar 27, 2011 at 11:43 PM

Sterling does look at objects as hierarchical. You are asking it to flatten them by specifying "Item" as a type. If you want it to remain a child, simply remove the table definition for Item. Then you can create a type to hold the available items like this:

public class AvailableItems : List<Item> with an id and store them there, and they will always exist as a child of some other object. It's only when you say you want to track them independently, not as a child, by specifying the type as a table that they are pulled out and treated like foreign keys.

Mar 27, 2011 at 11:54 PM

hmm, i'll have to think about what the best approach is then. I don't really remember reading anything about this when i was first setting it up. Can i suggest creating a brief explanation in your documentation that covers this. I think its important to know before you start designing your database/app relationship.