C# 对象中对象上的实体框架LINQ表达式

C# 对象中对象上的实体框架LINQ表达式,c#,entity-framework,linq,repository,expression,C#,Entity Framework,Linq,Repository,Expression,编辑这个问题是为了让它更清楚 我们首先设置了实体框架代码。出于示例的目的,我简化了两个类,实际上还有10多个类类似于“记录”,其中项是导航属性/外键 项目类别: public class Item { public int Id { get; set; } public int AccountId { get; set; } public List<UserItemMapping> UserItemMappings { get; set; } publ

编辑这个问题是为了让它更清楚

我们首先设置了实体框架代码。出于示例的目的,我简化了两个类,实际上还有10多个类类似于“记录”,其中项是导航属性/外键

项目类别:

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; }
}
User是每个repo中注入的用户对象,包含在存储库库中。 我们有一个项目存储库,其代码如下:

var items = this.GetAll()
    .Where(i => i.AccountId == this.User.AccountId);
我在存储库基础上创建了follow表达式,以方便对其进行过滤(希望可以重用)。由于LINQ to entities的工作方式,我们无法使用静态扩展方法(System.NotSupportedException“LINQ to entities无法识别方法X,并且此方法无法转换为存储表达式”)

我们根据该帐户内的用户权限进行了额外的筛选(同样,在另一种情况下,我不想在我们的每次回购中重复此代码):

但是,我们还需要在记录存储库中解决以下问题:

var records = this.GetAll()
    .Where(i => i.Item.AccountId == this.User.AccountId);
因为项是单个导航属性,所以我不知道如何将我创建的表达式应用于此对象

我想重用我在repo库中基于所有其他repo创建的表达式,这样我的“基于权限的”代码都在同一个位置,但我不能简单地将其插入,因为本例中的Where子句是expression>

使用以下方法创建接口:

Item GetItem();
由于LINQ对实体的限制,将其放在记录类上不起作用

我也不能创建一个基础抽象类并从中继承,因为除了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());
因此,只有一个父类是不可行的

是否有一种方法可以重用我已经创建的表达式,或者至少创建一个表达式/修改原始表达式,以便在其他repo中全面工作,这些repo当然会在GetAll中返回它们自己的对象,但所有repo都有一个到项的导航属性?我需要如何修改其他回购协议才能使用这些协议


谢谢

您应该只对谓词使用sql(或类似数据库的)项。如果您将this.User.AccountId放入lambda中,它在数据库中不存在,也无法被解析,这就是错误消息的来源。

我无法确定这是否适用于您的情况,这取决于实体的设置方式,但您可以尝试的一件事是使用一个像IHasItemProperty这样的接口和一个GetItem()方法,并使要使用此接口的实体实现该接口。大概是这样的:

public interface IHasItemProperty {
    Item GetItem();
}

public class Item: IHasItemProperty {

    public Item GetItem() {
       return this;
    }

    public int UserId {get; set;}
}

public class Record: IHasItemProperty {
    public Item item{get;set;}

    public Item GetItem() {
        return this.item;
    }
}

public class Repo
{
    protected Expression<Func<T, bool>> ItemIsOnAccount<T>() where T: IHasItemProperty
    {
        return entity => entity.GetItem().UserId == 5;
    }

}
公共接口IHasItemProperty{
Item GetItem();
}
公共类项目:IHasItemProperty{
公共项GetItem(){
归还这个;
}
public int UserId{get;set;}
}
公共类记录:IHasItemProperty{
公共项项{get;set;}
公共项GetItem(){
退回此项目;
}
}
公开回购
{
受保护的表达式ItemIsOnAccount(),其中T:IHasItemProperty
{
返回entity=>entity.GetItem().UserId==5;
}
}

我使用int只是为了使事情更简单。

表达式可重用性的第一步是将表达式移动到公共静态类。因为在您的例子中,它们与
User
绑定,所以我将使它们成为
User
扩展方法(但请注意,它们将返回表达式):

为了可重用于具有
引用的实体,您需要一个小型辅助工具实用程序,用于从其他lambda表达式合成lambda表达式,类似于

可以使用
表达式来实现它。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;
    }
}
这两个组成函数如下所示(我不喜欢名称
Compose
,因此有时我使用名称
Map
,有时
Select
Bind
Transform
等,但功能上它们是一样的。在这种情况下,我使用
Apply
ApplyTo
,唯一的区别是转换方向):

并将以下内容添加到实体存储库(在本例中为
Record
repository):

这应该足以满足您的要求


您可以进一步定义这样的接口

public interface IHasItem
{
    Item Item { get; set; }
}
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>());
}
让实体来实现它

public class Record : IHasItem // <--
{
    // Same as in the example - IHasItem.Item is auto implemented
    // ... 
}

如果需要的话,你可以用这样的东西

var items = this.GetAll().Where(this.User.OwnsItem().Or(this.User.CanAccessItem()));
项目
存储库中,或

var records = this.GetAll().Where(this.User.OwnsItem<Record>().Or(this.User.CanAccessItem<Record>()));
var records=this.GetAll().Where(this.User.OwnsItem()或(this.User.CanAccessItem());

记录库中。

您应该能够使用.AsQueryable()执行此操作

类帐户
{
公共IEnumerable用户{get;set;}
公共用户单用户{get;set;}
静态void查询()
{
IQueryable accounts=新帐户[0]。AsQueryable();
表达式userExpression=x=>x。已选定;
表达式accountAndUsersExpression=
x=>x.Users.AsQueryable().Where(userExpression.Any();
var resultWithUsers=accounts.Where(accountandusersPression);
表达式accountAndSingleUserExpression=
x=>new[]{x.SingleUser}.AsQueryable().Where(userExpression.Any();
var resultWithSingleUser=accounts.Where(accountAndSingleUserExpression);
}
}
类用户
{
已选择公共布尔值{get;set;}
}

有趣的是,我当时正试图想出一个
var items = this.GetAll().Where(this.User.OwnsItem());
var items = this.GetAll().Where(this.User.CanAccessItem());
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;
    }
}
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);
}
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);
}
static Expression<Func<Record, Item>> RecordItem => entity => entity.Item;
var records = this.GetAll().Where(this.User.Owns(RecordItem));
var records = this.GetAll().Where(this.User.CanAccess(RecordItem));
public interface IHasItem
{
    Item Item { get; set; }
}
public class Record : IHasItem // <--
{
    // Same as in the example - IHasItem.Item is auto implemented
    // ... 
}
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>());
}
var records = this.GetAll().Where(this.User.OwnsItem<Record>());
var records = this.GetAll().Where(this.User.CanAccessItem<Record>());
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);
}
var items = this.GetAll().Where(this.User.OwnsItem().Or(this.User.CanAccessItem()));
var records = this.GetAll().Where(this.User.OwnsItem<Record>().Or(this.User.CanAccessItem<Record>()));
class Account
{
    public IEnumerable<User> Users { get; set; }
    public User SingleUser { get; set; }


    static void Query()
    {
        IQueryable<Account> accounts = new Account[0].AsQueryable();

        Expression<Func<User, bool>> userExpression = x => x.Selected;

        Expression<Func<Account, bool>> accountAndUsersExpression =
            x => x.Users.AsQueryable().Where(userExpression).Any();
        var resultWithUsers = accounts.Where(accountAndUsersExpression);

        Expression<Func<Account, bool>> accountAndSingleUserExpression =
            x => new[] { x.SingleUser }.AsQueryable().Where(userExpression).Any();
        var resultWithSingleUser = accounts.Where(accountAndSingleUserExpression);
    }
}

class User
{
    public bool Selected { get; set; }
}