EF is automatically adding object to the collection [duplicate] - entity-framework

The abridged version of my WebApi controller is like so:
[HttpGet, Route("")]
public async Task<IHttpActionResult> Search(bool includeEntities)
{
IQueryable<VersionTopic> results = DbContext.VersionTopics;
if (includeEntities)
{
results = results.Include(o => o.CreatedBy);
results = results.Include(o => o.LastSavedBy);
results = results.Include(o => o.Topic.LastSavedBy);
results = results.Include(o => o.Topic.CreatedBy);
results = results.Include(o => o.Topic.PACKType.LastSavedBy);
// etc...
}
results = results.OrderBy(o => o.SortOrder);
return Ok(result.ToList());
}
For some reason, the LastSavedBy entity is ALWAYS populated, even when the includeEntities parameter is false.
Why would it be eager loading just this one entity but none of the others (as is required)?
Here's a screenshot:
My model is defined as so:
public class VersionTopic
{
[Key]
[Required]
public Guid VersionTopicId { get; set; }
[Required]
[Index("IX_VersionTopic_VersionId_TopicId", IsUnique = true, Order = 0)]
public Guid VersionId { get; set; }
[Required]
[Index("IX_VersionTopic_VersionId_TopicId", IsUnique = true, Order = 1)]
public Guid TopicId { get; set; }
[Required(AllowEmptyStrings = true)]
[MaxLength(250)]
public string Name { get; set; }
public string KeyMessage { get; set; }
[Required]
public int SortOrder { get; set; }
[Required]
public Guid CreatedById { get; set; }
[Required]
public DateTime CreatedDate { get; set; }
[Required]
public Guid LastSavedById { get; set; }
[Required]
public DateTime LastSavedDate { get; set; }
public virtual ICollection<VersionRecommendation> VersionRecommendations { get; set; } = new List<VersionRecommendation>();
[ForeignKey("CreatedById")]
public virtual ApplicationUser CreatedBy { get; set; }
[ForeignKey("LastSavedById")]
public virtual ApplicationUser LastSavedBy { get; set; }
[ForeignKey("TopicId")]
public virtual Topic Topic { get; set; }
[ForeignKey("VersionId")]
public virtual Version Version { get; set; }
public VersionTopic()
{
VersionTopicId = Guid.NewGuid();
}
}

It's basically explained in the following Tip from Loading Related Data - Eager Loading section of the EF Core documentation, but the same applies to EF6 and below:
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Unfortunately this behavior is not controllable, so the only options to avoid it is to use fresh new DbContext (or manually cleaning up the navigation properties which are not needed, but that's annoying and easy to forget).

Related

Entity Framework Db context issue in .net core related to Models

Am Trying to create Two Tables like bellow got some EF error.
public class Student : ModelsBase
{
public string AdharNumber { get; set; }
public byte Religion { get; set; }
public int CategoryID { get; set; }
public string Cast { get; set; }
public string SubCast { get; set; }
public string Photo { get; set; }
public DateTime DateOfJoining { get; set; } = DateTime.Now;
[Required]
public ICollection<Address> TemporaryAddress { get; set; }
[Required]
public ICollection<Address> PermanentAddress { get; set; }
}
public class Address : ModelsBase
{
public string DoorNo { get; set; }
public string StreetLocality { get; set; }
public string Landmark { get; set; }
public string City { get; set; }
public int Taluk { get; set; }
public int District { get; set; }
public int State { get; set; }
public string Pincode { get; set; }
public bool IsPermanent { get; set; } = true;
public bool IsDefault { get; set; } = true;
[ForeignKey("Student")]
public Guid StudentId { get; set; }
}
Getting the bellow error while trying to Run the "Add-Migration command"
Both relationships between 'Address' and 'Student.PermanentAddress' and between 'Address' and 'Student.TemporaryAddress' could use {'StudentId'} as the foreign key. To resolve this, configure the foreign key properties explicitly in 'OnModelCreating' on at least one of the relationships
Please help. Thanks!
Your issue is that from the Address side of things you have a Many-to-1 with a single Student, but from the Student side of things you want 2x 1-to-Many relationships.
Since The relationship is really just a 1-to-Many from the student that you want to discriminate between temporary and permanent addresses:
public class Student : ModelsBase
{
public string AdharNumber { get; set; }
public byte Religion { get; set; }
public int CategoryID { get; set; }
public string Cast { get; set; }
public string SubCast { get; set; }
public string Photo { get; set; }
public DateTime DateOfJoining { get; set; } = DateTime.Now;
[Required]
public ICollection<Address> Addresses { get; set; } = new List<Address>();
[NotMapped]
public ICollection<Address> TemporaryAddresses => Addresses.Where(x => !x.IsPermanent).ToList();
[NotMapped]
public ICollection<Address> PermanentAddresses => Addresses.Where(x => x.IsPermanent).ToList();
}
With 1-to-many collections I recommend initializing them to an empty list to avoid null reference exceptions especially if lazy loading is disabled.
The caveat here is that from EF's perspective, Student only has the Addresses collection, do not attempt to use either TemporaryAddresses or PermanentAddresses in a query expression as these are unmapped accessors. If you want to filter based on a permanent address you will have to do it through Addresses and include the condition on IsPermanent in the query.
For example:
// Not valid...
var studentsInDetroit = context.Students.Where(x => x.PermanentAddresses.Any(a => a.City == "Detroit")).ToList();
// Valid...
var studentsInDetroit = context.Students.Where(x => x.Addresses.Any(a => a.IsPermanent && a.City == "Detroit")).ToList();
Normally I don't recommend using unmapped accessors in entities because of this. It is generally better to leave entities representing pure domain/data state and project that down to view models which can be more concerned about splitting the data into a more palatable form for consumption.

Entity Framework Core Automapper latest viewed items for a specific user

Using Entity Framework Core, I would like to get a list of the 10 most recently viewed jobs by a user.
I am working on a CRM which contains User, Job and UserJobView classes.
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Job
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
}
public class UserJobView
{
public Guid Id { get; set; }
public Guid JobId { get; set; }
public Guid UserId { get; set; }
public DateTime LastViewedAt { get; set; }
public Job Job { get; set; }
}
I also have a JobDto which I project to using AutoMapper
public class JobDto : IMapFrom<Job>
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime? LastViewedAt { get; set; }
}
Each time a User views a Job I either update or create a UserJobView object setting the LastViewedAt property to be DateTime.Now
I can get the latest viewed items with the following fluent query
return await _context.UserJobViews
.Where(x => x.UserId == thisUserId)
.OrderByDescending(x => x.LastViewed)
.Take(10)
.Select(x => x.Job)
.ProjectTo<JobDto>(_mapper.ConfigurationProvider)
.ToListAsync();
However this obviously doesn't populate the LastViewedAt property for the JobDto. How might I go about doing this?
With a small addition to your configuration, you can make it happen:
CreateMap<UserJobView, JobDto>().IncludeMembers(s => s.Job);

LINQ query throw exception on FirstOrDefault method

I'm using EF core, and I have a many-to-many relationship between two entity
IotaProject <--> User
Here's entities & dto related to the question
public class IotaProject
{
[Key]
public int Id { get; set; }
[Required]
public string ProjectName { get; set; }
[Required]
public DateTime Create { get; set; }
public ICollection<ProjectOwnerJoint> Owners { get; set; } = new List<ProjectOwnerJoint>();
}
public class ProjectOwnerJoint
{
public int IotaProjectId { get; set; }
public IotaProject IotaProject { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User
{
[Key]
public int Id { get; set; }
[Required]
public string FullName { get; set; }
[Required]
public string ShortName { get; set; }
[Required]
public string Email { get; set; }
public ICollection<ProjectOwnerJoint> OwnedProjects { get; set; } = new List<ProjectOwnerJoint>();
}
public class ApplicationDbContext : DbContext
{
public DbSet<IotaProject> IotaProjects { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<ProjectOwnerJoint> ProjectOwnerJoint { get; set; }
}
public class IotaProjectDisplayDto
{
public int Id { get; set; }
public string ProjectName { get; set; }
public DateTime Create { get; set; }
public UserMinDto Owner { get; set; }
public int Count { get; set; }
public IEnumerable<UserMinDto> Reviewers { get; set; }
}
public class UserMinDto
{
public int Id { get; set; }
public string FullName { get; set; }
public string ShortName { get; set; }
}
Following LINQ is the problem, the LINQ purpose is to convert IotaProject to IotaProjectDisplayDto, and key part is that Owners property of IotaProject is ICollection and Owner property in IotaProjectDisplayDto is just one single element UserMinDto, so I only need to get the first element of IotaProject's Owners and that's FirstOrDefault() comes.
IEnumerable<IotaProjectDisplayDto> results = _db.IotaProjects.Select(x => new IotaProjectDisplayDto
{
Id = x.Id,
ProjectName = x.ProjectName,
Create = x.Create,
Owner = x.Owners.Select(y => y.User).Select(z => new UserMinDto { Id = z.Id, FullName = z.FullName, ShortName = z.ShortName }).FirstOrDefault()
});
return results;
it throws run-time exception
Expression of type 'System.Collections.Generic.List`1[ToolHub.Shared.iota.UserMinDto]' cannot be used for parameter
of type 'System.Linq.IQueryable`1[ToolHub.Shared.iota.UserMinDto]'
of method 'ToolHub.Shared.iota.UserMinDto FirstOrDefault[UserMinDto](System.Linq.IQueryable`1[ToolHub.Shared.iota.UserMinDto])' (Parameter 'arg0')
I'm guessing it's probably related to deferred execution, but after read some posts, I still can't resolve it.
Any tips would be appreciated.
Right now, the only way I can get this work is I change type of Owner property in IotaProjectDisplayDto into IEnumrable, which will no longer need FirstOrDefault() to immediate execution. And later on, I manually get the first element in the client to display.
This issue happened in Microsoft.EntityFrameworkCore.SqlServer 3.0.0-preview7.19362.6
I end up downgrade to EF core stable 2.2.6 as Ivan suggested in comment, and everything works fine.

Entity Framework is eager loading a single entity

The abridged version of my WebApi controller is like so:
[HttpGet, Route("")]
public async Task<IHttpActionResult> Search(bool includeEntities)
{
IQueryable<VersionTopic> results = DbContext.VersionTopics;
if (includeEntities)
{
results = results.Include(o => o.CreatedBy);
results = results.Include(o => o.LastSavedBy);
results = results.Include(o => o.Topic.LastSavedBy);
results = results.Include(o => o.Topic.CreatedBy);
results = results.Include(o => o.Topic.PACKType.LastSavedBy);
// etc...
}
results = results.OrderBy(o => o.SortOrder);
return Ok(result.ToList());
}
For some reason, the LastSavedBy entity is ALWAYS populated, even when the includeEntities parameter is false.
Why would it be eager loading just this one entity but none of the others (as is required)?
Here's a screenshot:
My model is defined as so:
public class VersionTopic
{
[Key]
[Required]
public Guid VersionTopicId { get; set; }
[Required]
[Index("IX_VersionTopic_VersionId_TopicId", IsUnique = true, Order = 0)]
public Guid VersionId { get; set; }
[Required]
[Index("IX_VersionTopic_VersionId_TopicId", IsUnique = true, Order = 1)]
public Guid TopicId { get; set; }
[Required(AllowEmptyStrings = true)]
[MaxLength(250)]
public string Name { get; set; }
public string KeyMessage { get; set; }
[Required]
public int SortOrder { get; set; }
[Required]
public Guid CreatedById { get; set; }
[Required]
public DateTime CreatedDate { get; set; }
[Required]
public Guid LastSavedById { get; set; }
[Required]
public DateTime LastSavedDate { get; set; }
public virtual ICollection<VersionRecommendation> VersionRecommendations { get; set; } = new List<VersionRecommendation>();
[ForeignKey("CreatedById")]
public virtual ApplicationUser CreatedBy { get; set; }
[ForeignKey("LastSavedById")]
public virtual ApplicationUser LastSavedBy { get; set; }
[ForeignKey("TopicId")]
public virtual Topic Topic { get; set; }
[ForeignKey("VersionId")]
public virtual Version Version { get; set; }
public VersionTopic()
{
VersionTopicId = Guid.NewGuid();
}
}
It's basically explained in the following Tip from Loading Related Data - Eager Loading section of the EF Core documentation, but the same applies to EF6 and below:
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Unfortunately this behavior is not controllable, so the only options to avoid it is to use fresh new DbContext (or manually cleaning up the navigation properties which are not needed, but that's annoying and easy to forget).

Entity Framework Code First Many to Many Setup For Existing Tables

I have the following tables Essence, EssenseSet, and Essense2EssenceSet
Essense2EssenceSet is the linking table that creates the M:M relationship.
I've been unable to get the M:M relationship working though in EF code first though.
Here's my code:
[Table("Essence", Schema = "Com")]
public class Essence
{
public int EssenceID { get; set; }
public string Name { get; set; }
public int EssenceTypeID { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<EssenceSet> EssenceSets { get; set; }
public virtual EssenceType EssenceType { get; set; }
}
[Table("EssenceSet", Schema = "Com")]
public class EssenceSet
{
public int EssenceSetID { get; set; }
public int EssenceMakerID { get; set; }
public string Name { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<Essence> Essences { get; set; }
}
[Table("Essence2EssenceSet", Schema = "Com")]
public class Essence2EssenceSet
{
//(PK / FK)
[Key] [Column(Order = 0)] [ForeignKey("Essence")] public int EssenceID { get; set; }
[Key] [Column(Order = 1)] [ForeignKey("EssenceSet")] public int EssenceSetID { get; set; }
//Navigation
public virtual Essence Essence { get; set; }
public virtual EssenceSet EssenceSet { get; set; }
}
public class EssenceContext : DbContext
{
public DbSet<Essence> Essences { get; set; }
public DbSet<EssenceSet> EssenceSets { get; set; }
public DbSet<Essence2EssenceSet> Essence2EssenceSets { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});
}
}
This is the code I'm trying to run:
Essence e = new Essence();
e.EssenceTypeID = (int)(double)dr[1];
e.Name = dr[2].ToString();
e.DescLong = dr[3].ToString();
//Get Essence Set
int setID = (int)(double)dr[0];
var set = ctx.EssenceSets.Find(setID);
e.EssenceSets = new HashSet<EssenceSet>();
e.EssenceSets.Add(set);
ctx.Essences.Add(e);
ctx.SaveChanges();
And here's the error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I'm not able to find the problem. I'd greatly appreciate help setting this up right.
Thanks!
Remove your Essence2EssenceSet model class. If junction table contains only keys of related entities participating in many-to-many relations it is not needed to map it as entity. Also make sure that your fluent mapping of many-to-many relations specifies schema for table:
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet", "Com");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});