两个IQueryables上的LINQ to Entities .Concat()抛出NullReferencesException

时间:2017-04-24 15:20:17

标签: c# entity-framework linq

我有以下抽象:

public interface IRepository<TEntity>
{
    IQueryable<TEntity> Entities { get; }
}

以下关闭实施:

public class CustomerRepository : IRepository<Customer>
{
    private readonly MyDbContext dbContext;

    public CustomerRepository(MyDbContext dbContext)
    {
        this.dbContext = dbContext;
    }

    public IQueryable<Customer> Entities => InternalCustomers.Concat(ExternalCustomers);

    private IQueryable<Customer> InternalCustomers =>
        from customer in dbContext.InternalCustomers
        select new Customer
        {
            Id = customer.Id,
            Name = customer.Name
            Company = new Company
            {
                Id = 1,
                Name = "Company",
            },
        };

    private IQueryable<Customer> ExternalCustomers =>
        from customer in dbContext.ExternalCustomers
        select new Customer
        {
            Id = customer.Id,
            Name = customer.Name
            Company = new Company
            {
                Id = customer.Company.Id,
                Name = customer.Company.Name,
            },
        };
}

我省略了冗余属性并简化了这个例子来解决问题。

班级Customer是自定义DTO(我将EntityFramework的实体 .InternalCustomers.ExternalCustomers 映射到我自己的自定义DTO,这可以简化为:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Company Company { get; set; }
}

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
}

我想注意select中的所有属性都按照正确的顺序设置。

执行.Entities查询时,我收到了NullReferenceException EntitiyFramework.dll,它来自 at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.VisitSetOp(SetOp op, Node n, AliasGenerator alias, Func`3 setOpExpressionBuilder) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.Visit(UnionAllOp op, Node n) at System.Data.Entity.Core.Query.InternalTrees.UnionAllOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n) at System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.Visit(FilterOp op, Node n) at System.Data.Entity.Core.Query.InternalTrees.FilterOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n) at System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.Visit(ConstrainedSortOp op, Node n) at System.Data.Entity.Core.Query.InternalTrees.ConstrainedSortOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n) at System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.BuildProjection(Node relOpNode, IEnumerable`1 projectionVars) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator.Visit(PhysicalProjectOp op, Node n) at System.Data.Entity.Core.Query.InternalTrees.PhysicalProjectOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n) at System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n) at System.Data.Entity.Core.Query.PlanCompiler.CTreeGenerator..ctor(Command itree, Node toConvert) at System.Data.Entity.Core.Query.PlanCompiler.ProviderCommandInfoUtils.Create(Command command, Node node) at System.Data.Entity.Core.Query.PlanCompiler.CodeGen.Process(List`1& childCommands, ColumnMap& resultColumnMap, Int32& columnCount) at System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Compile(List`1& providerCommands, ColumnMap& resultColumnMap, Int32& columnCount, Set`1& entitySets) at System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Compile(DbCommandTree ctree, List`1& providerCommands, ColumnMap& resultColumnMap, Int32& columnCount, Set`1& entitySets) at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition..ctor(DbProviderFactory storeProviderFactory, DbCommandTree commandTree, DbInterceptionContext interceptionContext, IDbDependencyResolver resolver, BridgeDataReaderFactory bridgeDataReaderFactory, ColumnMapFactory columnMapFactory) at System.Data.Entity.Core.EntityClient.Internal.EntityProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree, DbInterceptionContext interceptionContext) at System.Data.Entity.Core.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext) at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree) at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Boolean streaming, Span span, IEnumerable`1 compiledQueryParameters, AliasGenerator aliasGenerator) at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption) at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__6() at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess) at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__5() at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation) at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption) at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0() at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext() at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source) at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__2[TResult](IEnumerable`1 sequence) at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable`1 query, Expression queryRoot) at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute[TResult](Expression expression) at System.Data.Entity.Internal.Linq.DbQueryProvider.Execute[TResult](Expression expression) ,其中包含以下顶级堆栈跟踪:

Company = new Company
{
    Id = 0, //random number
    Name = "Hello",
},

如您所见,异常消息和堆栈跟踪对我没有帮助。特别是LINQ to Entities正在构建的“神奇”表达树,这是一个令人眼花缭乱的难题(至少对我而言)。

所以我对Repository实现进行了一些小调整,我得出了以下观察结果:

  1. 执行 InternalCustomers时,我从数据库中获取有效列表。
  2. 执行外部客户时,我也从数据库中获取有效列表。
  3. 当我使用.Concat()方法时,似乎发生了异常。
  4. 当我为ExternalContacts提供以下代码片段时,会发生异常:
  5. 代码段:

    rpt.SetDatabaseLogon(sConn.UserID, sConn.Password, sConn.DataSource + ",1433", sConn.InitialCatalog);
                foreach (ReportDocument subReport in rpt.Subreports)
                {
                    subReport.SetDatabaseLogon(sConn.UserID, sConn.Password, sConn.DataSource + ",1433", sConn.InitialCatalog);
                    for (int i = 0; i < subReport.DataSourceConnections.Count; i++)
                    {
                        subReport.DataSourceConnections[i].SetConnection(sConn.DataSource + ",1433", sConn.InitialCatalog, sConn.UserID, sConn.Password);
                        subReport.DataSourceConnections[i].IntegratedSecurity = false;
                    }
                }
                for (int i = 0; i < rpt.DataSourceConnections.Count; i++)
                {
                    rpt.DataSourceConnections[i].SetConnection(sConn.DataSource + ",1433", sConn.InitialCatalog, sConn.UserID, sConn.Password);
                    rpt.DataSourceConnections[i].IntegratedSecurity = false;
                }
    

    我得到的印象是,这可能是实体框架中的一个错误 6.1.3 ,但我不确定。我想知道这确实是一个错误,还是我做了一些愚蠢的事情,经过3个小时的调查后我无法弄明白。

1 个答案:

答案 0 :(得分:2)

这绝对是一个错误,因为你没有做错任何事情,而且异常是非常不友好的用户。

有一种解决方法,但需要额外的编码。诀窍是使用中间投影来“平坦”数据类(因为错误以某种方式与嵌套的Company投影相关),然后是Concat,最后在连接结果上应用所需的投影。所有这些都不会影响最终的SQL查询,它应该是简单的UNION ALL

以下是它的外观:

public class CustomerRepository : IRepository<Customer>
{
    private readonly MyDbContext dbContext;

    public CustomerRepository(MyDbContext dbContext)
    {
        this.dbContext = dbContext;
    }

    public IQueryable<Customer> Entities => InternalCustomersData.Concat(ExternalCustomersData).Select(CustomerSelector);

    private IQueryable<Customer> InternalCustomers => InternalCustomersData.Select(CustomerSelector);

    private IQueryable<Customer> ExternalCustomers => ExternalCustomersData.Select(CustomerSelector);

    private IQueryable<CustomerData> InternalCustomersData =>
        from customer in dbContext.InternalCustomers
        select new CustomerData
        {
            Id = customer.Id,
            Name = customer.Name,       
            CompanyId = 1,
            CompanyName = "Company",
        };

    private IQueryable<CustomerData> ExternalCustomersData =>
        from customer in dbContext.ExternalCustomers
        select new CustomerData
        {
            Id = customer.Id,
            Name = customer.Name,       
            CompanyId = customer.Company.Id,
            CompanyName = customer.Company.Name,
        };

    private static readonly Expression<Func<CustomerData, Customer>> CustomerSelector = data => new Customer
    {
        Id = data.Id,
        Name = data.Name,
        Company = new Company
        {
            Id = data.CompanyId,
            Name = data.CompanyName,
        }
    };

    private class CustomerData
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int CompanyId { get; set; }
        public string CompanyName { get; set; }
    }
}

烦人,但有效。