Entity Framework CORE Seeding Joining table - entity-framework-core

I am working on .NET CORE 6 along with EF CORE 7. I need to seed data in joining table but unable to do so and get error.
I am seed FileTypeId but not sure why EF core migration throwing error...
error
The seed entity for entity type 'JobFileType' cannot be added because it has the navigation 'FileType' set. To seed relationships, add the entity seed to 'JobFileType' and specify the foreign key values {'FileTypeId'}. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the involved property values.
ClassA
public class JobProfile
{
public JobProfile()
{
this.JobFileTypes = new HashSet<JobFileType>();
}
public Guid JobProfileId { get; set; }
public string Name { get; set; }
public ICollection<JobFileType>? JobFileTypes { get; set; }
}
ClassB
public class FileType
{
public FileType()
{
this.JobFileTypes = new HashSet<JobFileType>();
}
public Guid FileTypeId { get; set; }
public string Extension { get; set; } = string.Empty;
public ICollection<JobFileType>? JobFileTypes { get; set; }
}
Joing Table
public class JobFileType
{
public Guid JobFileTypeId { get; set; }
public Guid JobProfileId { get; set; }
public JobProfile JobProfile { get; set; } = new JobProfile();
public Guid FileTypeId { get; set; }
public FileType FileType { get; set; } = new FileType();
}
Seed Extension
public static class JobFileTypeSeed
{
public static void Seed(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<JobFileType>()
.HasData(
new JobFileType {JobFileTypeId = Guid.Parse("aaa"), JobProfileId = Guid.Parse("ccc"), FileTypeId = Guid.Parse("yyy") },
new JobFileType { JobFileTypeId = Guid.Parse("bbb"), JobProfileId = Guid.Parse("ccc"), FileTypeId = Guid.Parse("zzz") }
);
}
}
config
internal class JobFileTypeConfiguration : IEntityTypeConfiguration<JobFileType>
{
public void Configure(EntityTypeBuilder<JobFileType> builder)
{
builder.ToTable("JobFileType", "dbo");
builder.HasKey(column => column.JobFileTypeId);
builder
.HasOne(jobFileType => jobFileType.JobProfile)
.WithMany(jobProfile => jobProfile.JobFileTypes)
.HasForeignKey(jobFileType => jobFileType.JobProfileId);
builder
.HasOne(jobFileType => jobFileType.FileType)
.WithMany(fileType => fileType.JobFileTypes)
.HasForeignKey(jobFileType => jobFileType.FileTypeId);
}
}

There is not much to say about the concrete issue (which btw is not specific to joining entity, but any entity model seeding):
I am seed FileTypeId but not sure why EF core migration throwing error...
as the cause of the issue is included at the beginning of the error message:
because it has the navigation 'FileType' set.
And your entity has
public FileType FileType { get; set; } = new FileType();
// ^ ^ ^
// the problem
and the same for
public JobProfile JobProfile { get; set; } = new JobProfile();
which will be the next error if you resolve the original.
Remove both navigation property initializers (= new ...) and the problem will be gone.
As a general rule, you should never initialize reference navigation properties because it causes many side effects and/or improper behaviors (not only for seeding, but also eager/lazy/explicit data loading). Initializing collection navigation properties is arbitrary, but ok. Only reference navigation property initialization must be avoided. For more info, see EF codefirst : Should I initialize navigation properties? - quite old EF topic, but still applies.
If you are trying to resolve NRT warnings (as I guess), initializing with new is definitely not a proper way. One reason I don't like NRT is because it is forcing people to use "workarounds" for preventing compiler warnings, which in fact break the primary functionality. Specifically in EF Core, enabling NRT also changes the optional/required attribute of some properties, hence database column types (most noticeable for string properties/columns and reference navigations). You could read more about this in the Working with Nullable Reference Types topic in the official EF Core documentation, but in general I would just disable NRT for EF entity model classes.

