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

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; }
}

Related

How to create multiple Many-to-Many relationships using the same join table [EF7/Core]

Is it possible to create 2 M:M relationships using the same join table?
I have the following situation and am receiving the exception:
Unhandled Exception: System.InvalidOperationException: Cannot create a relationship between 'ApplicationUser.ExpertTags' and 'UserTag.User', because there already is a relationship between 'ApplicationUser.StudyTags' and 'UserTag.User'. Navigation properties can only participate in a single relationship
In Tag:
public class Tag {
public Tag() {
Users = new List<UserTag>();
}
public int TagId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<UserTag> Users { get; set; }
In ApplicationUser:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
StudyTags = new HashSet<UserTag>();
ExpertTags = new HashSet<UserTag>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public ICollection<UserTag> StudyTags { get; set; }
public ICollection<UserTag> ExpertTags { get; set; }
}
In UserTag (CLR join):
public class UserTag
{
public string UserId { get; set; }
public ApplicationUser User { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
In ApplicationDbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserTag>()
.HasKey(x => new { x.UserId, x.TagId });
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.StudyTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.ExpertTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.Tag)
.WithMany(t => t.Users)
.HasForeignKey(ut => ut.TagId);
}
Do I need to create separate CLR classes? Something like UserStudyTag and UserExpertTag?
Thanks!
Step down to SQL DB. You want to have table UserTag with one UserId field. How EF should guess, which records in this table are related to StudyTags and which to ExpertTags collections?
You should duplicate something.
Either split UserTag to two tables (UserStudyTag and UserExpertTag), or make two UserId fields in UserTag, say ExpertUserId and StudyUserId. Both nullable, with only one having some value in each record.

How to filter child collections Entity Framework

WEB API
Model::
public class Empresa
{
[Key]
public string CDEmpresa { get; set; }
public string NomeFantasia { get; set; }
[IgnoreDataMember]
public string Nome{ get; set; }
public List<EmpresaRamoAtividade> EmpresaRamoAtividade { get; set; }
}
public class EmpresaRamoAtividade
{
[Key]
public int CTRamoAtividade { get; set; }
[IgnoreDataMember]
public string CDEmpresa { get; set; }
public List<RamoAtividade> RamoAtividade { get; set; }
}
public class RamoAtividade
{
[IgnoreDataMember]
[Key]
public int CTRamoAtividadeTraducao { get; set; }
public int CTRamoAtividade { get; set; }
public string Atividade { get; set; }
public int Idioma { get; set; }
}
Controller::
Working ok:::
{
return db.Empresas
.Where(a => a.Associado.IsAssociado)
.Include(empresaRamo => empresaRamo.EmpresaRamoAtividade)
.Include(ramo => ramo.EmpresaRamoAtividade.Select(atividade => atividade.RamoAtividade));
}
Not working, I have to filter by "idioma" (language):::
{
return db.Empresas
.Where(a => a.Associado.IsAssociado)
.Include(empresaRamo => empresaRamo.EmpresaRamoAtividade)
.Include(ramo => ramo.EmpresaRamoAtividade.Select(atividade => atividade.RamoAtividade.Where(idioma => idioma.Idioma == 1)));
}
Error:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties. Parameter name: path
Can't I filter 3 level collection child?
Thank you..
You cannot filter using Include methods. It only supports select.
Disclaimer: I'm the owner of the project Entity Framework Plus (EF+)
EF+ Query IncludeFilter allow you to easily filter related entities:
{
return db.Empresas
.Where(a => a.Associado.IsAssociado)
.IncludeFilter(empresaRamo => empresaRamo.EmpresaRamoAtividade)
.IncludeFilter(ramo => ramo.EmpresaRamoAtividade.Select(atividade => atividade.RamoAtividade.Where(idioma => idioma.Idioma == 1)));
}
You can find the documentation here

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 6: one-to-many doesn't update foreign key when inserting and removing in the same operation

I have these classes in my project (the names in the code are in Portuguese, if necessary I can translate) :
public class EntityWithGuid : IEntityWithId<string>
{
protected EntityWithGuid()
{
this.Id = Guid.NewGuid().ToString("N").ToLower();
}
[Key]
public string Id { get; set; }
}
public class Servico : EntityWithGuid
{
public DateTime? Data { get; set; }
public string Descricao { get; set; }
public string Cliente_Id { get; set; }
[ForeignKey("Cliente_Id")]
public Cliente Cliente { get; set; }
[Required]
public ICollection<ServicoImagem> Imagens { get; set; }
[Required]
public ICollection<Tag> Tags { get; set; }
}
public class ServicoImagem : EntityWithGuid
{
[Required]
public string Nome { get; set; }
public string Servico_Id { get; set; }
[Required]
public Servico Servico { get; set; }
}
public class Tag : EntityWithGuid
{
[Required]
public string Nome { get; set; }
public string Fonetica { get; set; }
public ICollection<Servico> Servicos { get; set; }
}
And this is the Context configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ServicoConfiguration());
}
internal class ServicoConfiguration : EntityTypeConfiguration<Servico>
{
internal ServicoConfiguration()
{
this.HasMany(s => s.Tags)
.WithMany(t => t.Servicos)
.Map(mf =>
{
mf.MapLeftKey("Servico_Id");
mf.MapRightKey("Tag_Id");
mf.ToTable("ServicoTag");
});
this.HasMany(s => s.Imagens)
.WithRequired(i => i.Servico)
.HasForeignKey(f => f.Servico_Id);
}
}
After load a Servico entity the update method can do any operation with the Servico.Tags property (add and remove items), mark as modified and finally call Context.SaveChanges(). Everything works perfectly.
var servico = Context.Set<Servico>()
.Include(x => x.Cliente)
.Include(x => x.Tags)
.Include(x => x.Imagens)
.FirstOrDefault(x => x.Id == id);
...
// Remove tags
servico.Tags = servico.Tags.Except(oldTags).ToList();
// Add new tags
servico.Tags = servico.Tags.Concat(newTags).ToList();
...
Context.Entry(servico).State = EntityState.Modified;
Context.SaveChanges();
If I do the same thing with the Images property is only possible to make one type of operation at a time, add OR remove. If added and removed at the same time, the added item does not receive the value of the foreign key and error occurs in Context.SaveChanges() but if I do only one type of operation, it works perfectly.
The only solution I found was to make a loop to mark the item as deleted.
// Mark image as deleted
foreach (var imagem in imagensParaDeletar)
{
Context.Entry(imagem).State = System.Data.Entity.EntityState.Deleted;
}
I would like to understand why the problem ONLY occurs in this type of relationship and ONLY when I need to do both type of operation on the same property.

Why can't I do ToList()?

I build a model as below. The relationship between Recycler and Account is 1:1.
public class MyContext : DbContext
{
public DbSet<Quoter> Quoters { get; set; }
public DbSet<Account> Accounts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>()
.HasRequired(a => a.RecyclerRef)
.WithRequiredDependent(r => r.AccountRef);
}
}
public class Quoter
{
public int QuoterId { get; set; }
public string Text { get; set; }
}
public class Recycler : Quoter
{
public string Description { get; set; }
public virtual Account AccountRef { get; set; }
}
public class Account
{
public int AccountId { get; set; }
public Recycler RecyclerRef { get; set; }
}
But, I get exceptions when I do the either of these queries:
var query1 = context.Quoters
.OfType<Recycler>()
.Include(r => r.AccountRef)
.Where(r => r.QuoterId == 1)
.ToList();
var query2 = context.Set<Recycler>()
.Include(r => r.AccountRef)
.Where(r => r.QuoterId == 1)
.ToList();
Exception shows that ResultType is “Transient.reference[POCOFirst.Quoter]”,but recommanded is “Transient.reference[POCOFirst.Recycler]”
If I remove the ToList(), it works well. But I need a list as the return value of method.
Why can't I do ToList()? Thanks
It looks like you have stumble upon this bug in EF. Another reference to the bug.
Workaround would be to remove the Include method.