< Summary

Information
Class: ArturRios.Data.Export.Abstractions.ColumnMap
Assembly: ArturRios.Data.Export
File(s): /home/ci/agent/workspace/dotnet-data-docs_main/app/src/ArturRios.Data.Export/Abstractions/ColumnMap.cs
Line coverage
100%
Covered lines: 26
Uncovered lines: 0
Coverable lines: 26
Total lines: 63
Line coverage: 100%
Branch coverage
80%
Covered branches: 8
Total branches: 10
Branch coverage: 80%
Method coverage
100%
Covered methods: 5
Fully covered methods: 5
Total methods: 5
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
For()100%11100%
For(...)100%11100%
Build(...)80%1010100%
BuildGetter(...)100%11100%

File(s)

/home/ci/agent/workspace/dotnet-data-docs_main/app/src/ArturRios.Data.Export/Abstractions/ColumnMap.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Linq.Expressions;
 3using System.Reflection;
 4using ArturRios.Data.Export.Attributes;
 5
 6namespace ArturRios.Data.Export.Abstractions;
 7
 8/// <summary>A single export column: a header and a compiled getter over the record.</summary>
 9public sealed class Column(string header, Func<object, object?> getter)
 10{
 11    /// <summary>The column header.</summary>
 12    public string Header { get; } = header;
 13
 14    /// <summary>Reads the column value from a record instance.</summary>
 15    public Func<object, object?> Getter { get; } = getter;
 16}
 17
 18/// <summary>Builds and caches the ordered column plan for a record type.</summary>
 19public static class ColumnMap
 20{
 221    private static readonly ConcurrentDictionary<Type, IReadOnlyList<Column>> Cache = new();
 22
 23    /// <summary>Returns the cached column plan for <typeparamref name="T" />.</summary>
 6024    public static IReadOnlyList<Column> For<T>() => For(typeof(T));
 25
 26    /// <summary>Returns the cached column plan for <paramref name="type" />.</summary>
 6027    public static IReadOnlyList<Column> For(Type type) => Cache.GetOrAdd(type, Build);
 28
 29    private static IReadOnlyList<Column> Build(Type type)
 30    {
 831        var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
 3632            .Where(p => p.CanRead && p.GetIndexParameters().Length == 0
 3633                        && p.GetCustomAttribute<ExportIgnoreAttribute>() is null);
 34
 835        var planned = properties.Select(p =>
 836        {
 2637            var attribute = p.GetCustomAttribute<ExportColumnAttribute>();
 2638            return new
 2639            {
 2640                Header = attribute?.Name ?? p.Name,
 2641                Order = attribute?.Order ?? int.MaxValue,
 2642                Token = p.MetadataToken,
 2643                Getter = BuildGetter(p)
 2644            };
 845        });
 46
 847        return planned
 7848            .OrderBy(x => x.Order).ThenBy(x => x.Token).ThenBy(x => x.Header, StringComparer.Ordinal)
 2649            .Select(x => new Column(x.Header, x.Getter))
 850            .ToArray();
 51    }
 52
 53    // Compiles a delegate getter once per property (cached with the column plan) to avoid per-row reflection.
 54    private static Func<object, object?> BuildGetter(PropertyInfo property)
 55    {
 2656        var instance = Expression.Parameter(typeof(object), "instance");
 2657        var typed = Expression.Convert(instance, property.DeclaringType!);
 2658        var access = Expression.Property(typed, property);
 2659        var boxed = Expression.Convert(access, typeof(object));
 60
 2661        return Expression.Lambda<Func<object, object?>>(boxed, instance).Compile();
 62    }
 63}