Include ignored column in EFCore - entity-framework

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

Related

Having nullable foreign keys in one-to-one relationship with more than one entity

I wanted Movie, Actor, Director, User etc entities to have exactly one Image and an Image to belong to exactly one entity. I defined and configured them as -
Models : (simplified)
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Image Image { get; set; }
}
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
public Image Image { get; set; }
}
// Director, User etc are defined in similar way
public class Image
{
public int Id { get; set; }
public string Base64 { get; set; }
public int? MovieId { get; set; }
public int? ActorId { get; set; }
public int? DirectorId { get; set; }
public int? UserId { get; set; }
}
Configurations : (simplified)
public class MovieConfig : IEntityTypeConfiguration<Movie>
{
public void Configure(EntityTypeBuilder<Movie> builder)
{
builder.ToTable("Movie");
builder.HasKey(p => p.Id);
builder.Property(p => p.Title).IsRequired(true).HasColumnType("nvarchar(128)");
builder.HasOne(e => e.Image)
.WithOne()
.HasForeignKey<Image>(e => e.MovieId)
.OnDelete(DeleteBehavior.Cascade);
}
}
// Actors, Director, User etc are configured in similar way
public class ImageConfig : IEntityTypeConfiguration<Image>
{
public void Configure(EntityTypeBuilder<Image> builder)
{
builder.ToTable("Image");
builder.HasKey(p => p.Id);
builder.Property(p => p.Base64).IsRequired(true).HasColumnType("nvarchar(MAX)");
}
}
This generates the schema with Image table having one-to-one relationship with each of Movie, Actor, Director, User etc table, as expected.
What's bothering me are all those nullable foreign-key fields in Image, because -
a new nullable foreign-key must be added whenever a new entity with Image is introduced
for any image entry only one of those foreign-key columns is going to have a value
What other ways I could define and configure the entities to achieve the same result while avoiding those nullable foreign-keys?
Edit :
Or is it, in general practice, considered OK to have a schema like the one I currently have (with multiple nullable foreign-keys where only one of them can have a value)?
I don't have a lot of experience in database design/schema and best/general practices. It just felt wrong to me and that's where the question came.
So, please feel free to give your opinion and suggestion?
Create relationship for MovieId, ActorId, DirectorId and UserId in Image table in sql server.
Then re-update your dbcontext and those foreign IDs will be auto saved from movie, actor, director and user when inserting.

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()

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

Entity Framework core many to many not inserting

I am using EF7 and have a scenario which needs a many to many relationship.
I have a ParticipantSIR entity and a ParticipantAssessmentReport entity. There is a link table ParticipantSIRAssessmentReport between them.
public class ParticipantSIR
{
public int ParticipantSIRID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
public virtual Participant Participant { get; set; }
}
public class ParticipantAssessmentReport
{
public int ParticipantAssessmentReportID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
}
public partial class ParticipantSIRAssessmentReport
{
public int ParticipantSIRID { get; set; }
public int ParticipantAssessmentReportID { get; set; }
public virtual ParticipantAssessmentReport ParticipantAssessmentReport { get; set; }
public virtual ParticipantSIR ParticipantSIR { get; set; }
}
modelBuilder.Entity<ParticipantSIRAssessmentReport>(entity =>
{
entity.HasKey(e => new { e.ParticipantSIRID, e.ParticipantAssessmentReportID });
entity.HasOne(d => d.ParticipantAssessmentReport).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantAssessmentReportID).OnDelete(DeleteBehavior.Restrict);
entity.HasOne(d => d.ParticipantSIR).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantSIRID).OnDelete(DeleteBehavior.Restrict);
});
This appears to be the way this needs to be setup with EF core including the third entity. I got some of the information from. http://ef.readthedocs.io/en/latest/modeling/relationships.html#many-to-many
When I insert data the 2 outside entities get populated but not the link table.
Since there are no navigation properties between the ParticipantSIR and ParticipantAssessmentReport then I'm not sure how to add the linked data.
_db.ParticipantAssessmentReport.Add(participantAssessmentReport);
foreach (var sir in participantSirs)
{
_db.ParticipantSIR.Add(sir);
}
_db.SaveChanges();
Assuming we're talking about EF Core 1.0rc1 it looks like you have created your model correctly (except the virtual keyword doesn't do anything yet as lazy loading hasn't been implemented).
As many-to-many hasn't been implemented yet as of 1.0rc1 you need to do some extra work. See https://github.com/aspnet/EntityFramework/issues/1368#issuecomment-180066124 for the classic blog Post, Tag, PostTag example code.
In your case you need to explictly add to ParticipantSIRAssessmentReport, something like this:
var participantSIRAssessmentReport = new ParticipantSIRAssessmentReport {ParticipantSIR = participantSIR, ParticipantAssessmentReport = participantAssessmentReport };
_db.ParticipantSIRAssessmentReport.Add(participantSIRAssessmentReport);
_db.SaveChanges();
To map Many-To-Many relationships in EF you need to add the following to your DbContext's OnModelCreating() method:
modelBuilder.Entity<ParticipantSIR>()
.HasMany(e => e.ParticipantSIRAssessmentReport)
.WithMany(e => e.ParticipantSIR)
.Map(e => e.ToTable("ParticipantSIRAssessmentReport") //Name of the linking table
.MapLeftKey("ParticipantSIRId") //Name of the Left column
.MapRightKey("ParticipantSIRAssessmentReportId")); //Name of the right column
From here the relationship will be handled using the Collections inside each of the classes.

How to have an entity inherit from another base entity and map to db using TPC with EF 4.2?

Say I have an entity model aggregate for Activity, like so:
public class Activity : Entity
{
public int PersonId { get; set; }
public virtual Person Person { get; set; }
public int Number { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual ICollection<ActivityTag> Tags { get; set; }
}
public class ActivityTag : Entity
{
public int ActivityPersonId { get; set; }
public int ActivityNumber { get; set; }
public virtual Activity Activity { get; set; }
public int Number { get; set; }
public string Text { get; set; }
}
Forget about the relation between Activity and Person, but note the 1..* relation between Activity and ActivityTag. The fluent mapping looks more or less like this:
public class ActivityOrm : EntityTypeConfiguration<Activity>
{
public ActivityOrm()
{
ToTable("Activity", "Activities");
HasKey(p => new { p.PersonId, p.Number });
HasRequired(d => d.Person)
.WithMany()
.HasForeignKey(d => d.PersonId)
.WillCascadeOnDelete(true);
HasMany(p => p.Tags)
.WithRequired(d => d.Activity)
.HasForeignKey(d => new { d.ActivityPersonId, d.ActivityNumber })
.WillCascadeOnDelete(true);
Property(p => p.Content).HasColumnType("ntext");
}
}
public class ActivityTagOrm : EntityTypeConfiguration<ActivityTag>
{
public ActivityTagOrm()
{
ToTable("ActivityTag", "Activities");
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
Property(p => p.Text).IsRequired().HasMaxLength(500);
}
}
Given this, I want to introduce a new collection property to the Activity entity:
public ICollection<DraftedTag> DraftedTags { get; set; }
The DraftedTag entity should have the same exact properties and primary key as ActivityTag. The only thing that should be different is the table it is mapped to. I tried creating a class that derived from ActivityTag, like so:
public class DraftedTag : ActivityTag
{
}
public class DraftedTagOrm : EntityTypeConfiguration<DraftedTag>
{
public DraftedTagOrm()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("DraftedTag", "Activities");
});
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
}
}
The DraftedTagOrm has been added to the modelBuilder.Configurations collection, but without even adding the foreign key association to Activity, I get the following exception:
The property 'ActivityPersonId' is not a declared property on type
'DraftedTag'. Verify that the property has not been explicitly
excluded from the model by using the Ignore method or
NotMappedAttribute data annotation. Make sure that it is a valid
primitive property.
When I completely duplicate the code from the ActivityTag class and the ActivityTagOrm constructor into the respective DraftTag class / configuration constructor, then it works as expected -- I get two different tables with identical schemas, but different names. However each time I want to make a change to the ActivityTag class, I must make a corresponding change in the DraftTag class.
Is it possible to make this code DRYer by having DraftTag extend ActivityTag? If so, what would the EntityTypeConfiguration look like for DraftTag?