【问题标题】:System.Linq Expressions evaluating true on a single entitySystem.Linq 表达式在单个实体上评估为真
【发布时间】:2011-05-04 22:08:18
【问题描述】:

我正在尝试删除一些用于安全权限的 lambda 表达式。是否可以采用 lamda 表达式并将其应用于单个实体以实现 true?

假设我有一个 Person 和一个 DocumentFolder

    Expression<Func<Person, bool>> CanSeePerson()
    {
        return c =>  !c.IsPrivate;
    }

还有一个用于文件夹

    Expression<Func<DocumentFolder, bool>> CanSeeFolder()
    {
        return c => !c.IsPrivate && c.Owner.CanSeePerson(); // <- ???
    }

我怎么能在单一类型上使用 CanSeePerson() 函数来返回 true 并维护一个可以在这样的 linq 查询中使用的表达式

Entities.DocumentFolder.Where(CanSeeFolder());

我知道如何在 iqueryable 上使用 where,但我看不到如何将表达式树应用于单个值。


这会引发错误:无法创建“Person”类型的常量值。此上下文仅支持原始类型(“例如 Int32、String 和 Guid”)。

Expression<Func<DocumentFolder, bool>> CanSeeFolder()
    {
        return c => !c.IsPrivate &&_entities.Persons.Where(x => x.Id == c.Owner.Id).Any(CanSeePerson());
}

差异似乎是基于将 IQueryable Directly 放在语句中。这也不起作用

Expression<Func<DocumentFolder, bool>> CanSeeFolder()
    {
        return c => !c.IsPrivate &&_entities.Persons.Where(CanSeePerson()).Contains(c.User);
    }

但这确实有效

Expression<Func<DocumentFolder, bool>> CanSeeFolder()
        {
            var canSeePersons = _entities.Persons.Where(CanSeePerson());
            return c => !c.IsPrivate && canSeePersons.Contains(c.User);
        }

附言我知道我很烂@使用这个stackoverflow格式化的东西哈哈


之所以可行,是因为 CanSeePerson() 函数无法在表达式中转换和使用。当您将 canSeePersons 变量放入函数中时,您将放入可在表达式中使用的 iQueryable 类型。使用“var”有点复杂。

【问题讨论】:

  • 我可能遗漏了一些简单的东西

标签: c# linq


【解决方案1】:

您定义了一个 Expression&lt;Func&lt;Person, bool&gt;&gt; 来解决您的问题。诀窍是简单地将您的对象列表转换为您要过滤的列表。这并不总是可能的,但很多时候是可能的。你只需要有点创意:-)

使用扩展方法时,您可以轻松地想出一个解决方案,让您可以做到这一点:

var visibleFolders = Entities.DocumentFolder.WhereCanSeeFolder();

这是(完全干燥的)解决方案:

public static class SecurityExtensions
{
    public static IQueryable<DocumentFolder> WhereCanSeeFolder(
        this IQueryable<DocumentFolder> folders)
    {
        var visibleOwners = folders.Select(f => f.Owner)
            .Where(CanSeePerson);

        return
            from folder in folders.Where(CanSeeFolder)
            where visibleOwners.Contains(folder.Owner)
            select folder;
    }

    private static readonly Expression<Func<DocumentFolder, bool>>
        CanSeeFolder = folder => !folder.IsPrivate;

    private static readonly Expression<Func<Person, bool>>
        CanSeePerson = person => !person.IsPrivate;
}

我希望这会有所帮助。

【讨论】:

  • 我知道你打算用这个去哪里。我使用 Expression> 这样做的原因是我可以在另一段代码 (DRY) 上重用它,例如在 Picture.Owner 或 Notes.Owner 上。我不想为每个单独的班级重复相同的查询。我想在每个人上重用表达式
  • 我不想重复的原因是因为表达式会比这个复杂得多
  • @Kyle:我更新了我的答案。仔细查看我的代码以及我如何将DocumentFolder 对象列表转换为Person 对象的过滤列表并在where Contains 中使用它。我认为这有效地解决了您的问题,并使您的代码像沙漠一样干燥。
  • 这确实有效!谢谢,现在我必须弄清楚为什么这有效而另一个无效。非常感谢
  • 哇哈哈,我终于同时击败了著名的 Jon Skeet 和伟大的 Marc Gravell :-D
【解决方案2】:

表达式树在逻辑上一次只应用于一个值,在 LINQ 表达式中。

如果你说你想在进程中稍后将它应用到单个值,你可以使用:

// This can be cached
Func<DocumentFolder, bool> canSeeFolderDelegate = CanSeeFolder().Compile();

DocumentFolder folder = ...; // Get the value from somewhere
if (canSeeFolderDelegate(folder))
{
    // Yes, you can see that folder
}

【讨论】:

  • 如果你编译它就不再是一个表达式
  • @Kyle:当然,但你只是想在本地评估它,对吧?如果需要,您可以保留表达式并在需要时对其进行编译。您认为将其 only 保留为表达式有什么好处?鉴于您对 Marc 的回答很满意,听起来您有一些您没有告诉我们的要求。基本上,您需要为我们提供更多更多背景信息。
  • 我猜我丢了清晰度球,肯定不是第一次。我很抱歉。是的,它必须保留为表达式,以便我可以使用实体框架 4 将其发送到数据库
  • @Kyle:您最初是如何计划“将其发送到数据库”的?
  • 使用 ef4 和类似 Entities.DocumentFolder.Where(CanSeeFolder());
【解决方案3】:

啊,你想评估单个实体吗?

var func = CanSeeFolder().Compile(); // <=== store and re-use this;
                                     //      this isn't free
bool canSee = func(obj);

另一种方法可能是:

bool canSee = Enumerable.Repeat(obj, 1).AsQueryable().Any(CanSeeFolder());

但这可能仍然会在链中的某处执行Compile,因此您不妨使用更直接的代码(在顶部)。


编辑re cmets:

要评估在数据库中,您需要一个限制,例如:

bool canSee = db.Folders.Where(f => f.FolderId == id)
                .Where(CanSeeFolder()).Any();

这将我们限制为单行,然后添加您的额外过滤器。

【讨论】:

  • 无法编译它,因为那样你就无法将它作为表达式传输到数据库
  • @Kyle - 你没有说不得不在数据库中评估...我将添加(第二个)更新...
  • 我实际上尝试过类似的事情,但我得到了这样的错误
  • 无法创建“Person”类型的常量值。此上下文仅支持原始类型(“例如 Int32、String 和 Guid”)。
  • @Kyle - 如果没有看到您用来执行的代码,我无法对此发表评论。主键上的 Where 方法不使用 Expression.Constant 方法,所以应该没问题。
猜你喜欢
  • 2022-10-20
  • 2013-12-22
  • 2021-07-18
  • 1970-01-01
  • 2020-05-09
  • 2015-12-11
  • 2021-06-03
  • 1970-01-01
相关资源
最近更新 更多