简体   繁体   English

投射 IQueryable<EntityObject> 到 IQueryable<Specific>

[英]Cast IQueryable<EntityObject> to IQueryable<Specific>

We are trying to cast an instance of IQueryable<EntityObject> to an IQueryable<SpecificEntityObject> , the SpecificEntityObject type is only known at runtime.我们正在尝试将IQueryable<EntityObject>的实例转换为IQueryable<SpecificEntityObject>SpecificEntityObject类型仅在运行时已知。

We have tried using the code below, which does not compile because The type or namespace 'objType' does not exist.我们已尝试使用下面的代码,该代码无法编译,因为类型或命名空间 'objType' 不存在。

var t = query.ElementType;
Type objType = typeof(IQueryable<>).MakeGenericType(t);
var typed = query.Cast<IEnumerable<objType>>();


var grouped = typed.GroupByMany(groupBy.Select(grp => grp.Expression).ToArray());

Any ideas?有任何想法吗?

Use following IQueryable extension generic method query.ToDTO<sourceType,DestType>(); 使用以下IQueryable扩展通用方法query.ToDTO<sourceType,DestType>(); :

public static class QueryableExtensions
{
    public static IQueryable<TDest> ToDTO<TSource, TDest>(this IQueryable<TSource> source)
    {
        List<TDest> destinationList = new List<TDest>();
        List<TSource> sourceList = source.ToList<TSource>();

        var sourceType = typeof(TSource);
        var destType = typeof(TDest);
        foreach (TSource sourceElement in sourceList)
        {
            TDest destElement = Activator.CreateInstance<TDest>();
            //Get all properties from the object 
            PropertyInfo[] sourceProperties = typeof(TSource).GetProperties();
            foreach (PropertyInfo sourceProperty in sourceProperties)
            {
                //and assign value to each propery according to property name.
                PropertyInfo destProperty = destType.GetProperty(sourceProperty.Name);
                destProperty.SetValue(destElement, sourceProperty.GetValue(sourceElement, null), null);
            }
            destinationList.Add(destElement);
        }

        return destinationList.AsQueryable();
    }
}

For anyone else wanting to to project non-db values from a db query, this project from u/Luis Aguilar was very, very helpful to me. 对于其他想要从db查询中投射非db值的人来说,来自u / Luis Aguilar的 这个项目对我非常非常有帮助。

I had a very large legacy database (450GB) which was required to be served to OData/WebAPI. 我有一个非常大的遗留数据库(450GB),需要提供给OData / WebAPI。

The OData requirement meant I could not filter the source data (much) before returning it to the user. OData要求意味着在将数据返回给用户之前我无法过滤源数据。 We could silo it, but apart from that it is their data to query as they wish. 我们可以把它弄清楚,但除此之外,他们的数据可以按照自己的意愿进行查询。

More importantly, however, the legacy data was far too convoluted to expose as-is, and there was significant business logic required to collate the necessary data ( Include of navigation properties/foreign keys, lengthy clause predicates, etc). 但更重要的是,遗留数据过于复杂,无法按原样公开,并且需要大量业务逻辑来整理必要的数据( Include导航属性/外键,冗长子句谓词等)。

This meant the pagination and result limiting would not be available until after the query was already materialized. 这意味着在查询已经实现之后才能使用分页和结果限制。

Normal shortcuts for this kind of thing involve various strategies that involve materialization/eager loading. 这种事情的正常捷径涉及涉及物化/急切加载的各种策略。 However, due to the size of the dataset and lack of filtering, this would result in massive process memory bloat and out-of-memory crashes. 但是,由于数据集的大小和缺少过滤,这将导致大量进程内存膨胀和内存不足崩溃。

So, some code. 所以,一些代码。 Here's my config call, similar to what AutoMapper or OData require: 这是我的配置调用,类似于AutoMapper或OData所需:

using ExpressionFramework.Projections;
using ExpressionFramework.Projections.Configuration;

public class ProjectionModelBuilder : ProjectionModel
{
    protected override void OnModelCreating(ProjectionModelBuilder modelBuilder)
    {
        ClientDTO.ProjectionModel(modelBuilder);
        OrderDTO.ProjectionModel(modelBuilder);
        AnotherDTO.ProjectionModel(modelBuilder);
    }
}

