question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Improved support for value-converted arrays and lists

See original GitHub issue

As discussed here, some query combinations including Contains do not work if ValueConverters are used (throwing Couldn't find array or element type mapping in ArrayAnyAllExpression). If configured via HasConversion, everything works as expected.

public class Program
{
    private static void Main(string[] args)
    {
        ServiceCollection serviceCollection = new();
        serviceCollection.AddDbContext<MyContext>(options =>
        {
            options.LogTo(Console.WriteLine);

            options.UseNpgsql("Server=localhost;Port=5432;Database=test;UserID=postgres;Password=postgres;");

            options.ReplaceService<IValueConverterSelector, TypedIdValueConverterSelector>();
        });

        ServiceProvider buildServiceProvider = serviceCollection.BuildServiceProvider();

        MyContext context = buildServiceProvider.GetRequiredService<MyContext>();
        context.Database.EnsureCreated();

        // Works
        ////context.EntitiesA.Where(x => context.EntitiesB.Contains(x.EntityB)).ToList();

        ////EntityBId[] ids = context.EntitiesB.Select(x => x.Id).ToArray();
        ////context.EntitiesA.Where(x => ids.Contains(x.EntityB.Id)).ToList();

        // Does not work
        List<EntityB> list = context.EntitiesB.ToList();
        context.EntitiesA.Where(x => list.Contains(x.EntityB)).ToList();

        ////EntityB[] list = context.EntitiesB.ToArray();
        ////context.EntitiesA.Where(x => list.Contains(x.EntityB)).ToList();

        ////List<EntityBId> ids = context.EntitiesB.Select(x => x.Id).ToList();
        ////context.EntitiesA.Where(x => ids.Contains(x.EntityB.Id)).ToList();
    }
}

public class TypedIdValueConverterSelector : NpgsqlValueConverterSelector
{
    private readonly ConcurrentDictionary<(Type ModelClrType, Type ProviderClrType), ValueConverterInfo> converters = new();

    public TypedIdValueConverterSelector(ValueConverterSelectorDependencies dependencies) : base(dependencies)
    {
    }

    public override IEnumerable<ValueConverterInfo> Select(Type modelClrType, Type? providerClrType = null)
    {
        foreach (var converter in base.Select(modelClrType, providerClrType)) yield return converter;

        Type underlyingModelType = Nullable.GetUnderlyingType(modelClrType) ?? modelClrType;

        if (underlyingModelType.IsAssignableTo(typeof(EntityAId)))
            yield return converters.GetOrAdd((typeof(EntityAId), typeof(int)), _ =>
            {
                return new ValueConverterInfo(typeof(EntityAId), typeof(int),
                    valueConverterInfo => new EntityAIdValueConverter(valueConverterInfo.MappingHints));
            });

        if (underlyingModelType.IsAssignableTo(typeof(EntityBId)))
            yield return converters.GetOrAdd((typeof(EntityBId), typeof(int)), _ =>
            {
                return new ValueConverterInfo(typeof(EntityBId), typeof(int),
                    valueConverterInfo => new EntityBIdValueConverter(valueConverterInfo.MappingHints));
            });
    }
}

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options) : base(options)
    {
    }

    public DbSet<EntityA> EntitiesA { get; set; }

    public DbSet<EntityB> EntitiesB { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Using HasConversion instead of ValueConverters works
        ////modelBuilder.Entity<EntityA>().HasKey(x => x.Id);
        ////modelBuilder.Entity<EntityA>().Property(x => x.Id)
        ////    .HasConversion(id => id.Value, value => new EntityAId {Value = value});

        ////modelBuilder.Entity<EntityB>().HasKey(x => x.Id);
        ////modelBuilder.Entity<EntityB>().Property(x => x.Id)
        ////    .HasConversion(id => id.Value, value => new EntityBId { Value = value });
    }
}

public class EntityA
{
    public EntityAId Id { get; set; }

    public EntityB EntityB { get; set; }
}

public class EntityB
{
    public EntityBId Id { get; set; }
}

public class EntityAId
{
    public int Value { get; set; }
}

public class EntityBId
{
    public int Value { get; set; }
}

public class EntityAIdValueConverter : ValueConverter<EntityAId, int>
{
    public EntityAIdValueConverter(ConverterMappingHints mappingHints = null) :
        base(id => id.Value, value => new EntityAId {Value = value}, mappingHints)
    {
    }
}

public class EntityBIdValueConverter : ValueConverter<EntityBId, int>
{
    public EntityBIdValueConverter(ConverterMappingHints mappingHints = null) :
        base(id => id.Value, value => new EntityBId {Value = value}, mappingHints)
    {
    }
}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:11 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
rojicommented, Feb 22, 2021

Just read and tried to understand your explanation. Did I understand it correctly that with this fix I would still have to copy / paste your selector code (which - just to point out - is not an problem for me) to make sure array converters are created for my custom type converters?

Yeah, #1719 only fixes the last problem detailed above, and doesn’t refactor NpgsqlValueConverterSelector (which is a less severe thing and can be worked around as you saw). If you feel this is important, please open another issue to track it - hopefully at some point I can take a look.

1reaction
Dreselcommented, Feb 19, 2021

Thanks for the insight, this makes sense. Wish I had more time to deep dive into frameworks like this 😄

I will try the modified / copied NpgsqlValueConverterSelector workaround later, but I’m sure it will work out 👍

Read more comments on GitHub >

github_iconTop Results From Across the Web

Array vs List in Python | 6 Main Differences to Know
Both array and the list are used to store the data as a data structure. These data structures can be used for iteration...
Read more >
6.3. Enhanced For-Loop (For-Each) for Arrays
Use the enhanced for each loop with arrays whenever you can, because it cuts down on errors. You can use it whenever you...
Read more >
Performance of Arrays vs. Lists
Short answer: · If needed to add cells in the beginning/middle/end of the list (often) · If needed only sequential access (forward/backward) ·...
Read more >
Difference between List and Array in Python
In Python, lists and arrays are the data structures that are used to store multiple items. They both support the indexing of elements...
Read more >
Ultimate Guide to Lists, Tuples, Arrays and Dictionaries For ...
Tuples have a slight performance improvement to lists and can be used as indices to dictionaries. Arrays only store values of similar data ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found