Home > Enterprise >  Using LINQ to select EF properties in DBSet with specific Attributes
Using LINQ to select EF properties in DBSet with specific Attributes

Time:09-17

Is there a way to perform a LINQ query on Entity Framework DBSets and only return the properties that have a specific custom attribute?

My goal is to perform a query and return only the properties/columns that I need for an export.

I also want to make this an extension for IEnumerable as I have many separate EF Classes that will use this export attribute.

This is how I am visualizing it:

public class Person
{
    [Export]
    public string FirstName { get; set; }

    [Export]
    public string LastName { get; set; }

    [Export]
    public string Address { get; set; }

    [NeverExport]
    public string SocialSecurityNumber { get; set; }
}

public void main()
{
    var people = PersonRepository.GetAll();
    var exportResults = people.GetByAttribute(ExportAttribute);
{

public static IEnumerable<object> GetByAttribute(this IEnumerable<object> list, Attribute attribute)
{
    return list
        .Select(x =>
           // Select properties with { attribute } (ie. [Export])
        );
}

CodePudding user response:

I've made a very basic example for your problem. In short, you want to get all Properties, where a specific Attribute is present. You can achive this via reflection. First, you want all properties of a specific type, then only the properties, where a specific attribute is present.

Here is my example code:

using System;
using System.Linq;
using System.Reflection;

namespace Sandbox
{
    public class Program
    {
        public static void Main(string[] args)
        {

            var attrs = typeof(User).GetProperties().Where(x => x.GetCustomAttributes().Any(y => y.GetType() == typeof(Custom)));

            var users = new User[] 
                {
                    new User() { ID = 1, Name = "John", Lastname = "Doe" },
                    new User() { ID = 2, Name = "Jane", Lastname = "Doe" } 
                };

            foreach(var u in users)
            {
                foreach(var attr in attrs)
                {
                    Console.WriteLine(typeof(User).GetProperty(attr.Name).GetValue(u, null));
                }
            }
        }
    }


    public class User
    {
        public string Name { get; set; }
        [Custom]
        public int ID { get; set; }
        [Custom]
        public string Lastname { get; set; }
    }

    public class Custom : System.Attribute
    {

    }
}

CodePudding user response:

The following code projects any IQueryable to IQueryable<ExpandoObject> and you can use it for exporting data. Additionally code caches projection expression for reusing later in application.

Usage is simple:

var exportResult = anyQuery.ProjectForExport().ToList();

And implementation:

[AttributeUsage(AttributeTargets.Property)]
public class ExportAttribute : Attribute
{

}
public static class ExportTools
{
    private static readonly ConstructorInfo _expandoObjectConstructor =
        typeof(ExpandoObject).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException();

    private static readonly MethodInfo _expandoAddMethodInfo = typeof(IDictionary<string, object>).GetTypeInfo()
        .GetRuntimeMethods()
        .Single(mi => mi.Name == nameof(IDictionary<string, object>.Add) && mi.GetParameters().Length == 2);

    public static class ProjectionCache<T>
    {
        public static Expression<Func<T, ExpandoObject>> ProjectionExpression { get; } = BuildProjection();

        private static Expression<Func<T, ExpandoObject>> BuildProjection()
        {
            var param = Expression.Parameter(typeof(T), "e");

            var properties = typeof(T).GetProperties()
                .Where(p => p.GetCustomAttributes().Any(a => a is ExportAttribute)).ToList();

            if (properties.Count == 0)
                throw new InvalidOperationException($"Type '{typeof(T).Name}' has no defined Export properties.");

            var expr = (Expression)Expression.ListInit(
                Expression.New(_expandoObjectConstructor),
                properties.Select(p =>
                {
                    var readerExpr = Expression.MakeMemberAccess(param, p);
                    return Expression.ElementInit(_expandoAddMethodInfo, Expression.Constant(p.Name), readerExpr);
                }));

            var lambda = Expression.Lambda<Func<T, ExpandoObject>>(expr, param);
            return lambda;
        }
    }

    public static IQueryable<ExpandoObject> ProjectForExport<T>(this IQueryable<T> query)
    {
        return query.Select(ProjectionCache<T>.ProjectionExpression);
    }
}
  • Related