IQueryable与 EntityObject 一起使用泛型 &接口(可能吗?)

发布于 2025-01-02 18:10:09 字数 3619 浏览 1 评论 0原文

我有一个使用 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 返回值以使用布尔 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”由于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( 返回它),我似乎无能为力。 )有什么方法可以将我所缺少的这些放在一起吗?

I have a search repository for EntityFramework 4.0 using LinqKit with the following search function:

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

And another class which uses the IQueryable return value to subset the query in ways that are not possible using the Boolean LinqKit PredicateBuilder expressions:

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);
}

The problem here is that 'T' as EntityObject does not define GUID, so I can't use this. The natural response to this is to actually define the SubsetByUser() method to use a constraint with a GUID property:

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);
}

But this doesn't work. I'm using LinqKit and the Expandable() method results in:

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

I need an IQueryable to be returned. I can do a fake like this:

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();
}

Which, of course, works, but which is also, of course, a bat-shit crazy thing to do. (The whole reason I want IQueryable is to not execute the query until we're final.

I've even tried this:

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);
}

Which uses reflection to get the Runs collection– working around the compiler error. I thought that was rather clever, but it results in the LINQ exception:

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.

I could also try to change the search method:

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

But this won't compile of course because IRunElement is not an EntityObject, and ObjectSet constrains T as a class.

The final possibility is simply making all the parameters and return values 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);
}

Which also works, but which, again, doesn't allow us to delay instantiation until the end.

So, it seems like there's little I can do to make this work without instantiating everything as an IEnumerable and then returning it using AsQueryable(). Is there some way I can put this together that I'm missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

南风起 2025-01-09 18:10:09

对此的自然反应是实际定义 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.* 函数手动创建表达式树,这样您就可以避免首先包含强制转换。

The natural response to this is to actually define the SubsetByUser() method to use a constraint with a GUID property:

...

But this doesn't work. I'm using LinqKit and the Expandable() method results in:

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

You're very close with that. You can make this work if you use an ExpressionVisitor that removes all unnecessary casts (casts to a base type or an implemented interface) that are automatically generated.

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);
    }
}

Alternatively, create the expression tree manually using the Expression.* functions, so that you can avoid including the cast in the first place.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文