Description
When attempting to eagerly .Include() Many-to-Many ICollection properties on an already nested ICollection, I receive a EntityCommandCompilationException with the innerException: System.NotSupportedException: APPLY joins are not supported.
Are there any (suitable) workarounds, or is this simply not supported by EF6/SQLite? If not supported, would migrating to EntityFramework.Core work?
If some of you can confirm that this is a limitation, I'd create an issue on the SQLite issue tracker
Quick Problem Synopsis
// Bare minimum model definitions - see below for a full implementation
public class Mode
{
[Key] public int Id { get; set; }
}
public class Type
{
[Key] public int Id { get; set; }
}
public class Preset
{
[Key] public string PresetId { get; set; }
/* Note: Both Types and Modes are Many-To-Many Entity Relationship configured
* using Join Table/Fluent API */
public ICollection<Type> Types { get; set; } = new List<Type>();
public ICollection<Mode> Modes { get; set; } = new List<Mode>();
}
public class Plugin
{
[Key] public int Id { get; set; }
public ICollection<Preset> Presets { get; set; } = new List<Preset>();
}
public void test ()
{
context.Plugins
.Include("Presets.Modes")
.Include("Presets.Types")
.Load();
}
// EntityCommandCompilationException An error occurred while preparing the command definition. See the inner exception for details.
// innerException System.NotSupportedException: APPLY joins are not supported
This is the basic code which fails. See below for the complete code and how the models are implemented.
In contrast, including just one of the two ICollection navigation properties works fine:
context.Plugins
.Include("Presets.Modes")
.Load();
Also, including more than one ICollection on the same level works fine:
context.Presets
.Include("Modes")
.Include("Types")
.Load();
Further Information, Environment
The problem only affects EntityFramework6
together with System.Data.SQLite.EF6. As far as I know EntityFramework.SqlServerCompact is not affected.
I am using EF6 Code-First, I have not tried EF6 Model-First or Database-First (and switching to either of those is not an applicable solution)
The problem only occurs with many-to-many collection navigation properties, and only if I attempt to load multiple ones on the same level
I have tried common alternatives, including Select(), which yields the same exception
I have not tried building manual joins or queries, as this would result in extreme code maintenance loss in my project
At this point, I believe the root cause is a limitation of the SQLite EF6 implementation (which doesn't seem to be documented) rather than something I could fix easily with code
I've only found one similar StackOverflow question regarding multiple .Include(), and the (awful to use) SQLite bugtracker as well as the mailing lists don't seem to mention this particular issue
The full source code, including a test database with test data can be found in my GitHub EntityFrameworkResearch repository. Specifically the Model definitions, the DbContext and the repro code
Code Reference
public class Mode
{
[Key] public int Id { get; set; }
public ICollection<Plugin> Plugins { get; set; }
public ICollection<Preset> Presets { get; set; }
public string Name { get; set; }
}
public class Type
{
[Key] public int Id { get; set; }
public ICollection<Plugin> Plugins { get; set; } = new List<Plugin>();
public ICollection<Preset> Presets { get; set; } = new List<Preset>();
public string Name { get; set; }
public string SubTypeName { get; set; }
}
public class Plugin
{
[Key] public int Id { get; set; }
public ICollection<Preset> Presets { get; set; } = new List<Preset>();
public ICollection<Type> DefaultTypes { get; set; } = new List<Type>();
public ICollection<Mode> DefaultModes { get; set; } = new List<Mode>();
}
public class Preset
{
[Key] public string PresetId { get; set; } = Guid.NewGuid().ToString();
[ForeignKey("Plugin")] public int PluginId { get; set; }
public Plugin Plugin { get; set; }
public ICollection<Type> Types { get; set; } = new List<Type>();
public ICollection<Mode> Modes { get; set; } = new List<Mode>();
}
public class ApplicationDatabaseContext : DbContext
{
// Constructor and further database initialization omitted for clarity
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Plugin>()
.HasMany(p => p.DefaultModes)
.WithMany(q => q.Plugins)
.Map(mc => mc.MapLeftKey("PluginId").MapRightKey("ModeId").ToTable("PluginModes"));
modelBuilder.Entity<Plugin>()
.HasMany(p => p.DefaultTypes)
.WithMany(q => q.Plugins)
.Map(mc => mc.MapLeftKey("PluginId").MapRightKey("TypeId").ToTable("PluginTypes"));
modelBuilder.Entity<Preset>()
.HasMany(p => p.Types)
.WithMany(q => q.Presets)
.Map(mc =>mc.MapLeftKey("PresetId").MapRightKey("TypeId").ToTable("PresetTypes"));
modelBuilder.Entity<Preset>()
.HasMany(p => p.Modes)
.WithMany(q => q.Presets)
.Map(mc => mc.MapLeftKey("PresetId").MapRightKey("ModeId").ToTable("PresetModes"));
}
public DbSet<Plugin> Plugins { get; set; }
public DbSet<Preset> Presets { get; set; }
public DbSet<Mode> Modes { get; set; }
public DbSet<Type> Types { get; set; }
}
Related
In Entity Framework Core version 2.2 or 3.0, is it possible to use owned/complex types in such a way that this kind of configuration is possible:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public ProductProperties Properties { get; set; }
}
public class ProductProperties {
public List<ProductSize> Sizes { get; set; }
}
public class Size {
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductSize {
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Product)
.WithMany(x => x.Properties.Sizes)
.HasForeignKey(x => x.ProductId);
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Size)
.WithMany()
.HasForeignKey(x => x.SizeId);
The error message which is seen for this kind of approach usually ends up in:
'x => x.Properties.Sizes' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'
An earlier found answer is almost exactly matching my question, but this was posted in 2013. By the time it was almost certainly not possible.
HasForeignKey relationship through a Complex Type property
The sources on Microsoft are only giving examples for creating an entity with the complex type itself, not for creating relationships between them.
The cause of the issue
In your sample code it's quite clear there is no specific Many to Many relation. To make my argument a bit more convincing what follows is a model of your entities and their relations:
The new class structure
For a Many to Many relation to work in EF the product and size tables need to have an implicit relation with each other through a singular junction table. In my proposed solution I've chosen the ProductProperty table. There I've added the fields from the productsize junction table:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
public class ProductProperty
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
public class Size
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
The functions
modelBuilder.Entity<ProductProperty>()
.HasKey(pp => new { pp.ProductId, pp.SizeId });
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Product)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.ProductId);
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Size)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.SizeId);
Additional advice (EDIT)
Make the "Size" class a generic property class. This way the Many-to-Many relation won't get broken and querying will also be very easy:
public class Property
{
public int Id { get; set; }
public PropertyType propType { get; set; }
public string propValue { get; set; }
}
public enum PropertyType
{
Size,
Fontsize,
...
}
As a final argument this change will make it easier to change existing properties or add new ones
Sources
https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
You can check the owned entity types released in 2019 Check documentation here
An example from the link is the following:
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
The owns many function should help you like this:
modelBuilder.Entity<Distributor>().OwnsMany(p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Let me know if I misunderstood your question.
I did research on my problem, but somehow I don't get it.
I have 4 classes with linked entities in a 1:n way:
public class ContentProtectionProject
{
[Required]
public int Id { get; set; }
...
[Required]
public List<UrlToProtect> UrlsToProtect { get; set; }
[Required]
public int AccountId { get; set; }
[ForeignKey("AccountId")]
public Account Account { get; set; }
}
public class UrlToProtect
{
[Required]
public int Id { get; set; }
...
[Required]
public List<UrlTextContent> UrlsTextContent { get; set; }
[Required]
public int ContentProtectionProjectId { get; set; }
[ForeignKey("ContentProtectionProjectId")]
public ContentProtectionProject ContentProtectionProject { get; set; }
}
public class UrlTextContent
{
[Required]
public int Id { get; set; }
...
[Required]
public List<UrlTextSnippet> UrlTextSnippets { get; set; }
[Required]
public int UrlToProtectId { get; set; }
[ForeignKey("UrlToProtectId")]
public UrlToProtect UrlToProtect { get; set; }
}
public class UrlTextSnippet
{
[Required]
public int Id { get; set; }
...
[Required]
public int UrlTextContentId { get; set; }
[ForeignKey("UrlTextContentId")]
public UrlTextContent UrlTextContent { get; set; }
}
I like to get all data for a project, which I try to get this way by projectId from a repository:
public async Task<ContentProtectionProject> GetContentProtectionProject(int contentprotectionProjectId)
{
var contentProtectionProject = await _context.ContentProtectionProjects
.Include(x => x.UrlsToProtect)
.ThenInclude(u => u.UrlsTextContent)
.FirstOrDefaultAsync(x => x.Id == contentprotectionProjectId);
return contentProtectionProject;
}
I am only able to go to the level of "UrlTextContent", but I am somehow not able to include "UrlTextSnippet".
My goal is to load a complete "Project" to be able to do some processing on the linked entities list items.
In the end I want to find all "UrlTextContent" for which no "UrlTextSnippets" are available by iteration over the linked entities.
I use .NET Core 2.1.403 with Entity Framework Core .NET 2.1.4-rtm-31024
Any help is very much appreciated.
Best regards
Edit:
Context class:
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base (options) {}
...
public DbSet<ContentProtectionProject> ContentProtectionProjects { get; set; }
public DbSet<UrlToProtect> UrlToProtects { get; set; }
public DbSet<UrlTextContent> UrlTextContents { get; set; }
public DbSet<UrlTextSnippet> UrlTextSnippets { get; set; }
}
Edit 2: Debug screenshot
"UrlTextSnippet" list is null, although one entry is available.
I hate those people that say you can do something you aren't able to do, so sorry in advance. According to the Documentation you should be able to chain those .ThenInclude() or the .Include(). Hopefully these get you on the right track.
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.Include(blog => blog.Owner)
.ThenInclude(owner => owner.Photo)
.ToList();
}
There is also this but I try to avoid selects if I can.
Additionally you might be running into this issue and you might be able to do it but the IntelliSense may be leading you astray.
Note: Current versions of Visual Studio offer incorrect code completion
options and can cause correct expressions to be flagged with syntax
errors when using the ThenInclude method after a collection navigation
property. This is a symptom of an IntelliSense bug tracked at
https://github.com/dotnet/roslyn/issues/8237. It is safe to ignore
these spurious syntax errors as long as the code is correct and can be
compiled successfully.
I would say that it takes a lot of time to get to know ASP.NET Core to understand how to achieve things then previous versions with webforms, but I understand that ASP.NET Core is bigger and you are able to build more complex solutions.
I'm quite new to ASP.NET Core and I'm trying to understand EF Core and related data. I'm using https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro to learn the basics and create my first ASP.NET Core application.
I have a Entity "Standard" that can have multiple Forms (Form entity). The entities share a couple of same properties so I've made them both inherit from a master class called MasterDocument. Previously called Document.
Standard:
namespace Skjemabasen.Models.Document
{
public class Standard : MasterDocument
{
[Display(Name = "Kategori")]
public virtual Category Category { get; set; }
[Display(Name = "Dokumenter")]
public ICollection<Form> Forms { get; set; }
}
}
Form:
public class Form : MasterDocument
{
public Format Format { get; set; }
public virtual Member Assignee { get; set; }
public String Status { get; set; }
[ForeignKey("Standard")]
public int StandardId { get; set; }
public Standard Standard { get; set; }
public ICollection<Customer.Subscription> Subscribers { get; set; }
}
MasterDocument:
namespace Skjemabasen.Models.Document
{
public class MasterDocument : IDocument
{
public int ID { get; set; }
[Required]
[Display(Name = "EStandard")]
[StringLength(50)]
public string EStandard { get; set; }
[Required]
[Column("Betegnelse")]
[Display(Name = "Betegnelse")]
[StringLength(60)]
public string Betegnelse { get; set; }
[Display(Name = "Kommentar")]
public string Comment { get; set; }
}
}
I understand that this can cause circular request or circular deletion so I inserted a DeleteBehavior.Restrict on Standard:
modelBuilder.Entity<Standard>()
.HasOne(d => d.Forms)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
My complete context class:
namespace Skjemabasen.Data
{
public class SkjemabasenContext : DbContext
{
public SkjemabasenContext(DbContextOptions<SkjemabasenContext> options) :base(options)
{
}
public DbSet<Member> Members { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Standard> Standards { get; set; }
public DbSet<Form> Forms { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Revision> Revisions { get; set; }
public DbSet<Subscription> Subscriptions { get; set; }
public DbSet<MasterDocument> Documents { get; set; }
public IQueryable<Customer> CurrentCustomers
{
get { return Customers.Where(c => c.Inactive == false); }
}
public IQueryable<Customer> InActiveCustomers
{
get { return Customers.Where(c => c.Inactive == true); }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Member>().ToTable("Member");
modelBuilder.Entity<Category>().ToTable("Category");
modelBuilder.Entity<Standard>().ToTable("Standard");
modelBuilder.Entity<Form>().ToTable("Form");
modelBuilder.Entity<Customer>().ToTable("Customer");
modelBuilder.Entity<Revision>().ToTable("Revision");
modelBuilder.Entity<Subscription>().ToTable("Subscription");
modelBuilder.Entity<MasterDocument>().ToTable("Document");
modelBuilder.Entity<Standard>()
.HasOne(d => d.Forms)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
}
}
}
When I try to run the application I get the error:
System.ArgumentException: 'The entity type
'System.Collections.Generic.ICollection`1[Skjemabasen.Models.Document.Form]'
provided for the argument 'clrType' must be a reference type.' Because
all Forms must have a parent Standard and both 'Standard' and 'Form'
inherits from MasterDocument, I understand that ASP.NET Core warns
about circular deletion, but I'm not sure how to achieve this. The
error says something about ICollection of 'Forms' not being a
reference type. Is something missing in 'Standard' related to the
relation between and 'Form'.
Based on https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro I can't figure out what I'm missing here.
I'm assuming you don't actually want to have polymorphic entities by inheriting from MasterDocument. So, from what I see, you want Form and Standard to share the same properties of MasterDocument while MasterDocument being itself an Entity. If that's the case, just abstract away those properties to a base class:
public abstract class MasterBaseDocument
{
public int ID { get; set; }
[Required]
[Display(Name = "EStandard")]
[StringLength(50)]
public string EStandard { get; set; }
[Required]
[Column("Betegnelse")]
[Display(Name = "Betegnelse")]
[StringLength(60)]
public string Betegnelse { get; set; }
[Display(Name = "Kommentar")]
public string Comment { get; set; }
}
public class Form : MasterBaseDocument
{
...
}
public class Standard : MasterBaseDocument
{
...
}
public class MasterDocument : MasterBaseDocument
{
// right now, empty here...
}
That should fix it.
Another approach to your model would be to have a MasterDocument FK on Forms and Standard. That way you don't get the duplicates fields on the tables.
Further improving: Also, keep in mind that you can achieve all those configurations you have using attributes with FluentAPI. This way your classes are keep and decouple from EF stuff. That just adds noise and makes it very hard to read. Should be examples on Fluent API on EF docs as well.
I'm missing something when using the data annotations.
This is my first class
[Table("PriceFeed")]
public class PriceFeed : History
{
public PriceFeed()
{
this.Votes = new List<PriceVote>();
this.History = new List<PriceFeed__History>();
}
[Key]
public long Id { get; set; }
[ForeignKey("Store")]
public long Store_Id { get; set; }
[ForeignKey("Item")]
public long Item_Id { get; set; }
[Required]
public decimal Price { get; set; }
public Store Store { get; set; }
public Item Item { get; set; }
public virtual ICollection<PriceFeed__History> History { get; set; }
}
And this is my second class
[Table("PriceFeed__History")]
public class PriceFeed__History : History
{
[Key]
public long Id { get; set; }
[ForeignKey("PriceFeed")]
public long PriceFeed_Id { get; set; }
[Required]
public decimal Price { get; set; }
public virtual PriceFeed PriceFeed { get; set; }
}
When I run the add-migration, it creates the database correctly but when I try to access PriceFeed.History it gives me an error
{"Message":"An error has occurred.","ExceptionMessage":"A specified Include path is not valid. The EntityType 'Verdinhas.Web.Contexts.PriceFeed' does not declare a navigation property with the name 'PriceFeed__History'."
I always worked with API Fluent and typed by myself the code like
.Entity<Student>()
.HasRequired<Standard>(s => s.Standard)
.WithMany(s => s.Students)
.HasForeignKey(s => s.StdId);
But now I'm using the data annotations and when I generate the migration, it does not create the "withmany" like the above.
What am I doing wrong?
The issue has nothing to do with Data Annotations which seems to be correct in your model.
As mentioned in the comments, the exception is caused by a code that tries to use Include method with string "'PriceFeed__History" - you seem to think that you should specify the related entity types, but in fact you need to specify the navigation property names, which in your case is "History".
I need help creating the relationship in entity framework as everything I have tried gives me errors when trying to add the migration or if I get passed that then I try to update the database and get an error about indexes with the same name.
public class Profile
{
public Profile()
{
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string VersionCreated { get; set; }
public string DiskLocation { get; set; }
public string Name { get; set; }
public DateTime DateTime { get; set; }
public virtual Product Product { get; set; }
public virtual Instance OriginalInstance { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
public class Instance
{
public Instance()
{
TestResults = new HashSet<TestResult>();
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public string UserFriendlyName { get; set; }
public virtual Product Product { get; set; }
public virtual Profile LastKnownProfile { get; set; }
public virtual Computer Computer { get; set; }
public virtual ICollection<TestResult> TestResults { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
The problem with the above classes is that the OrginalInstance property on the Profile class and the LastKnownProfile in the Instance class are supposed to just be foreign keys to those specific tables and they probably won't be the same very often. They can also both possibly be null.
I have tried:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile);
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance);
This gave me an Unable to determine the principal end of an association between the types 'EcuWeb.Data.Entities.Instance' and 'EcuWeb.Data.Entities.Profile'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations. error.
and with:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile).WithOptional();
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance).WithOptional();
The database adds a foreign key reference back to itself.
...that the OrginalInstance property on the Profile class and the
LastKnownProfile in the Instance class are supposed to just be foreign
keys to those specific tables and they probably won't be the same very
often. They can also both possibly be null.
In this case you actually want two one-to-many relationships between Profile and Instance if I don't misunderstand your quote above. It would mean that many Profiles can have the same OriginalInstance and that many Instances can have the same LastKnownProfile. The correct mapping would look like this then:
modelBuilder.Entity<Profile>()
.HasOptional(p => p.OriginalInstance)
.WithMany()
.Map(m => m.MapKey("OriginalInstanceId"));
modelBuilder.Entity<Instance>()
.HasOptional(i => i.LastKnownProfile)
.WithMany()
.Map(m => m.MapKey("LastKnownProfileId"));
The lines with MapKey are optional. Without them EF will create a foreign key with a default name.
Also note that you must use HasOptional (instead of HasRequired) if "both can possibly be null".