【问题标题】:IQueryable<T> with EntityObject using Generics & Interfaces (Possible?)IQueryable<T> 与 EntityObject 使用泛型和接口(可能吗?)
【发布时间】:2012-02-25 14:17:12
【问题描述】:

我有一个使用具有以下搜索功能的 LinqKit 的 EntityFramework 4.0 搜索存储库:

public IQueryable<T> Search<T>(Expression<Func<T, bool>> predicate) 
    where T : EntityObject
{
    return _unitOfWork.ObjectSet<T>().AsExpandable().Where(predicate);
}

另一个类使用 IQueryable 返回值以使用 Boolean LinqKit PredicateBuilder 表达式无法实现的方式对查询进行子集化:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : EntityObject
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GUID,
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

这里的问题是 'T' as EntityObject 没有定义 GUID,所以我不能使用它。对此的自然反应是实际定义 SubsetByUser() 方法以使用具有 GUID 属性的约束:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : IHaveMetadata
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GUID,
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

但这不起作用。我正在使用 LinqKit,Expandable() 方法的结果是:

System.NotSupportedException: Unable to cast the type 'Oasis.DataModel.Arc' to
type 'Oasis.DataModel.Interfaces.IHaveMetadata'. LINQ to Entities only supports 
casting Entity Data Model primitive types

我需要返回一个 IQueryable。我可以像这样做一个假的:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : EntityObject
{
    return set.AsEnumerable()
              .Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GUID,
                    meta => meta.ElementGUID,
                    (arc, meta) => arc)
              .AsQueryable();
}

当然,这很有效,但当然,这也是一件非常疯狂的事情。 (我想要 IQueryable 的全部原因是在我们完成之前不执行查询。

我什至试过这个:

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : EntityObject
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GetType().GetProperty("GUID").GetValue(arc,null),
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

它使用反射来获取 Runs 集合——解决编译器错误。我认为这很聪明,但它会导致 LINQ 异常:

System.NotSupportedException: LINQ to Entities does not recognize the 
method 'System.Object GetValue(System.Object, System.Object[])' method, 
and this method cannot be translated into a store expression.

我也可以尝试改变搜索方式:

public IQueryable<T> Search<T>(Expression<Func<T, bool>> predicate) 
    where T : IRunElement
{
    return _unitOfWork.ObjectSet<T>().AsExpandable().Where(predicate);
}

但这当然不会编译,因为 IRunElement 不是 EntityObject,而 ObjectSet 将 T 约束为一个类。

最后的可能性是简单地将所有参数和返回值设为 IEnumerable:

public IEnumerable<T> SubsetByUser<T>(IEnumerable<T> set, User user) 
    where T : EntityObject
{
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
                    arc => arc.GetType().GetProperty("GUID").GetValue(arc,null),
                    meta => meta.ElementGUID,
                    (arc, meta) => arc);
}

这也有效,但同样不允许我们将实例化延迟到最后。

所以,如果不将所有内容实例化为 IEnumerable,然后使用 AsQueryable() 返回它,我似乎无能为力。有什么方法可以把我错过的这些放在一起吗?

【问题讨论】:

  • 是否可以显示更多关于您如何使用此 SubsetByUser 函数的代码(您将从查询中调用它)。尝试做类似的事情。
  • @AaronLS,我不再参与这个项目,但基本上——IIRC,set 参数是另一个查询的返回集。所以它只是 _subsetContainer.SubsetByUser(_container.Search(predicate), someUser);

标签: linq entity-framework linq-to-entities iqueryable linqkit


【解决方案1】:
对此的自然反应是实际定义 SubsetByUser() 方法以使用具有 GUID 属性的约束: ... 但这不起作用。我正在使用 LinqKit 和 Expandable() 方法导致: System.NotSupportedException:无法将类型“Oasis.DataModel.Arc”转换为 输入“Oasis.DataModel.Interfaces.IHaveMetadata”。 LINQ to Entities 仅支持 转换实体数据模型基元类型

您对此非常接近。如果您使用 ExpressionVisitor 删除自动生成的所有不必要的转换(转换为基本类型或实现的接口),您可以完成这项工作。

public IQueryable<T> SubsetByUser<T>(IQueryable<T> set, User user) 
    where T : IHaveMetadata
{
    Expression<Func<T, Guid>> GetGUID = arc => arc.GUID;
    GetGUID = (Expression<Func<T, Guid>>)RemoveUnnecessaryConversions.Instance.Visit(GetGUID);
    return set.Join(_searcher.Search<Metadatum>((o) => o.UserGUID == user.GUID),
        GetGUID,
        meta => meta.ElementGUID,
        (arc, meta) => arc);
}

public class RemoveUnnecessaryConversions : ExpressionVisitor
{
    public static readonly RemoveUnnecessaryConversions Instance = new RemoveUnnecessaryConversions();

    protected RemoveUnnecessaryConversions() { }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Convert
            && node.Type.IsAssignableFrom(node.Operand.Type))
        {
            return base.Visit(node.Operand);
        }
        return base.VisitUnary(node);
    }
}

或者,使用Expression.* 函数手动创建表达式树,这样您就可以避免首先包含强制转换。

【讨论】:

  • 这是完美的。正是我想要的。非常感谢!
  • @hvd 我想我正在尝试做类似的事情。您能否举例说明如何在查询中调用 SubsetByUser?我很困惑,因为它不是扩展方法。我正在尝试创建一种可供 linq2entities 使用的方法:stackoverflow.com/questions/10826275/…
  • @AaronLS 你会使用from x in SubsetByUser(...) ... select x——这不是你想要做的。这是一个返回查询的函数。您正在尝试从查询中调用函数。 (也就是说,我后来发现我的答案是不必要的复杂,并且会在可能的情况下更新。)
  • @hvd SubsetByUser 在哪里声明?在静态类中?方法不是静态的?我只知道如何使用 linqkit 来声明一个名为 asOfCurrent 的局部表达式变量,这样我就可以做到.Where(asOfCurrent)。这是朝着正确方向迈出的一步。所以我想让它更全局可用,并且还能够接受一个参数(一个 DateTime),类似于上面如何接受一个用户参数。
  • @AaronLS 它可能是一个静态函数,除了这个函数使用的_searcher(据我所知)特定于这个问题。我猜这是上下文中的实例方法。在您的情况下,它很可能是您想要的任何类(静态或非静态)中的静态方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-03
  • 2012-02-07
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
  • 1970-01-01
相关资源
最近更新 更多