The proper order is to set the "master data" first and then try to set the join table, as you would expect.
The defaulting
{get;set;} = new Something();
Could be the offending declaration, since any instance upon creation will have the relation JobFileType already set

Related

Swagger-net breaks when using [FromUri] with a complex EF model

I'm using Swagger-Net in my .NET 4.5.1 WebAPI project and one of my API calls is causing the Swagger UI to spin forever on load before coming back with the error below.
Specifically, I found that using [FromUri] in combination with a complex EF entity that has references to other entities ends up causing this.
[HttpPost]
public APIResponse CreateSchool([FromUri]School school)
{
// save school object to db
}
public partial class School : IAuditableEntity,IEntity
{
public School()
{
this.Affiliations = new HashSet<Affiliation>();
this.SchoolAccreditations = new HashSet<SchoolAccreditation>();
this.SchoolAdultRoles = new HashSet<SchoolAdultRole>();
this.SchoolCareOptions = new HashSet<SchoolCareOption>();
this.SchoolDailySessions = new HashSet<SchoolDailySession>();
this.SchoolEligibilityRequirements = new HashSet<SchoolEligibilityRequirement>();
// ...more hashsets
[DataMember]
public int SchoolID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public bool Active { get; set; }
//...more properties
}
}
Is there a way to still use FromUri and the EF model? Or do I need to change my API call signature?

Deleting association between one or zero to one entities with EntityFramework

I have entities set up something like this:
public class MyThing
{
public int Id { get; set; }
public virtual MyOtherThing { get;set; }
}
public class MyOtherThing
{
public int Id { get; set; }
public virtual MyThing MyThing { get; set; }
}
My intention is that 'MyThing' can have one or none of MyOtherThing, and I also want a navigation link from MyOtherThing to it's parent.
I have configured the following EntityBaseConfiguration for the 'MyOtherThing' entity:
this.HasOptional(x => x.MyThing)
.WithOptionalPrincipal(x => x.MyOtherThing);
I can assign and modify MyOtherThing to MyThing no problem, but when I want to unassign 'MyOtherThing' from 'MyThing', how do I do this?
I tried the following:
myThing.MyOtherThing = null;
and then editing the entity by setting the EntityState.Modified state, but this didn't remove the association between the entities.
I tried adding the following to my MyThing entity, but this resulted in an EF 'Multiplicity is not valid' error when updating my database model:
public int? MyOtherThingId{ get; set; }
Thanks in advance!
I tried the following:
myThing.MyOtherThing = null;
If you want to remove an optional dependent entity (here: MyOtherThing) from a principal entity (here MyThing) by setting it to null, you have to pull the entity from the database with the dependent entity included, for example:
var mything = context.MyThings.Include(m => m.MyOtherThing)
.Single(t => t.Id == idValue);
(It's also OK when the belonging MyOtherThing is loaded into the context later, for example by lazy loading).
Without Include, myThing.MyOtherThing already is null and EF doesn't detect any change. Note that the statement myThing.MyOtherThing = null; doesn't execute lazy loading, which is a bit confusing because with collections the behavior is different.
By the way, the dependent entity can also be removed from the database directly, which is more efficient.
var ot = context.Set<MyOtherThing>().Find(idValue);
context.Set<MyOtherThing>().Remove(ot);
context.SaveChanges();

EF6:How to include subproperty with Select so that single instance is created. Avoid "same primary key" error

I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:
Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
public class Person
{
public int PersonId { get; set; }
public string Personname { get; set }
public ICollection Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string Line1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person Person { get; set; }
public ICollection<Feature> Features { get; set; }
}
// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
public int FeatureId { get; set; }
public string Featurename { get; set; }
public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
public void UpdateCandidate(Person newPerson)
{
Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
.....
.....
.....
}
Error:
Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Kindly suggest.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.
What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var person = dbContext.People.Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}

Update object when using entity splitting - code first

