简体   繁体   English

使用EF的LINQ查询中的奇怪空引用异常

[英]Strange null reference exception in LINQ query with EF

I have the following three related entity classes: 我有以下三个相关的实体类:

public class ContextInstance
{
    public Int64 Id { get; set; }

    public virtual List<ContextParamValue> ContextParamValues { get; set; }
}

public class ContextParamValue
{
    public Int64 Id { get; set; }

    public virtual Int64 ContextParamId { get; set; }

    public virtual ContextParam ContextParam { get; set; }

    public virtual ContextInstance ContextInstance { get; set; }

    public virtual Int64 ContextInstanceId { get; set; }

    public string Value { get; set; }
}

public class ContextParam
{
    public Int64 Id { get; set; }

    [Required]
    public string Name { get; set; }

    [DefaultValue("")]
    public string Description { get; set; }
}

I have set up fluent relationships as follows: 我建立了流畅的关系如下:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

modelBuilder.Entity<ContextInstance>()
                .HasMany(ci => ci.ContextParamValues)
                .WithRequired(cpv => cpv.ContextInstance)
                .HasForeignKey(cpv => cpv.ContextInstanceId)
                .WillCascadeOnDelete(true);

I have the following "Helper" class, the ParamValueToList method of which is intermittently throwing a null-reference exception : 我有以下“Helper”类,其ParamValueToList方法间歇性地抛出空引用异常

public class RuntimeHelper : IDisposable
    {
        DocumentDbContext db;

        ConfigurationHelper ch;

        private RuntimeHelper()
        {
        }

        public RuntimeHelper(DocumentDbContext context)
        {
            db = context;
            ch = new ConfigurationHelper(context);
        }

        public List<ContextParamValue> ParamValuesToList(string[] ParamNames, string[] ParamValues)
        {
            Trace.TraceInformation("-- ParamValuesToList invoked --");

            if (ParamNames != null && ParamNames.Length != ParamValues.Length)
                throw new System.ArgumentException("ParamNames and ParamValues may not differ in length.");

            Dictionary<string, string> d = new Dictionary<string, string>();

            for (int i = 0; i < ParamNames.Length; i++)
            {
                string pName = ParamNames[i];
                string pValue = ParamValues[i];
                d.Add(pName, pValue);
                Trace.TraceInformation("ParamValuesToList Key: " + pName + "; Value: " + pValue + ";");
            }

            Trace.TraceInformation("Value of db:" + db.ContextParamValues.ToString());

            var cpvList = db.ContextParamValues
                 .Include(x => x.ContextParam)
                 .ToArray<ContextParamValue>();

            List<ContextParamValue> lst = cpvList
                 .Where(pv => d.Contains(new KeyValuePair<string, string>(pv.ContextParam.Name, pv.Value)))
                 //.Where(pv => true == true)
                 .ToList<ContextParamValue>();

            Trace.TraceInformation("-- ParamValuesToList executed --");

            return lst;
        }

        public List<ContextInstance> GetContextInstances(List<ContextParamValue> ContextParamValues, bool AsNoTracking = false)
        {
            if (!AsNoTracking)
                return db.ContextInstances
                    .Include(x => x.ContextClass)
                    .Include(x => x.ContextParamValues.Select(p => p.ContextParam))
                    .Include(x => x.Documents)
                    .AsEnumerable<ContextInstance>() // <-- Allows boolean method as part of LINQ query
                    .Where(ci => IsSubset(ci.ContextParamValues, ContextParamValues))
                    .ToList<ContextInstance>();
            else
                return db.ContextInstances
                    .Include(x => x.ContextClass)
                    .Include(x => x.ContextParamValues.Select(p => p.ContextParam))
                    .Include(x => x.Documents)
                    .AsNoTracking()
                    .AsEnumerable<ContextInstance>()// <-- Allows boolean method as part of LINQ query
                    .Where(ci => IsSubset(ci.ContextParamValues, ContextParamValues))
                    .ToList<ContextInstance>();
        }

        public List<ContextInstance> GetContextInstances(string[] ParamNames, string[] ParamValues, bool AsNoTracking = false)
        {

            return GetContextInstances(ParamValuesToList(ParamNames, ParamValues), AsNoTracking);
        }
}

