Seb*_*Seb 10 c# linq expression entity-framework repository
编辑此问题,以期使其更清楚。
我们有实体框架代码优先设置。为了示例目的,我简化了两个类,实际上,与“记录”类似,大约有十多个类,其中“项”是导航属性/外键。
物品类别:
public class Item
{
public int Id { get; set; }
public int AccountId { get; set; }
public List<UserItemMapping> UserItemMappings { get; set; }
public List<GroupItemMapping> GroupItemMappings { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
记录类:
public class Record
{
public int ItemId { get; set; }
public Item Item { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
this.User是每个存储库中注入的用户对象,包含在存储库库中。我们有一个包含以下代码的Item存储库:
var items = this.GetAll()
.Where(i => i.AccountId == this.User.AccountId);
Run Code Online (Sandbox Code Playgroud)
我在存储库的基础上创建了follow表达式,以轻松地对其进行过滤(以希望再次使用)。由于LINQ to实体的工作方式,我们无法使用静态扩展方法(System.NotSupportedException“ LINQ to Entities无法识别方法X,并且该方法无法转换为存储表达式。”)。
protected Expression<Func<Item, bool>> ItemIsOnAccount()
{
return item => item.AccountId == this.User.AccountId;
}
Run Code Online (Sandbox Code Playgroud)
通过这样做,我已经解决了上述情况:
var items = this.GetAll().Where(this.ItemIsOnAccount());
Run Code Online (Sandbox Code Playgroud)
我们还基于该帐户内的用户权限进行了其他过滤(同样,在另一种情况下,我不想在我们拥有的每个存储库中重复此代码):
protected Expression<Func<Item, bool>> SubUserCanAccessItem()
{
return item => this.User.AllowAllItems
|| item.UserItemMappings.Any(d => d.UserId.Value == this.User.Id)
|| item.GroupItemMappings.Any(vm =>
vm.Group.GroupUserMappings
.Any(um => um.UserId == this.User.Id));
}
Run Code Online (Sandbox Code Playgroud)
我可以使用以下方法:
var items = this.GetAll().Where(this.SubUserCanAccessItem());
Run Code Online (Sandbox Code Playgroud)
但是,我们还需要在Record存储库中解决以下问题:
var records = this.GetAll()
.Where(i => i.Item.AccountId == this.User.AccountId);
Run Code Online (Sandbox Code Playgroud)
因为Item是单个导航属性,所以我不知道如何将创建的表达式应用于该对象。
我想重用我在所有其他所有存储库中在存储库中创建的表达式,以便我的“基于权限”代码都位于同一位置,但是我不能简单地将其放入,因为在这种情况下,Where子句是表达式<Func <Record,bool >>。
使用以下方法创建接口:
Item GetItem();
Run Code Online (Sandbox Code Playgroud)
由于存在实体的LINQ,因此无法将其放到Record类上。
我也不能创建基础抽象类并从其继承,因为除Item之外,可能还有其他对象需要过滤。例如,记录中可能还包含具有权限逻辑的“事物”。并非所有对象都需要按“项”和“事物”进行过滤,某些对象仅一个,另一个,另一个。
var items = this.GetAll()
.Where(this.ItemIsOnAccount())
.Where(this.ThingIsOnAccount());
var itemType2s = this.GetAll().Where(this.ThingIsOnAccount());
var itemType3s = this.GetAll().Where(this.ItemIsOnAccount());
Run Code Online (Sandbox Code Playgroud)
因此,只有一个父类是行不通的。
有没有一种方法可以重用已经创建的表达式,或者至少创建一个表达式/修改原始表达式以在OTHER仓库内全面使用,当然可以在GetAll中返回自己的对象,但是所有对象都有一个导航属性到项目?我需要如何修改其他存储库才能使用它们?
谢谢
Iva*_*oev 11
表达式可重用性的第一步是将表达式移到公共静态类。由于在您的情况下,它们与绑定在一起User
,因此我将它们作为User
扩展方法(但请注意,它们将返回表达式):
public static partial class UserFilters
{
public static Expression<Func<Item, bool>> OwnsItem(this User user)
=> item => item.AccountId == user.AccountId;
public static Expression<Func<Item, bool>> CanAccessItem(this User user)
{
if (user.AllowAllItems) return item => true;
return item => item.UserItemMappings.Any(d => d.UserId.Value == user.Id) ||
item.GroupItemMappings.Any(vm => vm.Group.GroupUserMappings.Any(um => um.UserId == user.Id));
}
}
Run Code Online (Sandbox Code Playgroud)
现在Item
存储库将使用
var items = this.GetAll().Where(this.User.OwnsItem());
Run Code Online (Sandbox Code Playgroud)
要么
var items = this.GetAll().Where(this.User.CanAccessItem());
Run Code Online (Sandbox Code Playgroud)
为了可重复用于具有Item
引用的实体,您需要一个小的帮助程序实用程序,用于将其他lambda表达式组成的lambda表达式,类似于将Linq表达式“ obj => obj.Prop”转换为“ parent => parent.obj.Prop”。
可以使用来实现它Expression.Invoke
,但是由于并非所有查询提供程序都支持调用表达式(不能肯定EF6可以支持EF Core,因此),像往常一样,我们将使用自定义表达式访问器将lambda参数表达式替换为另一个任意表达式:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { Source = source, Target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : node;
}
}
Run Code Online (Sandbox Code Playgroud)
而且,这两个组成功能如下(我不喜欢这个名字Compose
,所以有时我用的名字Map
,有时Select
,Bind
,Transform
等,但它们在功能上做同样的,在这种情况下,我使用的是Apply
和ApplyTo
,唯一的区别作为转换方向):
public static partial class ExpressionUtils
{
public static Expression<Func<TOuter, TResult>> Apply<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> outer, Expression<Func<TInner, TResult>> inner)
=> Expression.Lambda<Func<TOuter, TResult>>(inner.Body.ReplaceParameter(inner.Parameters[0], outer.Body), outer.Parameters);
public static Expression<Func<TOuter, TResult>> ApplyTo<TOuter, TInner, TResult>(this Expression<Func<TInner, TResult>> inner, Expression<Func<TOuter, TInner>> outer)
=> outer.Apply(inner);
}
Run Code Online (Sandbox Code Playgroud)
(没有什么特别的,提供了完整的代码)
现在,您可以通过将原始过滤器“应用”到Item
从另一个实体中选择属性的表达式来重用原始过滤器:
public static partial class UserFilters
{
public static Expression<Func<T, bool>> Owns<T>(this User user, Expression<Func<T, Item>> item)
=> user.OwnsItem().ApplyTo(item);
public static Expression<Func<T, bool>> CanAccess<T>(this User user, Expression<Func<T, Item>> item)
=> user.CanAccessItem().ApplyTo(item);
}
Run Code Online (Sandbox Code Playgroud)
并将以下内容添加到实体存储库(在本例中为Record
存储库):
static Expression<Func<Record, Item>> RecordItem => entity => entity.Item;
Run Code Online (Sandbox Code Playgroud)
这将使您可以在那里使用
var records = this.GetAll().Where(this.User.Owns(RecordItem));
Run Code Online (Sandbox Code Playgroud)
要么
var records = this.GetAll().Where(this.User.CanAccess(RecordItem));
Run Code Online (Sandbox Code Playgroud)
这应该足以满足您的要求。
您可以进一步定义这样的接口
public interface IHasItem
{
Item Item { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
并让实体实施
public class Record : IHasItem // <--
{
// Same as in the example - IHasItem.Item is auto implemented
// ...
}
Run Code Online (Sandbox Code Playgroud)
然后添加其他这样的助手
public static partial class UserFilters
{
public static Expression<Func<T, Item>> GetItem<T>() where T : class, IHasItem
=> entity => entity.Item;
public static Expression<Func<T, bool>> OwnsItem<T>(this User user) where T : class, IHasItem
=> user.Owns(GetItem<T>());
public static Expression<Func<T, bool>> CanAccessItem<T>(this User user) where T : class, IHasItem
=> user.CanAccess(GetItem<T>());
}
Run Code Online (Sandbox Code Playgroud)
这将允许您省略RecordItem
存储库中的表达式并使用它来代替
var records = this.GetAll().Where(this.User.OwnsItem<Record>());
Run Code Online (Sandbox Code Playgroud)
要么
var records = this.GetAll().Where(this.User.CanAccessItem<Record>());
Run Code Online (Sandbox Code Playgroud)
不知道它是否可以为您提供更好的可读性,但是它是一个选择,从语法上来说更接近Item
方法。
对于Thing
等,只需添加类似的UserFilters
方法。
作为奖励,您可以更进一步,添加常用PredicateBuilder
方法And
,Or
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body,
right.Body.ReplaceParameter(right.Parameters[0], left.Parameters[0])), left.Parameters);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Expression.Lambda<Func<T, bool>>(Expression.OrElse(left.Body,
right.Body.ReplaceParameter(right.Parameters[0], left.Parameters[0])), left.Parameters);
}
Run Code Online (Sandbox Code Playgroud)
所以你可以根据需要使用这样的东西
var items = this.GetAll().Where(this.User.OwnsItem().Or(this.User.CanAccessItem()));
Run Code Online (Sandbox Code Playgroud)
在Item
存储库中,或
var records = this.GetAll().Where(this.User.OwnsItem<Record>().Or(this.User.CanAccessItem<Record>()));
Run Code Online (Sandbox Code Playgroud)
在Record
存储库中。
小智 -2
您应该只使用 sql(或类似数据库)项目作为谓词。如果您将 this.User.AccountId 放入您的 lambda 中,那么它在数据库中不存在并且无法被它解析,这就是错误消息的来源。
归档时间: |
|
查看次数: |
302 次 |
最近记录: |