Using DataQuery / ObjectQuery & Unit Tests with CustomTableItem / CustomTableItemProvider

Sean Wright asked on April 3, 2015 16:42

Hello,

I am starting a project (running on Kentico 8.2.13) where we will be utilizing Custom Tables instead of Documents in the tree. We are creating a separate project to represent the abstractions of these custom table objects and the Kentico API calls to retrieve and modify this data.

Here is an example of what I would like to achieve with these wrapper classes over the Custom Table data calls. In this case, it would be a custom table implementation of an Address (I know there is already a Kentico class & table for Addresses).

public class Address : AbstractInfo<Address>
{
    public int AddressID { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string AddressCity { get; set; }
    public int AddressStateID { get; set; }
    public string AddressZip { get; set; }

    public Address() { }
}

public class AddressProvider : AbstractInfoProvider<Address, AddressProvider> {
    public AddressProvider() {}

    public static Address GetAddress(int ID)
    {
        // Doesn't work - cannot convert Address (AbstractInfo) to CustomTableItem
        var query = CustomTableItemProvider.GetItems<Address>();
        return query.Where<Address>(a => a.AddressID == ID).FirstOrDefault();
    }
}

I'm not sure where to go from here. What is the best way to create my own wrappers or providers and get strongly typed data out of custom tables using DataQuery (ideally) or ObjectQuery (not ideal).

I would also like to write Unit Tests for these classes that wrap the calls to the Kentico API. I set up my Test project as directed in this Kentico devnet article and the test assertions passed ( Assert.IsNotNull(cls); and Assert.AreEqual(1, cls.ClassID); ).

Now I would like to Fake/mock my wrapper classes which means I need a way to mock the Address and AddressProvider classes as well as some data. I was thinking something along these lines initially but this seems to be the wrong direction

[TestInitialize]
    public void Init()
    {
        Fake<Address, AddressProvider>().WithData(
            Address.New(ad =>
            {

            })
        );
    }

Thanks!

Recent Answers


Sean Wright answered on April 4, 2015 07:37

I ended up trying to solve the Object/Provider wrapping classes using the approach below

public class Address : CustomTableItem
{
    new public const string ClassName = "custom.tablename";

    public int AddressID { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string AddressCity { get; set; }
    public int AddressStateID { get; set; }
    public string AddressZip { get; set; }

    public Address() : base(ClassName) { }
}

public class AddressProvider 
{
    public static Address GetAddressByID(int ID)
    {
        Address address = CustomTableItemProvider.GetItem<Address>(ID);
        return address;
    }

    public static IEnumerable<Address> GetAddressesInCity(string City)
    {
        var query = CustomTableItemProvider.GetItems<Address>();

        return query.Where<Address>(adr => adr.City == City).AsEnumerable();
    }
}

I still have several questions about this approach.

  1. Does CustomTableItemProvider.GetItems<Type>() return an ObjectQuery that will only be parsed and executed in the database when enumerated over or will it end up getting all objects and then filter them in memory?
  2. Is wrapping calls to typed methods of CustomTableItemProvider and using classes inheriting from CustomTableItem the best way to work with strongly typed custom table objects in Kentico 8.x?
  3. How does the Kentico DataQuery/ObjectQuery API interact with LINQ methods like .AsEnumerable(), .AsQueryable(), ect... can I expect them to work the same as they do in standard .NET LINQ?
  4. Do calls to CustomTableItemProvider leverage Kentico's caching capabilities?
  5. How would I write unit tests against my Address and AddressProvider classes with mocked data?
0 votesVote for this answer Mark as a Correct answer

Josef Dvorak answered on April 20, 2015 13:44

Hello,

We have been in contact with Sean via email, so I am posting the answers here, should anyone else run into the same scenario.

Our API works on the database level, but you should make sure you do not mix in a LINQ statement, which executes the query and works with the data in application memory. The methods are .NET static extension methods which amplify ObjectQuery class, since it implements IQueryable interface. The ObjectQuery is IQueryable out-of-the-box, but supports only basic syntax. When you use something not-supported, you will get fallback to enumerable. If you want to use LINQ, we recommend to call Enumerable methods instead of Queryable.

Using CustomTableItemProvider is the best method of accessing data in CustomTable, but that may not be the best option for your scenario. Kentico allows you to define Classes and class fields the same way as you do with Custom Tables using Custom modules, and then generate ready to use ClassInfo and ClassProvider code. This approach has the benefit of using our Abstract provider class that we use for our classes, so it natively uses hashtables to cache data, and will be able to use the Fake class to fake test data. This object will behave like any other Kentico InfoObject or Provider. The only downside is that you will need to define UI for your custom module, but that can be done from the UI using the predefined UI Page templates for listing and editing.

The DataEngine API uses hashtables to store object information and they can use ID, CodeName or GUID as key. If the Kentico class you work with has these hashtables enabled, any method that retrieves a single object based these fields will use hashtable entry if available. Most built-in Kentico classes use these hashtables, but some objects may have it turned off to conserve memory. This usually only happens for infrequently used objects, or if the class is missing that particular field (i.e. GUID). For example the CustomTableItemProvider has ID and GUID hashtables turned on, so as long as you retrieve single object this way it will come from cache if it is there. Other object may have more aggressive caching turned on. For example the SKUInfoProvider will retrieve all SKUs and store it in hashtable the first time it is used to retrieve one.

The hashtables are not used when loading more than one object, or when using SQL conditions in the DataEngine API. These are executed against the database. If you would like to cache results of such queries, for example in your web parts, you will need to do so using CacheHelper.

The Fake class that you tried to use is meant to substitute ProviderObject of our AbstractProvider Class with a copy that has fake data loaded. Since your custom class is not a Kentico provider it has no ProviderObject property, and therefore it has no use for you. You would need to write the logic that returns fake data directly into your provider class.

1 votesVote for this answer Mark as a Correct answer

Mohamed Rashed answered on December 17, 2015 11:47

Hi Sean, what is the final output you have?

we also build a project and heavily depend on custom tables, and when I see this thread, it's exactly describe our situation, we need to use a strongly typed classes, and unit testable classes.

I hope to share your experience it will be helpful for us in designing the solution.

Thanks

0 votesVote for this answer Mark as a Correct answer

Sean Wright answered on January 13, 2016 06:44

@Mohamed Rashed We took Josef's advice and moved to a Custom Module and custom Info/Provider classes for all of our objects. We also ended up implementing a very custom caching layer using the Kentico CacheHelper API. The Custom Module approach was much easier to work with and much more powerful but it required some configuration to get everything working how we wanted (https://docs.kentico.com/display/K9/Creating+custom+modules).

Given the limitations of Kentico's 'unit testing' we did not end up delving to far into it but I really hope to see more added to it in the future (dependency injection and interfaces are two things currently missing in Kentico that would make this easier).

0 votesVote for this answer Mark as a Correct answer

   Please, sign in to be able to submit a new answer.