Search code examples
c#genericscollections

What is the best, more practical way to write such an entry with nested dictionaries? Which design pattern to use? C#


There is a lot of data in the database, and it is necessary to produce statistics (find the average number of each operation per day by each user of the application) using c # collections. In my opinion, it is necessary to use dictionaries:

var dict = new Dictionary<long?, Dictionary<DateTime, Dictionary<OperationsGroupType, int>>>();

Please advise a more practical way to write it. As it looks strange. Thank you

I wrote a function:

public void D()
        {
            var dict = new Dictionary<long?, Dictionary<DateTime, Dictionary<OperationsGroupType, int>>>();
            int pageNumber = 0;
            int pageSize = 5;
            int pageCount = 1;

            while (pageNumber < pageCount)
            {
                int count;
                foreach (OperationData op in OperationService.GetPage(pageNumber, pageSize, out count))
                    if(op.PerformedBy.HasValue)
                        if(op.PerformedDate.HasValue)
                            if (dict.ContainsKey(op.PerformedBy))
                                if (dict[op.PerformedBy].ContainsKey(op.PerformedDate.Value.Date.Date))
                                    if (dict[op.PerformedBy][op.PerformedDate.Value.Date.Date.Date.Date].ContainsKey(op.Type)) dict[op.PerformedBy][op.PerformedDate.Value.Date.Date.Date.Date][op.Type]++;
                                    else dict[op.PerformedBy][op.PerformedDate.Value.Date.Date.Date.Date].Add(op.Type, 1);
                                else dict[op.PerformedBy].Add(op.PerformedDate.Value.Date.Date.Date.Date, new Dictionary<OperationsGroupType, int> { { op.Type, 1 } });
                            else dict.Add(op.PerformedBy, new Dictionary<DateTime, Dictionary<OperationsGroupType, int>> { { op.PerformedDate.Value.Date.Date.Date.Date, new Dictionary<OperationsGroupType, int> { { op.Type, 1 } } } });

                pageCount = (count - 1) / pageSize + 1;
                pageNumber++;
            }

            foreach (var item in dict)
            {
                var opDateDict = new Dictionary<DateTime, int>();
                foreach (var operDate in item.Value) opDateDict.Add(operDate.Key, operDate.Value.Sum(count => count.Value));

                SystemLogger.Instance.WriteErrorTrace(String.Format("Average number of user operations {0} per day: {1}\n", item.Key, opDateDict.Values.Sum() / opDateDict.Count));
            }
        }
OperationsGroupType - this enum

Please tell me how to replace the dictionary with a more practical design? Which pattern is best for solving this problem?


Solution

  • It's terribly difficult to say what's best or most practical - and that's because you didn't really define what you mean by "best" or "practical".

    I'm going to define them as minimal code and minimal repetition.

    To start with I created these extension methods:

    public static class Ex
    {
        public static R Ensure<T, R>(this Dictionary<T, R> @this, T key) where R : new
        {
            if (@this.ContainsKey(key))
                return @this[key];
            else
            {
                var r = new R();
                @this[key] = r;
                return r;
            }
        }
    
        public static R Ensure<T, R>(this Dictionary<T, R> @this, T key, Func<R> factory)
        {
            if (@this.ContainsKey(key))
                return @this[key];
            else
            {
                var r = factory();
                @this[key] = r;
                return r;
            }
        }
    }
    

    With those I can rewrite you code like this:

    foreach (OperationData op in OperationService.GetPage(pageNumber, pageSize, out count))
    {
        if (op.PerformedBy.HasValue)
            if (op.PerformedDate.HasValue)
            {
                dict.Ensure(op.PerformedBy).Ensure(op.PerformedDate.Value.Date).Ensure(op.Type, () => 0);
                dict[op.PerformedBy][op.PerformedDate.Value.Date][op.Type]++;
            }
    }