The specific statement from the above method that is throwing the error is 上面抛出错误的方法的具体说法是

List<ContextParamValue> lst = cpvList
                 .Where(pv => d.Contains(new KeyValuePair<string, string>(pv.ContextParam.Name, pv.Value)))
                 .ToList<ContextParamValue>();

The null reference exception is NOT thrown under the following condition: 在以下条件下引发空引用异常:

  • There exist, for a given ContextInstance, only 1 ContextParamValue 对于给定的ContextInstance,只存在1个ContextParamValue
  • Example, ContextParamValue.ContextParam.Name = "ClientId" and ContextParamValue1.Value = "1" 示例,ContextParamValue.ContextParam.Name =“ClientId”和ContextParamValue1.Value =“1”

The null reference exception is thrown under the following condition: 空引用异常以下条件下抛出:

  • There exist, for a given ContextInstance, two-or-more ContextParamValues 对于给定的ContextInstance,存在两个或更多个ContextParamValues
  • Example, ContextParamValue1.ContextParam.Name = "ClientId" and ContextParamValue1.Value = "1" PLUS ContextParamValue2.ContextParam.Name = "MotivationId" and ContextParamValue2.Value = "1". 示例,ContextParamValue1.ContextParam.Name =“ClientId”和ContextParamValue1.Value =“1”PLUS ContextParamValue2.ContextParam.Name =“MotivationId”和ContextParamValue2.Value =“1”。

I can confirm the following about the helper method in question: 我可以确认以下关于有问题的帮助方法:

  • d is not null nor does it contain any keyvaluepairs with null values d不为null,也不包含任何具有空值的keyvaluepairs
  • cpvList is not null and not empty when the error occurs. 发生错误时,cpvList不为null且不为空。
  • ContextParam does not load for parent ContextParamValue entities in all cases (it only loads for the first ContextParamValue instance but for subsequent instances only a null value is loaded). 在所有情况下,ContextParam都不为父ContextParamValue实体加载(它仅为第一个ContextParamValue实例加载,但对于后续实例仅加载空值)。
  • There are no null ContextParam entries in database... All ContextParamValues has one ContextParam entry. 数据库中没有空的ContextParam条目...所有ContextParamValues都有一个ContextParam条目。

The following trace and stacktrace information is generated during runtime: 运行时期间会生成以下跟踪堆栈 跟踪信息:

