Mapping arrays feature is really cool https://www.npgsql.org/efcore/mapping/array.html
However when I work with DDD I would like to expose collections as readonly using IEnumerable or IReadOnlyCollection
public class Entity
{
public IReadOnlyCollection<Guid> Attributes => attributes ;
private Guid[] attributes = Array.Empty<Guid>();
}
and then, when I use LINQ on entity with backing field
.Where(c => c.Attributes.Contains(id))
it will generate in memory query instead of SQL query
WHERE "guid" = ANY("c"."Attributes")
Is it possible to support LINQ with sql properly for IReadOnlyCollection with backing fields?
This is an interesting idea that is unfortunately unsupported at this time.
The challenge is that the interface does not have an associated mapping handler, which triggers an exception upon model creation.
It _almost_ seems plausible via backing fields, but that will still trigger the exception:
```c#
static void Main(string[] args)
{
var connection =
$"Host=localhost;Port=5432;Username={args[0]};Password={args[1]};Database=some_context;";
using (var ctx = new SomeContext(connection))
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
ctx.SomeEntity.Add(new SomeEntity { Id = 1 });
ctx.SaveChanges();
}
using (var ctx = new SomeContext(connection))
{
var _ = ctx.SomeEntity.Where(x => x.ReadOnlyList.Contains(Guid.Empty)).ToArray();
}
Console.WriteLine("Completed successfully");
}
class SomeEntity
{
Guid[] _array = new Guid[0];
public int Id { get; set; }
public IReadOnlyList<Guid> ReadOnlyList => _array;
}
class SomeContext : DbContext
{
readonly string _connection;
public DbSet<SomeEntity> SomeEntity { get; set; }
public SomeContext(string connection) => _connection = connection;
protected override void OnConfiguring(DbContextOptionsBuilder builder)
=> builder.UseNpgsql(_connection);
protected override void OnModelCreating(ModelBuilder builder)
=> builder.Entity<SomeEntity>()
.Property(x => x.ReadOnlyList)
.HasField("_array");
}
```
I'll leave this issue open for now, as @roji may have some insight on workarounds.
As I know IEnumerable<T> support was removed from the driver.
The reason of why there is no handler for interfaces is that not every iterator can be iterated twice. Some of them are immutable and always return the same values, some of them throws an InvalidOperationException, etc.
To workaround it you can implement your own type handler for the driver and probably add translator to the EF provider. But I think that collection support should be extended to work with the ICollection<T> and IReadOnlyCollection<T> interfaces.
What you're looking for should be possible by mapping a private array field, and then defining a property which exposes it - but with EF Core totally ignoring it. This way EF Core only deals with your field, which is of the correct type, but your application accesses it only via the public property, which casts it to whatever you need. @austindrenski I think that your example doesn't work because you still attempt to make EF Core aware of the property - it needs to ignore it instead. See the EF Core docs on backing fields.
Regarding direct support for the abstractions you requested... As @YohDeadfall explained above, IEnumerable support was removed - it does not really correspond to the database (which is an array), and the driver has to enumerate the value twice. This last bit made it very easy for users to trigger hard-to-detect bad performance (as complex expressions were evaluated twice) or exceptions (when an enumerable simply can't be enumerated twice). Enumerable simply isn't the right abstraction here.
Regarding Collection, that again does not correspond well to the database type - PostgreSQL arrays are an ordered type, whereas a Collection isn't. Mapping IList<T> to arrays does work though.
Closing this issue but feel free to post back with questions if anything remains unclear.
@austindrenski I think that your example doesn't work because you still attempt to make EF Core aware of the property - it needs to ignore it instead. See the EF Core docs on backing fields.
I tried a variation of that, but it also triggered the exception. That said, I put that sample together pretty quickly, so it's entirely possible that I just botched the configuration.
@myshon Let us know if you get the backing field version to work. It would be great to have an example in the docs of how to accomplish this.
@austindrenski I think that your example doesn't work because you still attempt to make EF Core aware of the property - it needs to ignore it instead. See the EF Core docs on backing fields.
I tried a variation of that, but it also triggered the exception. That said, I put that sample together pretty quickly, so it's entirely possible that I just botched the configuration.
Hmm, EF Core only knows about the private array field, so I don't see any reason for any exception to occur... Note that if the property has a public getter/setter pair, you'd need to explicitly tell EF Core to ignore it, otherwise it'll again try to map it and throw.
Hmm, EF Core only knows about the private array field, so I don't see any reason for any exception to occur... Note that if the property has a public getter/setter pair, you'd need to explicitly tell EF Core to ignore it, otherwise it'll again try to map it and throw.
It looked something like this:
```c#
[NotMapped]
public IReadOnlyList
```c#
builder.Entity<SomeEntity>()
.Property(b => b.ReadOnlyList)
.HasField("_array")
.UsePropertyAccessMode(PropertyAccessMode.Field);
Perhaps that property reference in the fluent config should have been omitted?
Perhaps that property reference in the fluent config should have been omitted?
Yeah, that's what I meant - check out the docs on mapping fields without properties. The point is that EF Core doesn't need to map - or even know - the property in question, it's just a user-facing wrapper over the private field, which provides casting.
Regarding
Collection, that again does not correspond well to the database type - PostgreSQL arrays are an ordered type, whereas aCollectionisn't. MappingIList<T>to arrays does work though.
Checked that the new ordered dictionary class implements IList<T>. So other ordered collections should implement it as well, and there is no reason to support collection interfaces. But ArrayHandler lacks support of read only lists.
I'm facing the same problem, but i'm use Microsoft.EntityFrameworkCore.Proxies.
When I'm pointing out in Entity
private readonly List<SomeEntity> _rCollection = new List<SomeEntity>();
public virtual IReadOnlyCollection<SomeEntity> REntities => _rCollection.AsReadOnly();
and in "Context"
builder.Entity<Test>(entity =>
{
var navigation = entity.Metadata.FindNavigation(nameof(AppUser.REntities));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
});
then private property "_rCollection" gonna be empty until I explicitly evaluate property in debug tools IDE or call getter "REntities" .
Is there a solution to this problem?
Is there a solution to this problem?
What exactly is the problem you'd like to see fixed? If you're using lazy-loading proxies, and then it makes sense for _rCollection to only get populated once you asked for it - that's what lazy-loading is all about, and it has nothing to do with arrays or lists.
Most helpful comment
Yeah, that's what I meant - check out the docs on mapping fields without properties. The point is that EF Core doesn't need to map - or even know - the property in question, it's just a user-facing wrapper over the private field, which provides casting.