Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.InvalidOperationException: 'Couldn't find a value for that key' #221

Closed
C-Wal opened this issue Mar 18, 2022 · 27 comments
Closed

System.InvalidOperationException: 'Couldn't find a value for that key' #221

C-Wal opened this issue Mar 18, 2022 · 27 comments
Assignees
Labels

Comments

@C-Wal
Copy link

C-Wal commented Mar 18, 2022

Why does this simple set-up result in an exception? I am using .Net Framework 4.6.2 and AgileMapper 1.8.

public class ItemDto
{
    public int A { get; set; }
    public string B { get; set; }
}

public class Item
{
    public int A { get; set; }
    public string B { get; set; }

    public Item() { }

    public Item(int A, string B)
    {
        this.A = A;
        this.B = B;
    }
}

public class MapperConfig : MapperConfiguration
{
    protected override void Configure()
    {
        WhenMapping.From<ItemDto>().To<Item>();
    }
}

    static void Main(string[] args)
    {
        var mapper = Mapper.CreateNew();
        Mapper.WhenMapping.UseConfigurations.From<MapperConfig>();

        var dto = new ItemDto { A = 1, B = "two" };

        var item = mapper.Map(dto).ToANew<Item>();
    }

The exception and message are returned from the call to ToANew().

If I remove the constructor with parameters from the Item class then there is no exception thrown.

If I use Over() with a new instance of Item then there is no exception thrown.

@SteveWilkes SteveWilkes self-assigned this Mar 18, 2022
@SteveWilkes
Copy link
Member

Hi,

Thanks for letting me know about this - it looks like it's being caused by your constructor parameters having the same name as your properties - if you change the constructor parameter names to lower case, it works. Obviously that shouldn't be happening, so I'll look into it and sort it out.

Cheers,

Steve

@C-Wal
Copy link
Author

C-Wal commented Mar 18, 2022

This was a simple repro of an issue we're facing in our actual project. After making the constructor parameters lower case in our project we get past that error and instead get 'an item with the same key has already been added'. We haven't got a simple repro for that yet, but I thought I'd FYI in case it's related.

@C-Wal
Copy link
Author

C-Wal commented Mar 18, 2022

We have now removed WhenMapping.ThrowIfAnyMappingPlanIsIncomplete() from the config and now the 'an item with the same key has already been added' error is gone and the mapping works. Not sure if that's expected or not?

@C-Wal
Copy link
Author

C-Wal commented Mar 18, 2022

Interestingly if we leave in WhenMapping.ThrowIfAnyMappingPlanIsIncomplete() and add SwallowAllExceptions() on the problem mapping we still get this 'an item with the same key has already been added' error when using that mapping.

@SteveWilkes
Copy link
Member

That's all good info, thanks very much for letting me know. I'll sort out each of those issues :)

@SteveWilkes
Copy link
Member

Hi,

I've fixed the initial issue with the same-named, same-casing constructor parameters, but have been unable to reproduce the 'an item with the same key has already been added' exception with ThrowIfAnyMappingPlanIsIncomplete() - are you able to give me a repro for that?

To offer a couple of notes - and I understand the code you've shared is an incomplete snippet from a larger project, so this might not be relevant:

  • If you create an instance-scoped mapper with Mapper.CreateNew(), that's the object you should apply configurations to, so it'd be:
mapper.WhenMapping.ThrowIfAnyMappingPlanIsIncomplete();
// and
mapper.WhenMapping.UseConfigurations.From<MapperConfig>();

...rather than:

Mapper.WhenMapping.ThrowIfAnyMappingPlanIsIncomplete();
// and
Mapper.WhenMapping.UseConfigurations.From<MapperConfig>();
  • To up-front cache a set of mapping plans in a MappingConfig, use GetPlansFor<ItemDto>().To<Item>() rather than WhenMapping.From<ItemDto>().To<Item>() - the latter is a starting point for configuration, and doesn't do anything by itself.

All the best,

Steve

@C-Wal
Copy link
Author

C-Wal commented Mar 21, 2022

Ah yes the capital M was typo on the repro, though the behaviour is the same either way.

Can we use WhenMapping.ThrowIfAnyMappingPlanIsIncomplete(); in the configuration class or does it have to be applied to the instance?

@C-Wal
Copy link
Author

C-Wal commented Mar 21, 2022

@SteveWilkes If you can publish the change for the initial issue we can test it and maybe it will resolve the others.

@C-Wal
Copy link
Author

C-Wal commented Mar 21, 2022

@SteveWilkes What is the correct way to clear the cache? We are often getting stuck with this error when calling GetPlanFor and undoing a change doesn't always clear it:

System.InvalidProgramException: 'Common Language Runtime detected an invalid program.'

@SteveWilkes
Copy link
Member

For 'Common Language Runtime detected an invalid program' - that's probably being thrown when the code is attempting to compile a mapping Func, but it's hard to say why. Do you have more of a stack trace?

@C-Wal
Copy link
Author

