LINQ for many-to-many in EF Core 2 - entity-framework

I have searched on google and stackoverflow for a while and yes I have found many articles on the subject but I still can't seem to know what I am doing wrong.
Problem: I do have a "link" table (many-to-many) between three tables:
- check
- car
- gate
My 'link' class looks like follows:
public class CheckCarGate
{
public int CheckId { get; set; }
public Check Check{ get; set; }
public int CarId { get; set; }
public Car Car{ get; set; }
public int GateId { get; set; }
public Gate Gate { get; set; }
}
DbContext:
public virtual DbSet<Car> Cars{ get; set; }
public virtual DbSet<Gate> Gates { get; set; }
public virtual DbSet<Check> Checks{ get; set; }
// The key:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<CheckCarGate>().HasKey(p => new { p.CheckId, p.CarId, p.GateId });
}
I did follow this documentation https://learn.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many when creating models. CheckCarGate is a navigation property in the entity models. E.g.
public class Gate
{
public int GateId { get; set; }
public string Descr { get; set; }
public ICollection<CheckCarGate> CheckCarGates { get; set; }
}
Code first created this table in the database. Now I'm trying to select all Checks with all Gates for one particular CarId.
I tried something like this:
var masterlist = _context.Checks.Where(p => p.CheckCarGate.Any(x => x.CarId == 12));
or:
var masterlist = _context.Checks
.Include(p => p.CheckCarGate)
.ThenInclude(p=>p.Gate)
.Include(p=>p.CheckCarGate)
.ThenInclude(p=>p.Car);
//Edit: I'm sorry for not giving enough information! Bear with me...
Could someone point me to the right direction?
Many thanks in advance!
N.

I'm not sure there is enough information here to answer your question; it appears like each check has its own checkcargate, which doesn't really make sense.
Assuming you had a table of checks and CheckCarGate table seperatatly, you can use where and select to make this query.
var out = CheckCarGateTable.Where(t => t.CarID == targetCarID)
.Select(c => checks.getbyid(c.CheckID))
or, based on your class definition
var out = CheckCarGateTable.Where(t => t.CarID == targetCarID)
.Select(c => c.Check))
First we get all CheckCarGates that refer to our target car, then we transform these into checks using the select call; out should be of type IEnumerable< Check >
and process the list like:
foreach (Check c in out)
{
proc(c);
}
This is the best I can do with the information given.

Your OnModelCreating(...) should have more in it defining the relationship
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<CheckCarGate>().HasKey(p => new { p.CheckId, p.CarId, p.GateId });
builder.Entity<CheckCarGate>().HasOne(c => c.car)
.WithMany(c => c.check)
.HasForeignKey(k => k.carId)
...
}
I'm not sure how you change your query after that, the documentation link you provided didn't have example usage.

I gave up looking for a LINQ solution. (2 days wasted).
Instead I decided to use raw sql and store the data in a new object.
Credits to: https://stackoverflow.com/users/355714/pius and his solution: https://stackoverflow.com/a/46013305/848642

Related

One-to-Many EF .NET Core Relationship Not Working

I have a .net core api application which includes EF to retrieve data. I have set up a data context and I can map tables from the db fine. When I try and set up a relationship though I am always getting a null back for the nested object.
I have an 'Opportunity' class which contains an ICollection of 'Notes'
public class Opportunity
{
public int Id { get; set; }
public string Name { get; set; }
...
public decimal FinalDealProfit { get; set; }
public ICollection<CRMNote> CRMNotes { get; set; }
}
and a Note class that references the opportunity:
public class CRMNote
{
public int Id { get; set; }
public int OpportunityId { get; set; }
public string Note { get; set; }
public string User { get; set; }
public DateTime DateTime { get; set; }
public string FilePath { get; set; }
public Opportunity Opportunity { get; set; }
}
In my context class have the following set up:
modelBuilder.Entity<Opportunity>(entity =>
{
entity.ToTable("CRM_Opportunity");
entity.HasMany<CRMNote>(n => n.CRMNotes)
.WithOne(t => t.Opportunity)
.HasForeignKey(k => k.OpportunityId);
});
and I have also been mapping the Note class:
modelBuilder.Entity<CRMNote>(entity =>
{
entity.ToTable("CRM_Note");
//entity.HasOne<Opportunity>(t => t.Opportunity)
// .WithMany(p => p.CRMNotes)
// .HasForeignKey(k => k.OpportunityId);
});
as you can see I have been playing around with how to connect the entities together.
Whenever I retrieve the opportunity though the notes array is always null. I have tried putting an empty constructor on the Opportunity class:
public Opportunity()
{
CRMNotes = new List<CRMNote>();
}
but this just means I get an empty array rather than a null.
I can't see what I have missed. I have checked the docs for it:
https://www.entityframeworktutorial.net/efcore/one-to-many-conventions-entity-framework-core.aspx
but clearly I have missed something. Any help greatly appreciated as this should be an easy task but something is clearly eluding me.
There are three common O/RM patterns used to load related data
Eager loading,
Explicit loading
and
Lazy loading
For example, in eager loading you can use:
var opportunities=context.opportunities.Include(opportunity=>opportunity.CRMNotes).ToList()

Include ignored column in EFCore

I have a scenario where I'd wish to ignore a column (so that it get's not joined on every include etc.) but include it explicit if I need to. Selecting all the other columns needed on every join is not a practible solution for me. I store a user's profile picture in the DB and the owner, midifier etc. gets joined on almost every object, making some queries pretty slow.
Is there a best practice for this scenario?
Edit: I thought about creating a 2nd, virtual Model mapped to the same table containing only this one column but two models mapped to the same table seam to make some problems.
As you have added in you Edit section. That is how I solved a similar problem.
This works for me and it runs in production today.
And I can easy include the the Body filed when I need to.
public class Content
{
public Guid Id { get; set; }
public string Heading { get; set; }
public string Preamble { get; set; }
public virtual ContentBody Body { get; set; }
}
public class ContentBody
{
public string Body { get; set; }
public Guid Id { get; set; }
}
This is where I do the Model mapping:
public class ContentMap : IEntityTypeConfiguration<Content>
{
public void Configure(EntityTypeBuilder<Content> builder)
{
// Primary Key
builder.ToTable("Content");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id).ValueGeneratedOnAdd();
builder.Property(p => p.Heading).HasColumnName("Heading");
builder.Property(p => p.Preamble).HasColumnName("Preamble");
// Relationships
builder.HasOne(t => t.Body)
.WithOne().HasForeignKey<ContentBody>(t => t.Id);
}
}
public class ContentBodyMap : IEntityTypeConfiguration<ContentBody>
{
public void Configure(EntityTypeBuilder<ContentBody> builder)
{
// Primary Key
builder.ToTable("Content");
builder.HasKey(e => e.Id);
builder.Property(p => p.Body).HasColumnName("Body");
}
}

Entity Framework Core: InvalidOperationException

I'm trying to setup a method that returns all presentations that are not overlapping with presentations you have signed up for. However, when trying to implement this I came across an error I can't seem to fix, I looked around and haven't been able to find any similar issues. Am I missing something obvious?
This is the Error:
InvalidOperationException: variable 't0' of type
'Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor+TransparentIdentifier`2[NameSpace.Models.Presentation,Microsoft.EntityFrameworkCore.Storage.ValueBuffer]'
referenced from scope '', but it is not defined
These are my models.
public class ApplicationUser : IdentityUser
{
public List<Presentation> MyPresentations { get; set; }
public List<PresentationUser> RegisteredPresentations { get; set; }
}
public class Presentation
{
public int PresentationId { get; set; }
public string HostId { get; set; }
public ApplicationUser Host { get; set; }
public List<PresentationUser> Attendees { get; set; }
public int TimeId { get; set; }
public PresentationTime Time { get; set; }
}
public class PresentationUser
{
public int PresentationId { get; set; }
public Presentation Presentation { get; set; }
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
public class PresentationTime
{
public int PresentationTimeId { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
This is the method I can't get to work
private async Task<IQueryable<Presentation>> GetAvailablePresentations()
{
User user = await context.Users
.Include(u => u.RegisteredPresentations)
.ThenInclude(eu => eu.Presentation.Host)
.FirstOrDefaultAsync(u => u.Id == userManager.GetUserId(User));
var Presentations = context.Presentations
.Include(e => e.Host)
.Include(e => e.Time)
.Include(e => e.Attendees)
.ThenInclude(e => e.ApplicationUser)
// Filter Out Conditions
.Where(e => e.Attendees.All(u => u.ApplicationUserId != user.Id)) // Cannot see Presentations they are attending.
.Where(e => e.HostId != user.Id); // Cannot see their own Presentation
var debug = user.RegisteredPresentations.Select(ex => ex.Presentation).ToList();
// This section makes it so that users can't sign up for more that one Presentation per timeslot.
// Error Occurs Here
Presentations = Presentations.Where(e => debug.All(ex =>
ex.Time.EndTime < e.Time.StartTime || e.Time.EndTime < ex.Time.StartTime));
// This also does not work
// Presentations = Presentations.Where(e => debug.All(ex => ex.Time.StartTime != e.Time.StartTime));
return Presentations;
}
If anyone can help me fix this it would be huge help.
Note: I stripped a lot of other logic to help isolate this issue, so I may have a couple unnecessary .Include() in this code.
Presentations is not a list, it's still a IQueryable - a not-yet-executed query to DB. Applying Where you instruct EF to apply additional WHERE in SQL.
But debug is a list of objects in memory (.ToList()). How you think EF will transfer them back to DB?
If you want all filtering be applied in DB - you should change debug to list of something "simple" (list of ids?) - then EF will be able to pass this list back to DB.
Alternatively, you should read all suitable Presentations into memory (call .ToList()) and apply last filtering in memory. You may calculate min(StartTime) and max(EndTime) from debug and apply this two simple values to Presentations query (you will receive less unnecessary items) then read to memory and apply "strong" filtering in memory.

EF7 Core Many to Many Reference object not populated

I am having difficulty getting EF7 to populate the objects referenced in a many to many join. I have followed the docs at https://docs.efproject.net/en/latest/modeling/relationships.html, but the object is still null. From what I can tell you don't have to do anything specific to get EF to populate them. I copied the sample code from the docs page as follows:
public class MyContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public string Title { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
I had to add a constructor to get it to run. I also added a Title field to the Tag class so it has more than just a key. Once I populated some data in the tables, I run the following code to retrieve from the database:
var results = _context.Posts.Include(s => s.PostTags).ToList();
When I examine the results in the debugger, the Tag objects are null even though the keys to obtain them are present. Notice that the Post objects are populated. It is always the second column of the two column key that is not joined:
This is the SQL generated by EF7:
SELECT [s].[PostId], [s].[Content], [s].[Title]
FROM [Posts] AS [s]
ORDER BY [s].[PostId]
SELECT [p].[PostId], [p].[TagId]
FROM [PostTag] AS [p]
WHERE EXISTS (
SELECT 1
FROM [Posts] AS [s]
WHERE [p].[PostId] = [s].[PostId])
ORDER BY [p].[PostId]
It doesn't appear to be fetching the Tag object at all. What am I missing here?
For completeness, I have included the sample data:
Thanks to #SOfanatic for pointing me in the right direction. I am not sure why EF doesn't automatically load the second reference class, but it doesn't. The following code will retrieve the Tag object (as well as the Post object even though we don't explicitly load it).
var results = _context.Posts.Include(s => s.PostTags).ThenInclude(t => t.Tag).ToList();
You are doing a many-to-many with what is known as Payload class. The PostTag class is technically not needed for EF to create a many-to-many relationship.
You are going to have to do something like this:
var results = _context.Posts.Include(s => s.PostTags.Select(pt => pt.Tag).ToList();
Right now your linq is loading the related entities, which are just the id's so it's of no use.
If your PostTags class is not going to have any other fields/properties you should look into creating a many-to-many without the payload

How can I have a Foo with exactly one Bar in EF 5 Code First

I'm a gibbering wreck trying to get EF code first to let me do something that I could do in 2 minutes in SQL. Had I not already spent 5 days trying to get it to work, I'd just code up my database in DDL and use ADO.NET. But I digress...
I want to have 2 tables, where each record in A has a corresponding record in B. They're both part of the same object; they need to be in separate tables for reasons I won't go into (but they really do, so don't go there). If I were designing it from the database end, I'd simply have an FK relationship from B to A. Job done.
In EF Code First, I've tried using both the shared primary key method and the one-to-one foreign key association method, and neither work for me. I've also tried 100 or so combinations of all the variants I can think of, and I'm no further forward.
As I said, all I want is there to be a navigable relationship from A to B (and back would be nice, but I've read that's not possible), and for that relationship to be lazy-loaded, so that I can say a.b and have access to the fields of b.
I can't possibly enumerate all the things I've tried, so let me just give an example of what nearly works:
class Foo
{
public int Id { get; set; }
public string FooProperty { get; set; }
public virtual Bar Bar { get; set; }
}
class Bar
{
public int Id { get; set; }
public string BarProperty { get; set; }
}
Note that there's no back-reference from Bar to Foo, since (a) SQL Server would complain about multiple cascade delete paths, and (b) EF would complain about not knowing which side is the principal end of the association. So... fine - I can live without it.
What this gets me in the database is a Foos table with Id, FooProperty and Bar_Id fields, and a Bars table with Id and BarProperty fields. That's pretty close to they way I'd model it in SQL, although I'd probably put the FK field in Bar rather than Foo. But since it's 1:1 it doesn't really matter, I guess.
The reason I say that this nearly works is that if I add a Bar and associated Foo and then load them back in, the Bar property of the Foo object is null.
using (var dbContext = new MyDbContext())
{
var foo = dbContext.Foos.Create();
foo.FooProperty = "Hello";
dbContext.Foos.Add(foo);
var bar = dbContext.Bars.Create();
bar.BarProperty = "world";
foo.Bar = bar;
dbContext.SaveChanges();
}
using (var dbContext = new MyDbContext())
{
foreach (var foo in dbContext.Foos)
Console.WriteLine(foo.Bar.Id); // BOOM! foo.Bar is null
}
I would normally expect the evaluation of foo.Bar to trigger lazy-loading of the Bar object, but it doesn't - that property remains null.
How can I fix it?
Thsi should work:
Context
public class FoobarCtx : DbContext
{
public DbSet<Bar> Bars { get; set; }
public DbSet<Foo> Foos { get; set; }
public FoobarCtx()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Bar>()
.HasRequired(f => f.Foo)
.WithRequiredDependent(b => b.Bar)
.Map(x => x.MapKey("FooId"))
.WillCascadeOnDelete(true);
}
}
Entities
public class Foo
{
public int Id { get; set; }
public string Foo1 { get; set; }
public string Foo2 { get; set; }
public virtual Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Bar1 { get; set; }
public string Bar2 { get; set; }
public virtual Foo Foo { get; set; }
}
I tested it in EF 4.3, but I think it should work also in EF5. The Key is the OnModelCreating method. There you can define either one or the other as the principal/descendant and get rid of the Microsoft SQL restriction.
For more information see this blog-post. For more information about the model builder (fluent API), go here.
To enable lazy loading, use the DbContext.FooSet.Create() method. Example here.
As a reminder to myself, if nothing else...
LueTm arrived at a solution that produced the table structure I originally had in mind (with a FooId column in the Bar table), and independent PKs on the two tables. It was still not possible to access the Foo.Bar property without first loading it using dbContext.Foos.Include(f => f.Bar).
I was also able to get things to work pretty well with a shared primary key (both tables have a PK, but only the one in Foos is an identity (auto-increment) column, and there's an FK relationship from the Id in Bars to the Id in Foos.
To do this, I had a Bar property in the Foo class, and a Foo property in the Bar class (so 2-way navigation works), and put the following in my OnModelCreating.
modelBuilder.Entity<Bar>()
.HasRequired(x => x.Foo)
.WithRequiredDependent(x => x.Bar)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Foo>()
.HasRequired(x => x.Bar)
.WithRequiredPrincipal(x => x.Foo)
.WillCascadeOnDelete(true);
(I'm not sure that the second call here actually does anything).
But again, you still need the Include() call in order to be able to access foo.Bar.
I just went through this myself. I have a FoodEntry, with a Food, and the Food has a FoodGroup.
public class FoodEntry
{
public int Id { get; set; }
public string User { get; set; }
public Food Food { get; set; }
public decimal PortionSize { get; set; }
public Portion Portion { get; set; }
public int Calories { get; set; }
public Meal Meal { get; set; }
public DateTime? TimeAte { get; set; }
public DateTime EntryDate { get; set; }
}
public class Food
{
public int Id { get; set; }
public string Name { get; set; }
public FoodGroup FoodGroup { get; set; }
}
public class FoodGroup
{
public int Id { get; set; }
public string Name { get; set; }
}
I'm using code first and my POCO classes are defined just as yours are. I do not have the properties marked virtual.
In any event, the database generates as I would expect - with foreign keys as you described.
But this query resulted in just getting the FoodEntry collection with nulls in the Food, Portion and Meal properties:
var foodEntries = db.FoodEntries
.Where(e => e.User == User.Identity.Name).ToList();
I changed the query to this, and the entire graph loaded for my collection:
var foodEntries = db.FoodEntries
.Include( p => p.Food)
.Include(p => p.Food.FoodGroup)
.Include(p => p.Portion)
.Include( p => p.Meal)
.Where(e => e.User == User.Identity.Name).ToList()
;
For a 1:1 relationship in addition to a 1:many relationship (Foo has several Bars and also a CurrentBar):
modelBuilder.Entity<Foo>()
.HasOptional(f => f.CurrentBar)
.WithMany()
.HasForeignKey(f => f.CurrentBarId);
modelBuilder.Entity<Bar>()
.HasRequired(b => b.Foo)
.WithMany(f => f.Bars)
.HasForeignKey(b => b.FooId);