I had a class called Document, which I split into two entities, in order to separate an expensive binary field:
[Table("Document")]
public class Document
{
[Key]
public int Id { get; set; }
... other fields ...
[Required]
public virtual DocumentBinary DocumentBinary { get; set; }
}
[Table("Document")]
public class DocumentBinary
{
[Key, ForeignKey("Document")]
public int DocumentId { get; set; }
public Document Document { get; set; }
public byte[] DocumentData { get; set; }
}
So, everything works fine, both entities share the same database table and DocumentData is only loaded when it's needed.
However, when it comes to updating the Document entity, I get an error stating that 'DocumentBinary is required'.
When I remove the [Required] attribute from DocumentBinary virtual property, I get the following error:
The entity types 'Document' and 'DocumentBinary' cannot share table 'Documents' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.
I can obviously do something like:
var test = document.DocumentBinary;
before updating the document object:
documentRepository.Update(document);
This will then load the binary data on my request and save the changes without any issues, but the whole point is that I shouldn't need to do that.
This can be achieved using the fluent API. If you remove the data annotations and in your OnModelCreating add this, it should work.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Document>().HasRequired(d => d.DocumentBinary).
WithRequiredDependent(db => db.Document);
}
I managed to resolve it by overriding my Update method in DocumentRepository:
public override void Update(Document document)
{
try
{
DataContext.Entry(document.DocumentBinary).State = EntityState.Modified; // added this line
DataContext.Entry(document).State = EntityState.Modified;
}
catch (System.Exception exception)
{
throw new EntityException("Failed to update document");
}
}
I know it probably does the same thing as me evaluating DocumentBinary by assigning it to 'test' variable, but it looks like a much cleaner solution.

Using the Entry<TEntity>().CurrentValues.SetValues() is not updating collections

I have not run into this before, because I usually handled collections by them selves instead of modifying them directly on the entity.
public class Schedule: BaseEntity
{
public Guid Id {get;set;}
public virtual int? DayOfTheWeekTypeId { get; set; }
public virtual DayOfTheWeekType DayOfTheWeekType { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
public DateTime? StartDateTime { get; set; }
public DateTime? EndDateTime { get; set; }
public string SpecialInstructions { get; set; }
}
Mapping class:
public ScheduleMapping()
{
HasMany(c => c.Instructors).WithMany().Map(m => { m.MapLeftKey("ScheduleId");
m.MapRightKey("InstructorId");
m.ToTable("Schedule_Instructors");
});
HasOptional(s => s.DayOfTheWeekType).WithMany().HasForeignKey(s => s.DayOfTheWeekTypeId).WillCascadeOnDelete(false);
Property(s => s.SpecialInstructions).IsMaxLength();
}
This is my update method:
public virtual void Update(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
//this is the original persisted entity
var persistedEntity = _repository.GetById(entity.Id);
if(originalEntity != null)
{
entity.Id = persistedEntity.Id;
UnitOfWork.ApplyCurrentValues<TEntity>(originalEntity,entity);
UnitOfWork.Commit();
}
}
This is the Method that handled the "merge"
public void ApplyCurrentValues<TEntity>(TEntity original, TEntity current) where TEntity : class
{
base.Entry<TEntity>(original).CurrentValues.SetValues(current);
}
If I modify the Instructors collection then try to apply the update, it seems to keep my original values. I have tried loading the the Schedule entity prior to the update and make my changes, but sometimes that causes a PK error (on the Instructors collection) in entity framework. As if it is trying to add an entity with the same key. So, instead I am rebuilding the Schedule entity (including the ID) manually and then updating it. When I do that I do not get any more errors, however, the Instructors collections doesn't change. I am thinking because CurrentValues. SetValues is being applied based on the persisted entity and not my updated version. the Should I handle my updates differently or do I need to manully
SetValues never updates navigation properties. When you execute your code it only knows about changes in simple / complex properties of the entity passed to your Update method. EF even don't know about related entities of the entity passed to your Update method.
You must manually tell EF about each change in your object graph - EF doesn't have any resolution mechanism for object graphs.