2017-04-24 69 views
1

我有以下抽象:LINQ到实体.Concat()在两个IQueryables抛出NullReferencesException

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

我想note表示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) 

正如你所看到的,异常消息和堆栈跟踪不帮我。特别是LINQ to Entities构建的'魔术'表达树,这是一个令人眼花缭乱的难题(至少对我来说)要进行调查。

所以我做了一些小的调整,资源库实现和我来到了下列意见:

  1. 当执行只有 InternalCustomers,我从数据库中获取一个有效的列表。
  2. 当执行时只有 ExternalCustomers,我也从数据库中得到一个有效的列表。
  3. 当我使用.Concat()方法时,异常似乎正在发生。
  4. 的例外情况当我有ExternalContacts下面的代码片段会发生:

代码片段:

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

我越来越强烈的印象,这可能是一个错误实体框架6.1.3,但我不确定。我想知道这是否确实是一个错误,或者我正在做一些愚蠢的事情,我经过3个小时的调查后才弄清楚。

+0

@DanielLorenz除非表中有重复需要删除。我怀疑根本没有重复的东西,所以没有必要为了检查它们而去检查它们。 – Servy

+1

@DanielLorenz不,它不会。该代码永远不会运行,只用于构建转换为SQL的表达式,而SQL在解引用时传播空值,而不是抛出。 – Servy

+0

@Servy在两个问题上都说了什么。我已经尝试过'.Union()',它的行为方式与'.Concat()'相同(如同引发同样的异常),但这里的想法应该简单地串联起来。 – QuantumHive

回答

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

烦人,但工程。