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);
}
}