2
votes

Very strange behaviour from EF which has me pulling my hair out.

The problem pertains mainly to the following entity class, and it's encapsulated properties:

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

    public Int64 ContextParamId { get; set; }

    public virtual ContextParam ContextParam { get; set; }

    public virtual ContextInstance ContextInstance { get; set; }

    public Int64 ContextInstanceId { get; set; }

    public string Value { get; set; }
}

As you can see, I have a ContextParamValue class, which has a unidirectional 1 to 1 relationship with ContextParam. Thus, ContextParamValue can access ContextParam but not the other way around.

The piece of code that has me in tatters are as follows:

public List<ContextParamValue> ParamValuesToList(string[] ParamNames, string[] ParamValues)
{
    if (ParamNames != null && ParamNames.Length != ParamValues.Length)
        throw new System.ArgumentException("ParamNames and ParamValues may not differ in length.");

    List<ContextParamValue> rList = new List<ContextParamValue>();

    for (int i = 0; i < ParamNames.Length; i++)
    {
        string pName = ParamNames[i];
        string pValue = ParamValues[i];

        List<ContextParamValue> lst = db.ContextParamValues
            //.Include(x => x.ContextParam)
            .Where(pv => pv.ContextParam.Name.ToLower().Trim().Equals(pName.ToLower().Trim()))
            .Where(pv => pv.Value.Equals(pValue))
            .ToList<ContextParamValue>();

        rList.AddRange(lst);
    }

    return rList;
}

The strange result of this code is that ContextParam is only loaded for the first element returned in rList. All the subsequent elements in rList has a null value for the ContextParam property. The following screenshots shows the element instance values during debugging:

First element in the list is A-OKFirst element in the collection... WINNING!

Subsequent elements... HUGE FAILSecond element in the collection... MASSIVE FAIL!

I have tried multiple alternative implementations for the above method, namely lazy loading, eager loading, even not building up the list from within a loop (I constructed a dictionary of the ParamNames and ParamValues array objects which allowed me to do set-based matching within the LINQ expression). Same result every time.

I also include the relevant snippets from my DbContext class:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    ModelMapper.InitializeRelationshipMappings(modelBuilder);
    base.Configuration.LazyLoadingEnabled = true;
}

AND

public static class ModelMapper
{
    public static void InitializeRelationshipMappings(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

        modelBuilder.Entity<Document>()
            .HasRequired(d => d.FileItem)
            .WithOptional(fi => fi.Document)
            .WillCascadeOnDelete(true);


        modelBuilder.Entity<Document>()
            .HasMany(d => d.DocumentClasses)
            .WithMany(dc => dc.Documents);

        modelBuilder.Entity<ContextClass>()
            .HasMany(cc => cc.RequiredClasses);

        modelBuilder.Entity<ContextClass>()
           .HasMany(cc => cc.OptionalClasses);

        modelBuilder.Entity<ContextClass>()
            .HasMany(cc => cc.Params)
            .WithRequired(cp => cp.ContextClass)
            .WillCascadeOnDelete(true);

        modelBuilder.Entity<ContextInstance>()
            .HasRequired(ci => ci.ContextClass);

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

        modelBuilder.Entity<ContextParamValue>()
            .HasRequired(cpv => cpv.ContextParam);
    }
}
2
Is there a reason ContextParam is absent from the mapping? - Wiktor Zychla
Ummm... it's not missing... the very last statement in the last code snippet above reads: modelBuilder.Entity<ContextParamValue>() .HasRequired(cpv => cpv.ContextParam); - user3083619
Thanks. Unfortunately not though. - user3083619
Have you debugged the "for loop" and watched to see what happened to the ContextParam Property during the second iteration of the loop? Is it populated with information from the db? I am curious if the names and values are only matching for the first item and not matching for the rest of the items. One other question: Because you are casting to a list, can we assume that there are instances where there are multiple objects returned with the same name and value? Or was that done for ease of writing the code, and only one object will ever be returned? - John Bartels

2 Answers

0
votes
  1. Please check if it is not a data related issue.
  2. Else try replacing your code

       List<ContextParamValue> lst = db.ContextParamValues
        //.Include(x => x.ContextParam)
        .Where(pv => pv.ContextParam.Name.ToLower().Trim().Equals(pName.ToLower().Trim()))
        .Where(pv => pv.Value.Equals(pValue))
        .ToList<ContextParamValue>();
    
    rList.AddRange(lst);
    

with another LINQ

List<ContextParamValue> lst = from db.ContextParamValues.Where(pv => pv.ContextParam.Name.ToLower() == pName.ToLower().Trim()
                          && db.ContextParamValues.Where(pv => pv.Value == pValue)).ToList<ContextParamValue>();
  • This might be a context issue as it is either not able to load or not able to execute the linq.Try commenting your code & I am sure you can use SP to get this data (I did the same & it worked)
0
votes

Humour me, please. Put your whole for loop inside a using block:

using (var myDb = new MyDataContext()) 
{
   for (int i = 0; i < ParamNames.Length; i++)
   { 
     //etc., replacing "db" with "myDb" 
   }
}

I would be interested to know if this changes anything.