Search code examples
c#linqextension-methodssystem.reflection

Filtering based on PropertyInfo Name


I am trying to modify the code below:

public static string ToCsv<T>(this IEnumerable<T> list, List<string> keyIndicators, List<string> inTreatmentCohort)
{
    var type = typeof(T);
    var props = type.GetProperties();

    //Setup expression constants
    var param = Expression.Parameter(type, "x");
    var doublequote = Expression.Constant("\"");
    var doublequoteescape = Expression.Constant("\"\"");
    var comma = Expression.Constant(",");

    //Convert all properties to strings, escape and enclose in double quotes
    var propq = (from prop in props
        let tostringcall = Expression.Call(Expression.Property(param, prop),
            prop.ReflectedType.GetMethod("ToString", new Type[0]))
        let replacecall = Expression.Call(tostringcall,
            typeof(string).GetMethod("Replace", new[] {typeof(string), typeof(string)}), doublequote,
            doublequoteescape)
        select Expression.Call(
            typeof(string).GetMethod("Concat", new[] {typeof(string), typeof(string), typeof(string)}),
            doublequote, replacecall, doublequote)
    ).ToArray();

    var concatLine = propq[0];
    for (var i = 1; i < propq.Length; i++)
        concatLine =
            Expression.Call(
                typeof(string).GetMethod("Concat", new[] {typeof(string), typeof(string), typeof(string)}),
                concatLine, comma, propq[i]);

    var method = Expression.Lambda<Func<T, string>>(concatLine, param).Compile();
    var header = string.Join(",", props.Select(p => p.Name).ToArray());

    return header + Environment.NewLine + string.Join(Environment.NewLine, list.Select(method).ToArray());
}

I need to filter based on the two lists (keyIndicators and inTreatmentCohort) which contain the suffix and prefix of the PropertyInfo Name field contained in the list being converted to CSV.

I tried adding a where keyIndicators.Contains(prop.Name) essentially simulating an IN statement this returns null.

I also tried putting the propq query in a foreach loop this brought the correct data but all the column names shown rather than just the column names that are selected in the keyIndicators or inTreatmentCohort list.

How do I modify this code to only return a csv that contains the columns in the two filtering lists rather than all the columns in list being converted?


Revised to include new linq queries

Have revised the code that I previously provided the Contains is now working as intended, these changes can be seen below:

public static string ToCsv<T>(this IEnumerable<T> list, List<string> keyIndicators,
    List<string> inTreatmentCohorts)
    {
        var type = typeof(T);
        var props = type.GetProperties();

        //Setup expression constants
        var param = Expression.Parameter(type, "x");
        var doublequote = Expression.Constant("\"");
        var doublequoteescape = Expression.Constant("\"\"");
        var comma = Expression.Constant(",");
        MethodCallExpression[] propq = { };
        var propqList = new List<MethodCallExpression>();

        //Convert all properties to strings, escape and enclose in double quotes


        foreach (var keyIndicator in keyIndicators)
        {
            if (keyIndicator != "Pend" && keyIndicator != "Area" && keyIndicator != "DrugGroup" &&
                keyIndicator != "Gender") continue;
            propq = (from prop in props
                let tostringcall = Expression.Call(Expression.Property(param, prop),
                    prop.ReflectedType.GetMethod("ToString", new Type[0]))
                let replacecall = Expression.Call(tostringcall,
                    typeof(string).GetMethod("Replace", new[] { typeof(string), typeof(string) }), doublequote,
                    doublequoteescape)
                where prop.Name.Contains(keyIndicator)
                select Expression.Call(
                    typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string), typeof(string) }),
                    doublequote, replacecall, doublequote)
            ).ToArray();

            propqList.AddRange(propq);
        }

        foreach (var keyIndicator in keyIndicators)
        {
            foreach (var inTreatmentCohort in inTreatmentCohorts)
            {
                propq = (from prop in props
                         let tostringcall = Expression.Call(Expression.Property(param, prop),
                             prop.ReflectedType.GetMethod("ToString", new Type[0]))
                         let replacecall = Expression.Call(tostringcall,
                             typeof(string).GetMethod("Replace", new[] { typeof(string), typeof(string) }),
                             doublequote,
                             doublequoteescape)
                         where prop.Name.Contains(inTreatmentCohort) && prop.Name.Contains(keyIndicator)
                         select Expression.Call(
                             typeof(string).GetMethod("Concat",
                                 new[] { typeof(string), typeof(string), typeof(string) }),
                             doublequote, replacecall, doublequote)
                ).ToArray();
            }

            propqList.AddRange(propq);
        }

        propq = propqList.ToArray();

        var concatLine = propq[0];
        for (var i = 1; i < propq.Length; i++)
            concatLine =
                Expression.Call(
                    typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string), typeof(string) }),
                    concatLine, comma, propq[i]);

        var method = Expression.Lambda<Func<T, string>>(concatLine, param).Compile();
        var header = string.Join(",", props.Select(p => p.Name).ToArray());

        return header + Environment.NewLine + string.Join(Environment.NewLine, list.Select(method).ToArray());
    }

