Не уверен, возможно ли то, что я пытаюсь сделать, или нет, но я хотел бы повторно использовать выражение linq для родительского свойства объектов.
С учетом классов:
class Parent {
int Id { get; set; }
IList<Child> Children { get; set; }
string Name { get; set; }
}
class Child{
int Id { get; set; }
Parent Dad { get; set; }
string Name { get; set; }
}
Если у меня есть помощник
Expression<Func<Parent,bool> ParentQuery() {
Expression<Func<Parent,bool> q = p => p.Name=="foo";
}
Затем я хочу использовать это при запросе данных для ребенка, например:
using(var context=new Entities.Context) {
var data=context.Child.Where(c => c.Name=="bar"
&& c.Dad.Where(ParentQuery));
}
Я знаю, что могу сделать это для дочерних коллекций:
using(var context=new Entities.Context) {
var data=context.Parent.Where(p => p.Name=="foo"
&& p.Childen.Where(childQuery));
}
но не вижу способа сделать это для свойства, которое не является коллекцией.
Это просто упрощенный пример, на самом деле ParentQuery будет более сложным, и я хочу избежать повторения этого в нескольких местах, так как вместо двух слоев у меня будет ближе к 5 или 6, но все они должны будут ссылаться родительский запрос для обеспечения безопасности.
Если это невозможно, моей другой мыслью было как-то так эффективно перевести выражение ParentQuery для данного типа:
p => p.Name == "foo";
превращается в:
c => c.Dad.Name == "foo";
но используя дженерики / некоторую другую форму построителя запросов, которая позволяет сохранить родительский запрос, а затем просто создать транслятор для дочернего объекта, который заменяет в свойстве маршрут к родителю.
EDIT:
Исходя из комментариев @David Morton
Первоначально похоже, что я могу просто перейти от Expression к функции делегата, а затем вызвать
.Где (ParentQuery () (c.Dad));
Однако я использую это в более широком шаблоне репозитория и не могу понять, как я могу использовать это с универсальными шаблонами и построителями предикатов - я не хочу получать строки из хранилища и фильтровать на клиенте (в данном случае веб-сервер). У меня есть общий метод получения данных, который принимает запрос базового выражения. Затем я хочу проверить, реализует ли предоставленный тип ISecuredEntity и добавляет ли он securityQuery для сущности, с которой мы имеем дело.
public static IList<T> GetData<T >(Expression<Func<T, bool>> query) {
IList<T> data=null;
var secQuery=RepositoryHelperers.GetScurityQuery<T>();
if(secQuery!=null) {
query.And(secQuery);
}
using(var context=new Entities.Context()) {
var d=context.GetGenericEntitySet<T>();
data=d.ToList();
}
return data;
}
ISecuredEntity:
public interface ISecuredEntity : IEntityBase {
Expression<Func<T, bool>> SecurityQuery<T>();
}
Пример сущности:
public partial class ExampleEntity: ISecuredEntity {
public Expression<Func<T, bool>> SecurityQuery<T>() {
//get specific type expression and make generic
Type genType = typeof(Func<,>).MakeGenericType(typeof(ExampleEntity), typeof(bool));
var q = this.SecurityQuery(user);
return (Expression<Func<T, bool>>)Expression.Lambda(genType, q.Body, q.Parameters);
}
public Expression<Func<ExampleEntity, bool>> SecurityQuery() {
return e => e.OwnerId==currentUser.Id;
}
}
и репозиторий Helpers:
internal static partial class RepositoryHelpers {
internal static Expression<Func<T, bool>> SecureQuery<T>() where T : new() {
var instanceOfT = new T();
if (typeof(Entities.ISecuredEntity).IsAssignableFrom(typeof(T))) {
return ((Entities.ISecuredEntity)instanceOfT).SecurityQuery<T>();
}
return null;
}
}
РЕДАКТИРОВАТЬ Вот (возможное) решение
В итоге я вернулся к использованию выражений и использованию LinqKit Invoke. Примечание: для EF я также должен был вызвать .AsExpandable () для entitySet
Ключевая часть в состоянии звонить:
Product.SecureFunction(user).Invoke(pd.ParentProduct);
чтобы я мог передать контекст в родительский запрос
Мои конечные классы выглядят так:
public interface ISecureEntity {
Func<T,bool> SecureFunction<T>(UserAccount user);
}
public class Product : ISecureEntity {
public Expression<Func<T,bool>> SecureFunction<T>(UserAccount user) {
return SecureFunction(user) as Expression<Func<T,bool>>;
}
public static Expression<Func<Product,bool>> SecureFunction(UserAccount user) {
return f => f.OwnerId==user.AccountId;
}
public string Name { get;set; }
public string OwnerId { get;set; }
}
public class ProductDetail : ISecureEntity {
public Expression<Func<T,bool>> SecureFunction<T>(UserAccount user) {
return SecureFunction(user) as Expression<Func<T,bool>>;
}
public static Func<ProductDetail,bool> SecureFunction(UserAccount user) {
return pd => Product.SecureFunction(user).Invoke(pd.ParentProduct);
}
public int DetailId { get;set; }
public string DetailText { get;set; }
public Product ParentProduct { get;set; }
}
Использование:
public IList<T> GetData<T>() {
IList<T> data=null;
Expression<Func<T,bool>> query=GetSecurityQuery<T>();
using(var context=new Context()) {
var d=context.GetGenericEntitySet<T>().Where(query);
data=d.ToList();
}
return data;
}
private Expression<Func<T,bool>> GetSecurityQuery<T>() where T : new() {
var instanceOfT = new T();
if (typeof(Entities.ISecuredEntity).IsAssignableFrom(typeof(T))) {
return ((Entities.ISecuredEntity)instanceOfT).SecurityQuery<T>(GetCurrentUser());
}
return a => true; //returning a dummy query
}
}
Спасибо за помощь всем.