C-Wal commented Mar 22, 2022

at System.Runtime.CompilerServices.RuntimeHelpers._CompileMethod(IRuntimeMethodInfo method)
at System.Reflection.Emit.DynamicMethod.CreateDelegate(Type delegateType, Object target)
at System.Linq.Expressions.Compiler.LambdaCompiler.CreateDelegate()
at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda, DebugInfoGenerator debugInfoGenerator)
at System.Linq.Expressions.Expression1.Compile() at AgileObjects.AgileMapper.ObjectPopulation.RepeatedMappings.RepeatedMapperFunc2.CreateMapperFunc(IObjectMappingData mappingData, Boolean isLazyLoading)
at AgileObjects.AgileMapper.ObjectPopulation.RepeatedMappings.RepeatedMapperFunc2..ctor(IObjectMappingData mappingData, Boolean lazyLoadFuncs) at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapper2.CacheRepeatedMappingFuncs()
at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapper2..ctor(Expression mapping, IObjectMappingData mappingData) at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.Create[TSource,TTarget](ObjectMappingData2 mappingData)
at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData2.GetOrCreateMapper() at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.<>c__72.b__7_0(IRootMapperKey key)
at AgileObjects.AgileMapper.Caching.ArrayCacheBase2.GetOrAdd(TKey key, Func2 valueFactory)
at AgileObjects.AgileMapper.ObjectPopulation.ObjectMapperFactory.GetOrCreateRoot[TSource,TTarget](ObjectMappingData2 mappingData) at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingData2..ctor(TSource source, TTarget target, Nullable1 elementIndex, Object elementKey, MappingTypes mappingTypes, IMappingContext mappingContext, IObjectMappingData declaredTypeMappingData, IObjectMappingData parent, Boolean createMapper) at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingDataFactory.ForRootFixedTypes[TSource,TTarget](TSource source, TTarget target, MappingTypes mappingTypes, IMappingContext mappingContext, Boolean createMapper) at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingDataFactory.ForRootFixedTypes[TSource,TTarget](TSource source, TTarget target, IMappingContext mappingContext, Boolean createMapper) at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingDataFactory.ForRootFixedTypes[TSource,TTarget](IMappingContext mappingContext, Boolean createMapper) at AgileObjects.AgileMapper.ObjectPopulation.ObjectMappingDataFactory.ForRootFixedTypes[TSource,TTarget](IMappingContext mappingContext) at AgileObjects.AgileMapper.Api.PlanTargetSelector1.GetMappingPlan[TTarget](IMappingContext planContext, Func2 mappingDataFactory, ICollection1 configurations)
at AgileObjects.AgileMapper.Api.PlanTargetSelector1.GetMappingPlan[TTarget](MappingRuleSet ruleSet, ICollection1 configurations)
at AgileObjects.AgileMapper.Api.PlanTargetSelector1.ToANew[TResult](Expression1[] configurations)
at ConsoleApp2.Program.Main(String[] args) in C:\Users\C-Wal\source\repos\ConsoleApp2\Program.cs:line 23

@C-Wal
Copy link
Author

C-Wal commented Mar 22, 2022

@SteveWilkes I made a debug build of AgileMapper and pulled out the mappingLambda debug view. I had to anonymize it a bit but hopefully it's close enough to give you an idea of what's going on:

#Lambda1<AgileObjects.AgileMapper.ObjectPopulation.MapperFunc2[ItemDto,Domain.Contact]>(
AgileObjects.AgileMapper.ObjectPopulation.IObjectMappingData2[ItemDto,Domain.Contact] $tcdToCData)
{ 
    .Try { 
        .Block(Domain.Contact $contact) 
        { 
            .If ( .Call $tcdToCData.TryGet( $tcdToCData.Source, $contact) ) 
            { 
                .Return Return { $contact } 
            };
            
            $contact = ($tcdToCData.Target ?? .New Domain.Contact());
            
            .Call $tcdToCData.Register( $tcdToCData.Source, $contact);
            
            "// No data sources for ID"; 
            "// No data sources for Title_ID"; 
            "// No data sources for Title or any of its child members"; 
            "// No data sources for FirstName"; 
            "// No data sources for LastName"; 
            "// No way to populate Initials (readonly string)"; 
            "// No way to populate FullName (readonly string)"; 
            "// No way to populate Age (readonly int?)";
            "// No data sources for ContactMethods or any of its child members"; 
            
            .If ($contact.PreferredEmail != .Default(Domain.ContactMethod))
            { 
                .Call $tcdToCData.MapRepeated(
                    $tcdToCData.Source,
                    $contact.PreferredEmail,
                    $tcdToCData.ElementIndex, 
                    $tcdToCData.ElementKey,
                    "PreferredEmail",
                    0) 
            };
                
            .If ($contact.PreferredContactMethod != .Default(Domain.ContactMethod)) 
            {
                .Call $tcdToCData.MapRepeated( 
                    $tcdToCData.Source,
                    $contact.PreferredContactMethod, 
                    $tcdToCData.ElementIndex,
                    $tcdToCData.ElementKey, 
                    "PreferredContactMethod",
                    0)
            };

            "// No data sources for Addresses or any of its child members"; 
            "// No data sources for PrimaryAddress or any of its child members";
            "// No way to populate HasPrimaryAddress (readonly bool)";
            "// No data sources for Comms or any of its child members"; 
            "// No way to populate HasComms (readonly bool)";

            .If ($contact.PreviousComm != .Default(Domain.Comm)) 
            { 
                .Call $tcdToCData.MapRepeated(
                    $tcdToCData.Source,
                    $contact.PreviousComm, 
                    $tcdToCData.ElementIndex, 
                    $tcdToCData.ElementKey,
                    "PreviousComm",
                    0)
            };

            "// No data sources for Created";
            "// No data sources for Creator_ID"; 
            "// No data sources for Creator or any of its child members"; 
            
            .Label $contact .LabelTarget Return: 
        }
    }
    .Catch (System.Exception $ex)
    {
        .Throw .Call AgileObjects.AgileMapper.MappingException.For( 
            "CreateNew",
            "ItemDto",
            "Item.Comm.Contact",
            $ex) 
    }
} 

