LINQ to Entities .Concat() 在两个 IQueryables 上抛出 NullReference



我有以下抽象:

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)

如您所见,异常消息和堆栈跟踪对我没有帮助。特别是对于 LINQ to Entities 正在构建的"魔术"表达式树,这是一个令人眼花缭乱的难题(至少对我来说)。

所以我对存储库实现做了一些小的调整,我得出了以下观察结果:

  1. 执行内部客户时,我从数据库中获得一个有效的列表。
  2. 执行外部客户时,我还从数据库中获取有效列表。
  3. 当我使用 .Concat() 方法。
  4. 当我有以下外部联系人代码片段时,不会发生异常:

代码片段:

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

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

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

有一个解决方法,但它需要额外的编码。诀窍是使用中间投影来"平面"数据类(因为错误与嵌套的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; }
}
}

烦人,但有效。

最新更新