Application: 2014-05-16T19:00:20 PID[4800] Error System.NullReferenceException: Object reference not set to an instance of an object. 应用程序:2014-05-16T19:00:20 PID [4800]错误System.NullReferenceException:对象引用未设置为对象的实例。 Application: at DocumentManagement.Helpers.RuntimeHelper.<>c__DisplayClass28.b__27(ContextParamValue pv) in c:\\Users\\xxx\\Dropbox\\xxx\\Active Projects\\xxx\\DocumentManagement\\Helpers\\DocsHelper_RT.cs:line 229 Application: at System.Linq.Enumerable.WhereArrayIterator1.MoveNext() Application: at System.Collections.Generic.List1..ctor(IEnumerable1 collection) Application: at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source) Application: at DocumentManagement.Helpers.RuntimeHelper.ParamValuesToList(String[] ParamNames, String[] ParamValues) in c:\\Users\\xxx\\Dropbox\\xxx\\Active Projects\\xxx\\DocumentManagement\\Helpers\\DocsHelper_RT.cs:line 228 Application: at DocumentManagement.Helpers.RuntimeHelper.GetContextInstances(String[] ParamNames, String[] ParamValues, Boolean AsNoTracking) in c:\\Users\\xxx\\Dropbox\\xxx\\Active Projects\\xxx\\DocumentManagement\\Helpers\\DocsHelper_RT.cs:line 262 Application: at xxx.Controllers.ClientController.LoadStep2(Int64 ClientId, String Error) in c:\\Users\\xxx\\D 应用程序:在DocumentManagement.Helpers.RuntimeHelper。<> c__DisplayClass28.b__27(ContextParamValue pv)中的c:\\ Users \\ xxx \\ Dropbox \\ xxx \\ Active Projects \\ xxx \\ DocumentManagement \\ Helpers \\ DocsHelper_RT.cs:第229行应用程序:at System。 Linq.Enumerable.WhereArrayIterator1.MoveNext()Application:at System.Collections.Generic.List1..ctor(IEnumerable1 collection)Application:at System.Linq.Enumerable.ToList [TSource](IEnumerable1 source)Application:at DocumentManagement.Helpers。 RuntimeHelper.ParamValuesToList(String [] ParamNames,String [] ParamValues)在c:\\ Users \\ xxx \\ Dropbox \\ xxx \\ Active Projects \\ xxx \\ DocumentManagement \\ Helpers \\ DocsHelper_RT.cs:第228行应用程序:在DocumentManagement.Helpers.RuntimeHelper。 GetContextInstances(String [] ParamNames,String [] ParamValues,Boolean AsNoTracking)在c:\\ Users \\ xxx \\ Dropbox \\ xxx \\ Active Projects \\ xxx \\ DocumentManagement \\ Helpers \\ DocsHelper_RT.cs:第262行应用程序:at xxx.Controllers.ClientController c:\\ Users \\ xxx \\ D中的.LoadStep2(Int64 ClientId,String Error) ropbox\\xxx\\Active Projects\\xxx\\xxx\\Views\\Client\\ClientController.cs:line 198 ropbox \\ xxx \\ Active Projects \\ xxx \\ xxx \\ Views \\ Client \\ ClientController.cs:第198行

在此输入图像描述

在此输入图像描述

The only way that your code would throw an exception would be if pv.ContextParam were null, because that is the only place you are dereferencing something that might cause a null pointer exception. 如果pv.ContextParam为null,则代码抛出异常的唯一方法是,因为这是您解除引用可能导致空指针异常的内容的唯一位置。

This would happen if you have ContextParamValues records without a corresponding ContextParam record, thus ContextParam would be null. 如果您具有ContextParamValues记录而没有相应的ContextParam记录,则会发生这种情况,因此ContextParam将为null。 Since we can't see your data model, you will have to check for that. 由于我们无法看到您的数据模型,因此您必须检查它。

Add this line of code and check in the debugger to see if it's true: 添加这行代码并检查调试器以查看它是否为真:

bool containsNulls = db.ContextParamValues
    .Include(x => x.ContextParam)
    .Any(x => x.ContextParam == null)

EDIT (removed all the middle steps, check history if you're interested): 编辑(删除所有中间步骤,如果您感兴趣,请查看历史记录):

Well, this doesn't actually answer the question, but it would solve your problem. 嗯,这实际上并没有回答这个问题,但它可以解决你的问题。 Let's just rewrite your code to be simpler and more efficient. 让我们重写您的代码,使其更简单,更高效。 If I read your code right, all you're looking to do is return the ContextInstances that have associated ContextValueParams with the provided name/value pairs, correct? 如果我正确地阅读了您的代码,那么您要做的就是返回与所提供的名称/值对相关联的ContextInstances,正确吗?

Why not just do this (add includes as you see fit): 为什么不这样做(根据需要添加包括):

public List<ContextInstance> GetContextInstances(
       string[] ParamNames, string[] ParamValues, bool AsNoTracking = false)
{
    var p = ParamNames.Zip(ParamValues, (a,b) => a+b);

    var ctx = db.ContextInstances
       .Where(x => p.All(y => x.ContextParamValues
          .Select(z => z.ContextParam.Name + z.Value).Contains(y)));

    return (AsNoTracking ? ctx.AsNoTracking() : ctx).ToList();
}

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

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