This design allows me to keep the projection rules in the DTO class with the rest of the business logic. 这种设计允许我将DTO类中的投影规则与其余的业务逻辑保持在一起。 Here's what the DTO-level code looks like: 这是DTO级代码的样子:

public static void ProjectionModel(ProjectionModelBuilder modelBuilder)
{
    modelBuilder
        .Projection<ClientDTO>()
        .ForSource<Client>(configuration =>
        {
            configuration.Property(dto => dto.Name).ExtractFrom(entity => entity.Name);
            // etc
        });
}

Where Client is my Entity/EDM type, mapped to db table and a gazillion foreign keys. Client是我的实体/ EDM类型,映射到db表和大量外键。

To then get a translated/projected Queryable , this is it: 然后得到一个已翻译/预计的Queryable ,这就是它:

IClientQueryService service = _ioc.Resolve<IClientQueryService>(); // Repository pattern 
var q = service.GetClients(); // withManyNavigationIncludes
var r = q.Where<Item>(
    i =>
        i.Name != null
        && i.Name != ""
        // lather rinse repeat, with many sub-objects navigated also
    ).AsQueryable();
var projectionModel = new ProjectionModelBuilder();
var s = projectionModel.Project<ClientDTO, Client>(r).AsQueryable();

Only the last two lines are relevant, but included the rest for context. 只有最后两行是相关的,但其余部分包含在上下文中。

The last thing I had to do was set this.IsAutoConfigured = false; 我要做的最后一件事是设置this.IsAutoConfigured = false; in the constructor for ProjectionSourceTypeConfiguration.cs in Luis' code; 在Luis代码中的ProjectionSourceTypeConfiguration.cs的构造函数中; this allowed me to order my projection definitions manually so navigation properties inside parent classes would configure their projections successfully. 这允许我手动订购我的投影定义,因此父类中的导航属性将成功配置它们的投影。

I can't thank https://stackoverflow.com/users/543712/luis-aguilar enough for his work. 我不能不感谢https://stackoverflow.com/users/543712/luis-aguilar他的工作。 After writing my own LINQ Provider/ ExpressionVisitor with various generic method invocations, translations and treewalks to still have various problems, his project was a godsend. 在编写了我自己的LINQ Provider / ExpressionVisitor其中包含各种泛型方法调用,翻译和treewalks仍有各种问题,他的项目是天赐之物。

If you do find to have to pipeline your own expression processing for performance or other reasons, I'd recommend these two answers to begin with. 如果你确实发现必须为了性能或其他原因管理你自己的表达式处理,我建议开始 两个答案。

var t = query.ElementType;
Type objType = typeof(IQueryable<>).MakeGenericType(t);
var typed = query.Cast<object>();


var grouped = typed.GroupByMany(groupBy.Select(grp => grp.Expression).ToArray());

If you don't have compile time type info, you have to rely on ugly reflection code all the way down.如果您没有编译时类型信息,则必须一直依赖丑陋的反射代码。 dynamic keyword might make things a bit tidy. dynamic关键字可能会使事情变得有点整洁。 Try something like:尝试类似:

var typed = (IQueryable)typeof(Queryable)
    .GetMethod(nameof(Queryable.Cast))
    .MakeGenericMethod(typeof(SpecificEntityObject)) // <--- your runtime type here
    .Invoke(null, new object[] { query });

// more reflection based calls to follow for further LINQ operations.

There is also nice little extension method:还有一个不错的小扩展方法:

public static IQueryable Cast(this IQueryable source, Type type)

in System.Linq.Dynamic.Core library.System.Linq.Dynamic.Core库中。

If you started using reflection, you need to use it with all methods too. 如果您开始使用反射,则需要将其与所有方法一起使用。 So you need to create 所以你需要创造

var myEnumType = typeof(IEnumerable<>).MakeGenericType(objType);

and also find extention method Cast matching needed type also at runtime. 并且还可以在运行时找到扩展方法Cast匹配所需的类型。

 myEnumType.GetMethod("Cast", BindingFlags.Public |
                BindingFlags.Static, 
                null, 
                CallingConventions.Any,  
                new Type[] {typeof(object)}, 
                null);

then you would be able to call that method 然后你就可以调用那个方法了

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM