Loading of references in EF7 - asp.net-core-mvc

I'm having two classes - author and blogpost:
public class Author
{
public Author()
{
Blogposts = new HashSet<Blogpost>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Blogpost> Blogposts { get; set; }
}
and
public class Blogpost
{
public Blogpost()
{
}
// Properties
public int Id { get; set; }
public string Text { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
Using EF7 (beta4), I'm connecting them the following way:
public partial class MyDbContext : DbContext
{
public virtual DbSet<Author> Author { get; set; }
public virtual DbSet<Blogpost> Blogpost { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>(entity =>
{
entity.Property(e => e.Id)
.ForSqlServer().UseIdentity();
});
modelBuilder.Entity<Blogpost>(entity =>
{
entity.Property(e => e.Id)
.ForSqlServer().UseIdentity();
});
modelBuilder.Entity<Blogpost>(entity =>
{
entity.Reference<Author>(d => d.Author).InverseCollection(p => p.Blogposts).ForeignKey(d => d.AuthorId);
});
}
}
When I access a blogpost Db.Blogpost.First(x => x.Id == id) I retrieve the Blogpost object - however, the .Author property is null. Also, when retrieving any Author object, it's .Blogposts collection is empty.
I understand the EF7 has neither implemented eager-loading nor lazy-loading yet. But how would I then retrieve/assign any objects referenced via foreign key?

EF 7 has implemented eager loading.
Use .Include
var post = context.Blogpost.First(); // post.Author will be null
var post = context.Blogpost.Include(b => b.Author).First(); // post.Author will be loaded
For more information on working with collections, see the answer to this question: How to work with collections

Related

EF Core - Relationship between 2 entities for non-PK columns

Here are my simplified entities for this questions:
public class Page
{
public Guid Id { get; set; }
public string PageType { get; set; }
public string PageName { get; set; }
public virtual ICollection<Section> Sections { get; set; }
}
public class Section
{
public Guid Id { get; set; }
public string SectionName { get; set; }
public string CmsContentId { get; set; }
[NotMapped]
public ICollection<CmsContentLanguage> Languages { get; set; }
}
public class CmsContentLanguage
{
public Guid Id { get; set; }
public string LangugeCode { get; set; }
public string CmsContentId { get; set; }
}
What I want to do is query my Page table and include the Sections navigation property, and for each Section the Languages property (which is not a db column) will be a collection of CmsContentLanguages. The response from my query would look like this:
{
"id":"6b3c680a-a5aa-4782-80ce-591f1d16abe2",
"pageName":"Description",
"pageType":"content",
"sections":[
{
"id":"e688b09e-9b1c-4094-aa04-cd044c820630",
"sectionName":"Introduction",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635",
"languages":[
{
"id":"11e19169-797c-4b6a-b5e8-2bdb9c1f28cc",
"languageCode":"en",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
},
{
"id":"19a0f31c-4b96-4afe-920f-40cea544eeab",
"languageId":"es",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
}
]
},
{
"id":"a53b9ace-b9a7-407d-b641-7a3c46077428",
"sectionName":"FAQs",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635",
"languages":[
{
"id":"11e19169-797c-4b6a-b5e8-2bdb9c1f28cc",
"languageCode":"en",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
},
{
"id":"19a0f31c-4b96-4afe-920f-40cea544eeab",
"languageId":"es",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
}
]
}
]
}
Multiple Section records can have the same CmsContentId value. Section.Languages is not a navigation property (nor do I think it should be).
Then I want to do a Linq query like this:
var guidId = Guid.Parse("6b3c680a-a5aa-4782-80ce-591f1d16abe2");
var query = dbContext.Pages
.Include(x => x.Sections)
.FirstOrDefault(p => p.Id == guidId);
At this point I have the response data I want, but the Languages property of each Section entity is null. This is where I'm lost as to how to setup relationships or do a query to get this data.
You are looking for the ThenInclude method. Here is how you use it.
var query = dbContext.Pages
.Include(x => x.Sections)
.ThenInclude(x => x.Languages)
.FirstOrDefault(p => p.Id == guidId);

EF Core 6 / Many to many relationship with custom connection table / ORA-00904 : invalid identifier

Hey im trying to create a many to many relationship as following:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Book2Category
{
public int BookId { get; set; }
public int CategoryId { get; set; }
}
The c# classes are exactly the same like the oracle database tables.
My fluent Api OnModelCreating method:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>(entity =>
{
entity.HasMany(b => b.Categories)
.WithMany(c => c.Books)
.UsingEntity<Book2Category>(d =>
{
d.HasKey(k => new { k.BookId, k.CategoryId});
});
});
modelBuilder.Entity<Category>(entity =>
{
entity.HasMany(b => b.Books)
.WithMany(c => c.Categories)
.UsingEntity<Book2Category>(d =>
{
d.HasKey(k => new { k.BookId, k.CategoryId});
});
});
}
I always get error "ORA-00904: \"BooksId\": invalid identifier" because EF Core is trying to execute the following select statement:
/*some selects before*/
left join (
select b2c.BookId, b2c.CategoryId, b2c.BooksId, b2c.CategoriesId
from Book2Category b2c
/*some more after but unimportant*/
)
So it takes the defined Keys BookId and CategoryId from OnModelCreating.
But also it genarates BooksId and CategoriesId (with an S).
This is autogenerated and comes from the properties defined in classes Book and Category.
What i want to achieve is to configure the entities in OnModelCreating() that i get rid of the two unnecessary b2c.BooksId, b2c.CategoriesId.
I know that there is a name convention for EF Core but i dont want to rename the columns in Book2Category.
Is that possible somehow?

ASP API CORE : Get relation Many to One

I have two entities : Article and PrixVariation.
One Article has Many Prix variation.
One Prix variation has One Article.
public partial class Article
{
public Article()
{
PrixVariations = new HashSet<PrixVariation>();
}
public int Id { get; set; }
public DateTime DateTime { get; set; }
...
public virtual ICollection<PrixVariation> PrixVariations { get; set; }
}
public partial class PrixVariation
{
public int Id { get; set; }
public DateTime DateTime { get; set; }
public int Article { get; set; }
public double Prix { get; set; }
public virtual Article ArticleNavigation { get; set; } = null!;
}
My Context is as follow :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>(entity =>
{
entity.ToTable("articles");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.DateTime)
.HasColumnType("datetime")
.HasColumnName("dateTime")
.HasDefaultValueSql("(getdate())");
});
modelBuilder.Entity<PrixVariation>(entity =>
{
entity.ToTable("prix_variation");
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasColumnName("id");
entity.Property(e => e.Article).HasColumnName("article");
entity.Property(e => e.DateTime)
.HasColumnType("datetime")
.HasColumnName("dateTime")
.HasDefaultValueSql("(getdate())");
entity.Property(e => e.Prix).HasColumnName("prix");
entity.HasOne(d => d.ArticleNavigation)
.WithMany(p => p.PrixVariations)
.HasForeignKey(d => d.Article)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_prix_variation_articles");
});
And my controller is as follow :
public class ArticlesController : ControllerBase
{
private readonly STGEORGESContext _context;
public ArticlesController(STGEORGESContext context)
{
_context = context;
}
// GET: api/Articles
[HttpGet]
public async Task<ActionResult<IEnumerable<Article>>> GetArticles()
{
return await _context.Articles.ToListAsync();
}
....
There is something not working here because when I launch the debogger, the collection of PrixVaration is always empty :
{"id":1,"dateTime":"2021-11-28T08:37:17","prixVariations":[]}
And of course in the database there is one PrixVaration linked to that Article..
Can anyone can help ?? Thaks a lot !!!
Pierre
It is called lazy loading. Ef doesn't load any object collections if you don't ask about it explicitly. So try this
return await _context.Articles.Include(i=> i.PrixVariations).ToListAsync();

Entity Framework Core 2 querying multi level collections with theniclude

I created two classes:
public class A {
public int Id { get; set; }
public ICollection<B> Bs { get; set; }
}
public class B {
public ICollection<C> C1s { get; set; }
public ICollection<C> C2s { get; set; }
}
then I tried to fetch them with ThenInclude method:
var result = context.As //public DbSet<A> As { get; set; }
.Include(a => a.Bs)
.ThenInclude(b => b.C1s)
.Include(a => a.Bs)
.ThenInclude(b => b.C2s)
.SingleOrDefaultAsync(a => a.Id.Equals(id)); //id is given
return await result;
But unfortunately both C1s and C2s collections are empty.
How to retrieve C entities which are related to B one?
I replaced .ThenInclude() methods with
.Include("Points.MasterPoints")
.Include("Points.SlavePoints")
That solved my issue.

Code first EF in ASP.NET core Web API single primary key

I'm using an external library to return data to me, the library has a lot of fields in it and goes quite deep with nested objects.
My class looks something like this;
public class Dto
{
public Dto(Val val)
{
Val = val;
}
[Key]
public int Id { get; set; }
public Val Value{ get; set; }
}
And when trying to save the changes to EF I get the error;
Cannot insert explicit value for identity column in table 'Value' when IDENTITY_INSERT is set to OFF.
When looking through the migrations it appears as if the Primary Key has been set on a few properties within the Value object. I've looked and looked but can't find a way to stop the migration automatically assigning primary keys. I've found stuff such as in the example above using the;
[Key]
attribute and adding bits and pieces into the OnModelCreating override but nothing has came up with a successful result.
Edit to give some more clarity instead of an example.
[DbContext(typeof(SummonerDtoContext))]
[Migration("20180314210242_Migration8")]
partial class Migration8
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.2-rtm-10011")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("LccWebAPI.Models.SummonerDto", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long?>("SummonerId");
b.HasKey("Id");
b.HasIndex("SummonerId");
b.ToTable("Summoners");
});
modelBuilder.Entity("RiotSharp.SummonerEndpoint.Summoner", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("AccountId");
b.Property<long>("Level");
b.Property<string>("Name");
b.Property<int>("ProfileIconId");
b.Property<int>("Region");
b.Property<DateTime>("RevisionDate");
b.HasKey("Id");
b.ToTable("Summoner");
});
modelBuilder.Entity("LccWebAPI.Models.SummonerDto", b =>
{
b.HasOne("RiotSharp.SummonerEndpoint.Summoner", "Summoner")
.WithMany()
.HasForeignKey("SummonerId");
});
#pragma warning restore 612, 618
}
}
}
and my actual data object I'm trying to store;
public class SummonerDto
{
public SummonerDto(Summoner summoner)
{
Summoner = summoner;
}
public int Id { get; set; }
public Summoner Summoner { get; set; }
}
and my context;
public class SummonerDtoContext : DbContext
{
public SummonerDtoContext(DbContextOptions<SummonerDtoContext> options)
: base(options)
{ }
public DbSet<SummonerDto> Summoners { get; set; }
}
I don't have access to modify the inside of the Summoner object itself to add annotations to ignore them as keys.
And the model structure of the Summoner object which I have no access to amend;
public class Summoner : SummonerBase
{
[JsonProperty("profileIconId")]
public int ProfileIconId { get; set; }
[JsonConverter(typeof(DateTimeConverterFromLong))]
[JsonProperty("revisionDate")]
public DateTime RevisionDate { get; set; }
[JsonProperty("summonerLevel")]
public long Level { get; set; }
}
And the base;
public class SummonerBase
{
public Region Region { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
public long AccountId { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
Edit : RESOLVED
After 3 hours of trying to figure this out I've finally managed to solve it, since I don't have access to annotate the models directly, I'd previously tried accessing the Ids through my SummonerDto model. Instead I accecssed them directly and it's worked.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SummonerDto>()
.HasKey(c => c.Id);
modelBuilder.Entity<Summoner>().Property(x => x.Id).ValueGeneratedNever();
modelBuilder.Entity<Summoner>().Property(x => x.ProfileIconId).ValueGeneratedNever();
modelBuilder.Entity<SummonerBase>().Property(x => x.Id).ValueGeneratedNever();
base.OnModelCreating(modelBuilder);
}

Resources