@SteveWilkes
Copy link
Member

Thanks! I'll see what I can figure out...

@SteveWilkes
Copy link
Member

Ello,

Looking at the callstack - the error is being thrown when the mapper is trying to create a function to map one of the recursive relationships - either ContactMethod or Comm - could you show me the object structures for those two types?

Thanks,
Steve

@C-Wal
Copy link
Author

C-Wal commented Mar 24, 2022

Adding IgnoreTargetMembersOfType() to the mapping config stops the exception so I think that's where the problem lies. I'll try and get a simple repro for it.

@C-Wal
Copy link
Author

C-Wal commented Mar 24, 2022

@SteveWilkes Attached is a small solution that reproduces the invalid runtime exception. FYI I think there is possibly more than one cause.
repo.zip

@SteveWilkes
Copy link
Member

Excellent, thank you very much!

@SteveWilkes
Copy link
Member

Ok, that was an interesting one - the problem came from the mapper trying to map the Contact.PreferredContactMethod property, which is a named, indexed property. I'd never come across those before (they're VB.NET-specific) and didn't know there was such a thing!

I've pushed a fix to the releases/next branch - the mapper now ignores these types of properties.

SteveWilkes added a commit that referenced this issue Mar 26, 2022
…efault values, re: #221 / Performance improvements
@SteveWilkes
Copy link
Member

I've now pushed a further fix which adds support for mapping VB.NET named, indexed properties, provided all the indexes have default values, which will be used when mapping the member.

@C-Wal
Copy link
Author

C-Wal commented Mar 28, 2022

Thanks, I'll check it out.

@C-Wal
Copy link
Author

C-Wal commented Mar 28, 2022

@SteveWilkes The updated AgileMapper does get past the issues we were having but then hits another. I've added a little more to one of the methods and the signature is now what we have in our solution. New repro project:
repro2.zip

@SteveWilkes
Copy link
Member

Thanks again, I'll take a look.

@SteveWilkes
Copy link
Member

I hadn't handled the default value for an optional index being Nothing - this is now fixed.

@C-Wal
Copy link
Author

C-Wal commented Mar 29, 2022

Great, I'll give that a go and let you know if there's any further issues.

@C-Wal
Copy link
Author

C-Wal commented Mar 29, 2022

@SteveWilkes FYI, we are still very interested in pushing on with integrating AgileMapper but unfortunately it looks like any further work on it is going to be a couple of sprints away so it might be a while before I can come back to you with any further issues..

@SteveWilkes
Copy link
Member

No problem @C-Wal - thanks for all your feedback so far :)

@SteveWilkes SteveWilkes added the in-branch A feature or bug fix exists in code, and a release will follow label Mar 30, 2022
SteveWilkes added a commit that referenced this issue Mar 31, 2022
* Fixing mapping of ctor parameters named eactly the same as members, re: #211 / Moving LangVersion to Build.Directory.props / Performance tweaks / Updating NuGet packages

* Renames for clarity

* Handling named, indexed VB.NET properties, re: #221

* Support for VB.NET named, indexed properties where all indexes have default values, re: #221 / Performance improvements

* Fixing cache concurrency

* Switching .NET Core test projects to LTS versions

* Adding .NET 5 + 6 test projects

* Updating release notes

* Handling named, indexed property null optional default index values, re: #211

* Tweak

* Updating to v1.8.1

* Removing NuGet pack bat file

* Add v1.8.1 NuGet package
@SteveWilkes
Copy link
Member

The defects we found in this issue are fixed as of v1.8.1, which is now available on NuGet - please feel free to open another issue here if you find more!

All the best,
Steve

@SteveWilkes SteveWilkes removed the in-branch A feature or bug fix exists in code, and a release will follow label Mar 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants