【发布时间】:2010-03-18 21:20:00
【问题描述】:
不确定我正在尝试的是否可行,但我想在对象父属性上重用 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 会更复杂,我想避免在多个地方重复这种情况,而不是只有 2 层,我将接近 5 或 6 层,但它们都需要参考父查询以确保安全。
如果这不可能,我的另一个想法是以某种方式有效地将 ParentQuery 表达式转换为给定类型: p => p.Name=="foo"; 变成: c => c.Dad.Name=="foo"; 但是使用泛型/某种其他形式的查询构建器,允许它保留父查询,然后只需要为每个子对象构建一个翻译器,该翻译器在属性路由中替换到父对象。
编辑: 继@David morton 的 cmets
最初看起来我可以从表达式更改为委托函数,然后调用 .Where(ParentQuery()(c.Dad));
但是我在更广泛的存储库模式中使用它,并且看不到如何将它与泛型和谓词构建器一起使用 - 我不想从存储中检索行并在客户端(在本例中为 Web 服务器)进行过滤。我有一个接受基本表达式查询的通用获取数据方法。然后我想测试一下提供的类型是否实现了 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;
}
}
和存储库助手:
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,我还必须在 entitySet 上调用 .AsExpandable()
关键部分是能够调用:
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
}
}
感谢大家的帮助。
【问题讨论】: