How do you select the parent(s) based on child properties? - ef-core-3.1

My Parent (movies) have child collections on them (actors), how do I select all the movies that have Actor A and Actor B or even Actor C etc...? I can do manage a simple query to find all Actor A movies etc...but doing (n) Actors seems unclear. My model is below:
public class Movie
{
public int MovieID { get; set; }
public string MovieTitle { get; set; }
public ICollection<MovieActor> MovieActors { get; set; }
}
public class MovieActor
{
public int MovieID { get; set; }
public Movie Movie { get; set; }
public int ActorID { get; set; }
public Actor Actor { get; set; }
}
public class Actor
{
public int ActorID { get; set; }
[DisplayName("First Name")]
public string ActorFirstname { get; set; }
[DisplayName("Last Name")]
public string ActorLastname { get; set; }
public ICollection<MovieActor> MovieActors { get; set; }
}
My dbContext config for that object looks like this:
public void Configure(EntityTypeBuilder<MovieActor> builder)
{
builder.HasKey(ma => new { ma.MovieID, ma.ActorID });
builder.HasOne(ma => ma.Movie)
.WithMany(m => m.MovieActors)
.HasForeignKey(ma => ma.MovieID);
builder.HasOne(ma => ma.Actor)
.WithMany(a => a.MovieActors)
.HasForeignKey(ma => ma.ActorID);
}

Assuming you want movies that have the specified actors in the same movie, you can do this.
private List<Movie> GetMovies(params int[] actorIds)
{
IQueryable<Movie> movies = dc.Movies;
foreach (int actorId in actorIds)
{
movies = movies.Where(w => w.MovieActors.Any(a => a.ActorId == actorId));
}
return movies.ToList();
}
or if you're wanting all movies that any of those actors have been in
return dc
.MovieActors
.Where(w => actorIds.Contains(w.ActorId))
.Select(s => s.Movie)
.Distinct()
.ToList();
There are many ways to access the data, not saying these are the best performance wise but they do give you options.

Related

EF Select Grandparent for Grandchildren

Class Brands, Models, Generations, Modification
How to get all Brands for ModificationName == "ACK"
public class Brand
{
public Brand()
{
this.Models = new HashSet<Model>();
}
public int BrandId { get; set; }
public virtual ICollection<Model> Models { get; set; }
}
public class Model
{
public Model()
{
this.Generations = new HashSet<Generation>();
}
public virtual ICollection<Generation> Generations { get; set; }
public int? BrandId { get; set; }
public virtual Brand Brand { get; set; }
}
public class Generation
{
public Generation()
{
this.Modifications = new HashSet<Modification>();
}
public int GenerationId { get; set; }
public virtual ICollection<Modification> Modifications { get; set; }
public int? ModelId { get; set; }
public virtual Model Model { get; set; }
}
public class Modification
{
public int ModificationId { get; set; }
public string ModificationName { get; set; }
public int? GenerationId { get; set; }
public virtual Generation Generation { get; set; }
}
Another approach could be to use the Join operator. For example>
from currentBrand in Context.Brand
join currentModel in context.Model on currentBrand.Id equals currentModel.BrandId
join currentGeneration in context.Generations on currentGeneration.ModelId equals currentModel.id
join currentModeification in context.Modification on currentModeification.GenerationId equals currentGeneration .Id
Where currentModeification.ModificationName == "ACK"
Trick here is to use SelectMany method.
var query =
from b in ctx.Brands
where b.Models
.SelectMany(m => m.Generations.SelectMany(g => g.Modifications))
.Where(m => m.ModificationName == "ACK").Any()
select b;
UPDATE with includes
It will work only with EF Core 5
var query =
from b in ctx.Brands
.Include(b => b.Models)
.ThenInclude(g => g.Generations)
.ThenInclude(m => m.Modifications.Where(x => x.ModificationName == "ACK"))
where b.Models
.SelectMany(m => m.Generations.SelectMany(g => g.Modifications))
.Where(m => m.ModificationName == "ACK").Any()
select b;

Json response does not contain all the navigation properties EntityFramework Core and ASP .NETCore Web API

I have migrated from Entity Framework 6 to EF Core and also Web Api .net framework to .net core.
I have many to many relationship that I have set up as follows
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var instrumentsToPlaces = modelBuilder.Entity<InstrumentPlace>();
instrumentsToPlaces.ToTable("InstrumentsToPlaces");
instrumentsToPlaces.HasKey(x => new { x.PlaceId, x.InstrumentId });
instrumentsToPlaces.HasOne(i => i.Instrument)
.WithMany(p => p.InstrumentsPlaces)
.HasForeignKey(ip => ip.InstrumentId);
instrumentsToPlaces.HasOne(p => p.Place)
.WithMany(i => i.InstrumentsPlaces)
.HasForeignKey(ip => ip.PlaceId);
var instrumentsToStyle = modelBuilder.Entity<InstrumentStyle>();
instrumentsToStyle.ToTable("InstrumentsToStyles");
instrumentsToStyle.HasKey(x => new { x.StyleId, x.InstrumentId });
instrumentsToStyle.HasOne(i => i.Instrument)
.WithMany(s => s.InstrumentStyles)
.HasForeignKey(si => si.InstrumentId);
instrumentsToStyle.HasOne(s => s.Style)
.WithMany(i => i.InstrumentStyles)
.HasForeignKey(si => si.StyleId);
}
I have included the navigation properties in the repository method as follows
public Instrument GetInstrumentByName(string name)
{
using (var starsAndCatzDbContext = new StarsAndCatzDbContext())
{
var instrument = _starsAndCatzDbContext.Instruments
.Include(a=>a.InstrumentsPlaces)
.ThenInclude(a=>a.Place)
.Include(a=>a.InstrumentStyles)
.ThenInclude(a=>a.Style)
.FirstOrDefault(i => i.Name == name);
return instrument;
}
}
Here are the classes
public class Instrument {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<InstrumentPlace> InstrumentsPlaces { get; set; }
public virtual ICollection<InstrumentStyle> InstrumentStyles { get; set; }
}
public class InstrumentPlace
{
public int InstrumentId { get; set; }
public Instrument Instrument { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
}
public class InstrumentStyle
{
public int InstrumentId { get; set; }
public Instrument Instrument { get; set; }
public int StyleId { get; set; }
public Style Style { get; set; }
}
public class Style {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<InstrumentStyle> InstrumentStyles { get; set; }
}
public class Place {
public int Id { get; set; }
public string Name { get; set; }
public string Division { get; set; }
public int Tier { get; set; }
public string State { get; set; }
public string Postcode { get; set; }
public float? Latitude { get; set; }
public float? Longitude { get; set; }
public virtual ICollection<InstrumentPlace> InstrumentsPlaces { get; set; }
}
The WebAPI method to be called is
[HttpGet("GetInstrumentByName/{suburb}/{instrument}"), Produces("application/json")]
public Instrument GetInstrumentByName(string suburb, string instrument)
{
try
{
var result = _instrumentRepository.GetInstrumentByName(instrument);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new Instrument();
}
}
When I send the request to "/api/instruments/west-end/guitar" I get the expected result when I place a breakpoint before sending the response as follows
As you notice, the Navigation properties are loaded (when I expand the collections I can see all the properties being loaded as well).
However the json response I receive is the following
Any suggestions or am I missing something here?
Thank you all in advanced
Thanks #H. Herzl for giving me a hint.
The solution was found in this other question
services.AddMvc().AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
https://stackoverflow.com/a/40501464/1513346

EF6 Ignoring related data

Scenario
public class Product : Entity, IAggregateRoot
{
public string Name { get; set; }
public string Dimension { get; set; }
public decimal Volume { get; set; }
public bool Featured { get; set; }
public Farm Farm { get; set; }
public int FarmId { get; set; }
/// <summary>
/// Sell Price
/// </summary>
public decimal BidPrice { get; set; }
public int QuantityAvaliable { get; set; }
public ICollection<Image> Images { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class Category : Entity, IAggregateRoot
{
public string Title { get; set; }
public string CategoryImage { get; set; }
public Category Parent { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
Relationship setup
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Created).HasColumnType("DateTime");
Property(x => x.Modified).HasColumnType("DateTime");
Property(x => x.BidPrice).HasColumnType("Decimal");
#region RELATIONSHIP
//BelongsTo
HasRequired(x => x.Farm);
HasRequired(x => x.Category);
HasMany(x => x.Images);
#endregion
}
So I have this two model where I need to bring the data from Product model with Category information
I have checked my database, the data is consistent, the Product record have the FK for the Category record.
but when I try to get Product Data using EF6, the category information doesnt come, I get a null object.
Because of = () =>
{
_product = _repository.Find(p => p.Id == 1, p => p.Category);
};
It should_not_be_bull = () =>
_product.Category.ShouldNotBeNull();
the response from data base is for Category is null. but the record is there.
I had it working properly before. for some random magic reason it just stop working.
THE FIND method
public virtual TEntity Find(Expression<Func<TEntity, bool>> predicate = null, params Expression<Func<TEntity, object>>[] includes)
{
var set = CreateIncludedSet(includes);
return (predicate == null) ?
set.FirstOrDefault() :
set.FirstOrDefault(predicate);
}
the CreateIncludeSet
private IDbSet<TEntity> CreateIncludedSet(IEnumerable<Expression<Func<TEntity, object>>> includes)
{
var set = CreateSet();
if (includes != null)
{
foreach (var include in includes)
{
set.Include(include);
}
}
return set;
}
the CreateSet method
private IDbSet<TEntity> CreateSet()
{
return Context.CreateSet<TEntity>();
}
MY DbContext implementation is here
https://github.com/RobsonKarls/FreedomWebApi/blob/dev/Source/Freedom.Infrastructure.DataAccess/Factories/FreedomDbContext.cs
all project is there too for further analisys
any help is valuable.
Thank you
The problem in your code is in this line in CreateIncludedSet method:
set.Include(include);
Yes, you include the data but you do not change you set. You should change it to something like:
set = set.Include(include);
Your code is a bit unclear, but try something like this....
_product = _repository.Include(p => p.Category).SingleOrDefault(x => x.Id == 1);
also see...
https://stackoverflow.com/a/7348694/6200410

Entity Framework Core (EF 7) many-to-many results always null

I have followed the instructions for the workaround for many-to-many described in Issue #1368 and the Docs Here... but when I try to navigate, it always returns null.
My Models:
public class Organization
{
public Guid OrganizationID { get; set; }
//...
public ICollection<OrganizationSubscriptionPlan> OrganizationSubscriptionPlans { get; set; }
}
public class SubscriptionPlan
{
public int SubscriptionPlanID { get; set; }
//...
public ICollection<OrganizationSubscriptionPlan> OrganizationSubscriptionPlans { get; set; }
public class OrganizationSubscriptionPlan
{
[ForeignKey("Organization")]
public Guid OrganizationID { get; set; }
public Organization Organization { get; set; }
[ForeignKey("SubscriptionPlan")]
public int SubscriptionPlanID { get; set; }
public SubscriptionPlan SubscriptionPlan { get; set; }
}
ApplicationDbContext:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<OrganizationSubscriptionPlan>().HasKey(x => new { x.OrganizationID, x.SubscriptionPlanID });
builder.Entity<OrganizationSubscriptionPlan>().HasOne(x => x.Organization).WithMany(x => x.OrganizationSubscriptionPlans).HasForeignKey(x => x.OrganizationID);
builder.Entity<OrganizationSubscriptionPlan>().HasOne(x => x.SubscriptionPlan).WithMany(x => x.OrganizationSubscriptionPlans).HasForeignKey(x => x.SubscriptionPlanID);
}
And my Query:
var organizations = _context.Organizations
.Include(o => o.OrganizationSubscriptionPlans);
foreach (var organization in organizations)
{
//....
var subscriptions = organization.OrganizationSubscriptionPlans
.Select(s => s.SubscriptionPlan);
// ^^^^^^^^^^^ why is subscriptions always null?
}
The "organizations" query returns the results as expected, including the list of OrganizationSubscriptionPlans within each one, but when I try to navigate to them in the foreach loop the "subscriptions" query returns null every time. What am I doing wrong?
Turns out it's a Lazy Loading issue. You have to "Include" the joining entity and then "ThenInclude" the other entity.
var organizations = _context.Organizations
.Include(o => o.OrganizationSubscriptionPlans)
.ThenInclude(s => s.SubscriptionPlan);
ForeignKey attr is to decorate reference properties to indicate them what primitive property hold the FK value.
public class OrganizationSubscriptionPlan
{
public Guid OrganizationID { get; set; }
[ForeignKey("OrganizationID")]
public Organization Organization { get; set; }
public int SubscriptionPlanID { get; set; }
[ForeignKey("SubscriptionPlanID")]
public SubscriptionPlan SubscriptionPlan { get; set; }
}

Select child record in relation two tables

What is the best solutions to retrieve records from child table in relation?
I cannot include the solution file in this question.
Model
[Table("Tbl_DefaultValue")]
public class DefaultValue
{
[Key]
public int DefaultValue_ID { get; set; }
public string DefaultVal_Name { get; set; }
public virtual ICollection<DefaultValue_Det> DefaultValue_Det { get; set; }
}
[Table("Tbl_DefaultValue_Det")]
public class DefaultValue_Det
{
[Key]
public int DefaultValue_Det_ID { get; set; }
public int DefaultValue_ID { get; set; }
public string DefaultValue_Value { get; set; }
public virtual DefaultValue DefaultValue { get; set; }
public virtual ICollection<Car> Cars { get; set; }
}
Controller
driverdt.TypeList =
new SelectList(db.DefaultValue_Det
.Where(a => a.DefaultValue_ID == db.DefaultValue
.Where(d => d.DefaultVal_Name == "marid")
.Max(b=>b.DefaultValue_ID)), "DefaultValue_Det_ID", "DefaultValue_Value");
return View( driverdt);
You can pre-populate collection DefaultValue_Det using FetchMany:
driverdt.TypeList =
new SelectList(db.DefaultValue_Det
.Where(a => a.DefaultValue_ID == db.DefaultValue
.Where(d => d.DefaultVal_Name == "marid")
.Max(b=>b.DefaultValue_ID))
.FetchMany(x => x.DefaultValue_Det)
, "DefaultValue_Det_ID", "DefaultValue_Value");