linq2db.EntityFrameworkCore
linq2db.EntityFrameworkCore copied to clipboard
LinqToDb fails with "Association key not found for type"
Trying to use linq2db with existing EF code base and found a case, which might be interesting to you as it seems to be valid for EF, but will fail in runtime when linq2db is used as expression engine.
Let's say we have next domain model:
public class Container
{
public int Id { get; set; }
public virtual ICollection<Item> Items { get; set; }
public virtual ICollection<ChildItem> ChildItems { get; set; }
}
public class Item
{
public int Id { get; set; }
public int ContainerId { get; set; }
public virtual ChildItem Child { get; set; }
public virtual Container Container { get; set; }
}
public class ChildItem
{
public int Id { get; set; }
public virtual Item Parent { get; set; }
}
ChildItem
here is basically a descendant of Item
, but relationship is implemented with use of composition instead of inheritance (migrating from EF 6 to EF Core 3, which doesn't support Table Per Type for hierarchies, hence such design).
And of course in real-world model both Item
and ChildItem
have more attributes and there're multiple Item
"descendants" omitted here for brevity.
Configuration for these entities is as follows:
internal sealed class ContainerConfiguration : IEntityTypeConfiguration<Container>
{
public void Configure(EntityTypeBuilder<Container> _)
{
_.HasKey(x => x.Id);
_.Property(x => x.Id).UseIdentityColumn();
}
}
internal sealed class ItemConfiguration : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> _)
{
_.HasKey(x => x.Id);
_.Property(x => x.Id).UseIdentityColumn();
_.HasOne(a => a.Container)
.WithMany(b => b.Items)
.IsRequired()
.HasForeignKey(a => a.ContainerId);
}
}
internal sealed class ChildItemConfiguration : IEntityTypeConfiguration<ChildItem>
{
public void Configure(EntityTypeBuilder<ChildItem> _)
{
_.HasKey(e => e.Id);
_.Property(e => e.Id).ValueGeneratedNever();
// semantically each ChildItem is also an Item, hence one-to-one relationship with Id as FK
_.HasOne(a => a.Parent)
.WithOne(b => b.Child)
.IsRequired()
.HasForeignKey<ChildItem>(a => a.Id);
}
}
Then this query, which is pretty valid in terms of EF will fail (EF 6 was able to handle that, but EF Core fails as well due to a known bug). Error message is Association key 'ContainerId' not found for type 'ChildItem.
var children =
dbContext.Containers
.Select(c => new
{
ChildItems = c.ChildItems
.Select(ch => new
{
ContainerId = ch.Parent.ContainerId,
}),
})
.ToLinqToDB()
.ToArray();
Things work, if I rewrite my query this way, using Items
as an entry point and navigating to child in the expression instead of using ChildItems
directly.
var children =
dbContext.Containers
.Select(c => new
{
ChildItems = c.Items
.Select(i => i.ChildItem)
.Select(ch => new
{
ContainerId = ch.Parent.ContainerId,
}),
})
.ToLinqToDB()
.ToArray();
Having said that, do I get it correctly that it's not possible to have ChildItems
collection inside the Container
type with linq2db?
Here is a code, if you decide to check it out to improve sth in the libraries. Repro.zip
It is TPH? If yes, I do not think it will work, linq2db do not support such "feature".
@sdanyliv No, it isn't.
All the entities are stored in separate tables and have such relationships:
-
Container
has one-to-many association withItem
; -
Item
has one-to-one association withChildItem
.
It was "Table per Type" with EF 6: ChildItem
was inherited from Item
, but now it's implemented with composition on the type level instead, so we have "inheritance" just semantically.
This issue is not only present in some exotic table setup. I ran into the same with the simplest possible many-to-one relation between two tables, and managed to replicate it in Microsoft's OData BookStore demo project (slightly modified to use SQLExpress instead of in-memory).
Basically, a query like this
public IQueryable<Book> Get() { return _db.Books .Where(b => b.Press.Category == Category.Book) .ToLinqToDB(); }
will give the following error
AggregateException: One or more errors occurred. (Association key 'PressId' not found for type 'BookStore.Models.Book.)
System.Threading.Tasks.Task<TResult>.GetResultCore(bool waitCompletionNotification) System.Threading.Tasks.Task<TResult>.get_Result() LinqToDB.EntityFrameworkCore.Internal.LinqToDBForEFQueryProvider<T>.GetAsyncEnumerator(CancellationToken cancellationToken) Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadInternal<T>(object value) Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, object asyncEnumerable, Func<object, Task<ICollection>> reader) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Seems like a bug to me. Or am I not using ToLinqToDB()
correctly?
Here's the project: BookStoreDotNet5.zip
@nforss I might guess, that proper configuration is missing for the Book
entity hence the runtime failure. Also I'm not that sure that it's possible to have navigation property without the foreign key property.
So, pls, add PressId
property to the Book
type to start with and, if that is not enough, configure the relation explicitly in the OnModelCreating
method of your DbContext
type.
Hope that helps.
Thank you for the quick reply!
Adding the PressId property to the Book class does indeed fix the problem. I was under the impression that linq2db.EntityFrameworkCore would use the existing EF DbContext mappings to figure these relationships out, but I guess that is not the case. It's of course a bit of a hassle to explicitly type out all the foreign key properties that EF doesn't need, but since we're not using that many classes with LinqToDB, maybe we'll manage.
@nforss It indeed uses the EF model to build its own, it's just not that every feature of EF is supported (and vice versa). So I'm guessing that having a property for a FK is required, but I'm not an author of the library and haven't used it heavily, so might well be mistaken. Try to play with EF entity configuration and/or AssociationAttribute
, it might help.
Have found the same behaviour in a 1..* with the child expecting a reference to the parent, using an explicit [ParentName]Id. I'm just trying to use the TempTable as part of EF Core. EF Core can handle the relationship implicitly and I don't want to start spreading [ParentName]Id in all my model files. It's really a shame as otherwise it very much fitted my requirements.