1

Base description and context:

Structure-wise there is a database object that has lists of objects (basically each list is a table), and each member of said lists can have its own lists.

All the XML stuff gets done in the background so the coder doesn't have to worry about that.

If an object in a list has a list or lists of his own, they are empty until object.open() is called (loading data from XML to memory).

The problem is navigating this structure. It would be great if I could call something like GetLazilyInitializedXX() that would return a new list with the same references, but which has an automated action when any member is accessed (but ONLY then. Not all of them, unless foreach really goes through all of them for example!). Something like LazyList<>.

Example of use: I need to find the latest yCount (called amount in the code) data from a deeper part of the structure. The lists are sorted from the oldest record date to newest. So currently I have to make a new list with a reversed order, use foreach on each subsequent list and manually call .open() on the list members, and return the selection when yCount is reached.

If the objects in lists would have .open() when being accessed / getted (on any way, i.e. even in foreach and only those that really were "getted") from such a "LazyList", a "select / where" could be used (even in foreach), simplifying a lot of the code and removing a lot of the nesting.

Current WIP code (with a substantial amount of nesting):

/// <summary>
/// Gets the latest Daily Test Data.
/// </summary>
/// <param name="amount">The amount of Daily Tests to get</param>
/// <param name="fromDateTime">The date to get Daily Tests from (default - today)</param>
/// <returns></returns>
public static List<QCUnitDailyTestData> GetLatestDailyTestData(int amount = 1, DateTime? fromDateTime = null)
{
    List<QCUnitDailyTestData> result = new List<QCUnitDailyTestData>();

    //Removing the time from the date. If fromDateTime is null, using current DateTime
    var fromDate = fromDateTime?.Date ?? DateTime.Now.Date;

    Log.Info("Selecting " + amount + " latest Daily Tests from: " + fromDate); //todo format fromDate?

    //We need to go through years from the latest to oldest.
    foreach (ProductionYear productionYear in Database.GetProductionYearsFromLatest())
    {
        //LazyInit!
        productionYear.Open();

        //We need to go through days from the latest to oldest.
        foreach (ProductionDay productionDay in productionYear.GetProductionDaysFromLatest())
        {
            //LazyInit!
            productionDay.Open();

            //If the production day is before fromDate or it is the same day as fromDate...
            if (productionDay.Data.Date.Date.CompareTo(fromDate) < 1)
            {
                //We only want the newest daily test. The other ones (in the same day) aren't used!
                QCUnitDailyTest dailyTest = productionDay.GetDailyTestsFromLatest().FirstOrDefault();

                //A day doesn't have to have a daily test - if there is no daily test, continue with the next day
                if (dailyTest == null) continue;

                //LazyInit!
                dailyTest.Open();

                result.Add(dailyTest.Data); //Adding to the resulting list

                //If we have enough, we return the resulting list
                if (result.Count == amount)
                {
                    Log.Info("Selection completed successfully.");
                    return result;
                }
            }
        }
    }

    //if there are no Daily Tests matching our criteria, or there weren't enough of them, we return either an empty
    // list or those we found
    int count = result.Count;
    Log.Warn(count == 0 ? "No Daily Tests were found." : "Only " + count + " Daily Tests were found.");
    return result;
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
Templayer
  • 11
  • 2
  • Lazy = because it's big? See [this](https://stackoverflow.com/q/32782653/1997232). – Sinatr May 14 '20 at 07:25
  • @Sinatr Not only because the XML files are big, but also because there is a bazillion of them... but that is just the context, as I cannot change anything in regards to the XML files - the person that did it spent a lot of time on it and were are in a corporate environment / on a schedule etc. so changing the XML part of it is out of question. – Templayer May 14 '20 at 07:33
  • Could you not change the type to a IList or IReadOnlyList and make a custom implementation of this with whatever lazy loading logic you want? Or maybe return an IEnumerable and use an iterator block? – JonasH May 14 '20 at 08:02
  • The architecture is *very* old-school... I'd expect to see something like that in legacy projects with ADO.NET... I'm not sure you can do much about it as you're bound with the architecture where each model is also a mini `DbContext` and knows how to access database(xml file) which among other things violates SOLID principles... Well, theoretically you can use LINQ `.Where().Take(x)` instead of the second `foreach` loop but that would only make things worse. If code in example works - just use it. – Fabjan May 14 '20 at 08:03
  • "I'd expect to see something like that in legacy projects with ADO.NET" V You haven't seen our ancient Java project. From a version of Java that didn't even have enums. SO. MANY. BOOLEAN COLUMNS. IN. DATABASE!! :D :D :D – Templayer May 15 '20 at 07:30

1 Answers1

0

I did solve it but the solution is very specific to our "database" and the main question stands - how to automatically do an action with a List<> element upon said element being accessed in any way.

Data.Controller:

/// <summary>
/// Gets the latest Daily Test Data.
/// </summary>
/// <param name="amount">The amount of Daily Tests to get. Default - 1</param>
/// <param name="fromDateTime">The date to get Daily Tests from (default - today)</param>
/// <returns></returns>
public static List<QCUnitDailyTestData> GetLatestDailyTestData(int amount = 1, DateTime? fromDateTime = null)
{
    List<QCUnitDailyTestData> result = new List<QCUnitDailyTestData>();

    //Removing the time from the date. If fromDateTime is null, using current DateTime
    var fromDate = fromDateTime?.Date ?? DateTime.Now.Date;

    Log.Info("Selecting " + amount + " latest Daily Tests from: " + fromDate); //todo format fromDate?

    //We need to go through years, days and daily tests from the latest to oldest.
    foreach (var dailyTest in
        from productionYear in Database.GetProductionYearsFromLatest()
        from productionDay in productionYear.GetProductionDaysFromLatest()
            where productionDay.GetData().Date.Date.CompareTo(fromDate) < 1 //If the production day is before fromDate or it is the same day as fromDate...
        select productionDay.GetDailyTestsFromLatest().FirstOrDefault()//We only want the newest daily test. The other ones (in the same day) aren't used!
        into dailyTest where dailyTest != null /*A prod. day doesn't have to have a daily test*/ select dailyTest)
    {
        result.Add(dailyTest.GetData()); //Adding to the resulting list

     //If we have enough, we return the resulting list
     if (result.Count == amount)
     {
         Log.Info("Selection completed successfully.");
         return result;
     }
 }

        //if there are no Daily Tests matching our criteria, or there weren't enough of them, we return either an empty
        // list or those we found
        int count = result.Count;
        Log.Warn(count == 0 ? "No Daily Tests were found."
                            : "Only " + count + " Daily Tests were found.");
        return result;
    }

Data.ProductionYear:

    /// <summary>
    ///  <see cref="Days"/> lists <see cref="ProductionDay"/> instances.
    /// </summary>
    private readonly List<ProductionDay> Days = new List<ProductionDay>();

    /// <summary>
    /// For LazyInits - opens the year on access to days
    /// </summary>
    /// <returns></returns>
    public List<ProductionDay> GetDays()
    {
        if (!Opened) Open();
        return Days;
    }

    /// <summary>
    /// Returns A NEW INSTANCE of production days, with the order from latest to newest
    /// </summary>
    /// <returns>See summary</returns>
    public List<ProductionDay> GetProductionDaysFromLatest()
    {
        List<ProductionDay> days = new List<ProductionDay>(GetDays());
        days.Sort((day1, day2) => day1.GetDate(true).CompareTo(day2.GetDate(true)));
        return days;
    }

And just for completeness' sake Data.ProductionDay:

    private DateTime Date => Data.Date; //Nullpointer when the day isn't opened
    private DateTime DateLazy;

    /// <summary>
    /// If closed, return DataLazy instead of Data.Date, unless lazyStrict is false
    /// </summary>
    /// <returns>See summary</returns>
    public DateTime GetDate(bool lazyStrict = false)
    {
        if (!Opened && !lazyStrict)
        {
            Open();
            return Data.Date;
        }

        return Data?.Date ?? DateLazy;
    }

But as I said, this solution is pretty much specific to our project.

EDIT: Also the formatting (brackets...) are kinda screwed up. They are not in the coding environment (JetBrains Rider), so I will probably just keep them in here as they are, in all their screwy glory.

EDIT2: Also the code provided for years and days are not the whole classes, only the relevant parts.

Templayer
  • 11
  • 2
  • I was able to do something like that via Aspect Programming (AspectJ) in Java. There an action is done when a field is accessed in any way (before returning it for example) - including getting the field value via reflection. – Templayer May 15 '20 at 07:34