I need to rewrite the LINQ query for var header so that only columns selected in propq are shown when the csv is generated.


Solution

  • After making some revisions I managed to get the Contains to work in conjunction with a couple of foreach loops for processing the two filter lists.

    the complete code is shown below:

    public static string ToCsv<T>(this IEnumerable<T> list, List<string> keyIndicators,
        List<string> inTreatmentCohorts)
        {
            var type = typeof(T);
            var props = type.GetProperties();
    
            //Setup expression constants
            var param = Expression.Parameter(type, "x");
            var doublequote = Expression.Constant("\"");
            var doublequoteescape = Expression.Constant("\"\"");
            var comma = Expression.Constant(",");
            MethodCallExpression[] propq = { };
            var propqList = new List<MethodCallExpression>();
            var columnNames = new List<string>();
    
            //Convert all properties to strings, escape and enclose in double quotes
    
            foreach (var keyIndicator in keyIndicators)
            {
                foreach (var inTreatmentCohort in inTreatmentCohorts)
                {
                    propq = (from prop in props
                        let tostringcall = Expression.Call(Expression.Property(param, prop),
                            prop.ReflectedType.GetMethod("ToString", new Type[0]))
                        let replacecall = Expression.Call(tostringcall,
                            typeof(string).GetMethod("Replace", new[] {typeof(string), typeof(string)}), doublequote,
                            doublequoteescape)
                        where prop.Name.Contains(keyIndicator) && prop.Name.Contains(inTreatmentCohort)
                        select Expression.Call(
                            typeof(string).GetMethod("Concat", new[] {typeof(string), typeof(string), typeof(string)}),
                            doublequote, replacecall, doublequote)
                    ).ToArray();
    
                    var columnNameQuery = (from prop in props
                        where prop.Name.Contains(keyIndicator) && prop.Name.Contains(inTreatmentCohort)
                        select prop.Name);
    
                    columnNames.AddRange(columnNameQuery);
    
                    propqList.AddRange(propq);
                }
            }
    
            propq = propqList.ToArray();
    
            var concatLine = propq[0];
            for (var i = 1; i < propq.Length; i++)
                concatLine =
                    Expression.Call(
                        typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string), typeof(string) }),
                        concatLine, comma, propq[i]);
    
            var method = Expression.Lambda<Func<T, string>>(concatLine, param).Compile();
            var header = string.Join(",", columnNames.ToArray());
    
            return header + Environment.NewLine + string.Join(Environment.NewLine, list.Select(method).ToArray());
        }
    

    The column names that I want to be included in the CSV are added to a columnNames list and are then added to the header variable so the the columns and the data match up in the CSV.