Improved support for value-converted arrays and lists
See original GitHub issueAs 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:
- Created 3 years ago
- Comments:11 (6 by maintainers)
Top 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 >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
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.
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 👍