How to create a dependency graph using composite keys in EF Core - ef-core-2.1

Trying to store a composite key table which is keyed for both fields to the table it defines dependencies for.
Example case
Import files: 1..10
Dependencies 1: 2,3; 2: 4,5; 4:10
Intent is to use this key-only table for code to do code first strongly typed definitions while also being light weight, and it seemed like the most straight forward way to do it before running into problems.
Current code:
public class ImportFileDependency
{
[Key]
[ForeignKey("ImportFile")]
public int ImportFileId { get; set; }
[ForeignKey("Id")]
public ImportFile ImportFile {get; set;}
[Key]
[ForeignKey("ImportFile")]
public int ImportFileDependencyId { get; set; }
[ForeignKey("Id")]
public ICollection<ImportFile> ImportFileDependencies { get; set; }
}
public class ImportFile
{
[Key]
public int Id {get; set;}
public string Name { get; set; }
public string WorkbookTab { get; set; }
public string File { get; set; }
public ICollection<ImportFileDependency> Dependencies { get; set; }
}
...
modelBuilder
.Entity<ImportFileDependency>(e =>{
e.HasKey(ifd => new { ifd.ImportFileId, ifd.ImportFileDependencyId });
e.HasOne(ifd => ifd.ImportFile)
.WithMany(i => i.Dependencies);
});
modelBuilder
.Entity<ImportFile>()
.HasMany(i => i.Dependencies)
.WithOne()
.HasForeignKey(z => z.ImportFileId);
...
After multiple revisions of following the responses of the add-migration exception response, currently on:
There are multiple properties pointing to navigation 'ImportFile' in entity type 'ImportFileDependency'. To define composite foreign key using data annotations, use ForeignKeyAttribute on navigation.
which did not update from the most recent iteration.
I seem to have recursed into a deadend so looking for guidance

Given the time you've asked it, you probably found the answer yourself or gave up on it, but if someone else struggles with this error, this solved my issue: Entity Framework Code First - two Foreign Keys from same table
You have to define the relationship using fluent API.

Related

EntityFramework Include and possibly join?

I have the following table structure as shown in the picture. (see: Table structure). Both tables ("Batches" and "Methods") reference to a "Project" table.
When I now create a new Project I would like to get all childs created as well.
Doing so I did the follwoing:
_dbContext.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods).AsNoTracking().FirstOrDefault();
Now the problem is the following:
New Batch and Method instances are created - thus they get a new ID(PK). The referenced Project_Id (FK) is set correct. But in my new Measurement instance only the Batch_Id(FK) is set correct and the Method_Id remains unchanged (has the old value) (see: result).
What I need is that the Measurements.Mehtod_Id is set from the Methods table. Is there any suitable solution for that?
My entities look like the following
public class Project
{
[Key]
public long Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public virtual List<Batch> Batches { get; set; }
public virtual List<Method> Methods { get; set; }
}
public class Batch : BaseObject
{
public Batch()
{
BatchFiles = new List<FileAttachment>();
Measurements = new List<Measurement>();
}
public long Id { get; protected set; }
public long Project_Id { get; set; }
public virtual Project Project { get; set; }
public virtual List<Measurement> Measurements { get; set; }
}
public class Method : BaseObject
{
public Method()
{
Parameters = new List<Parameter>();
}
public long Id { get; protected set; }
public long Project_Id { get; set; }
public virtual Project Project { get; set; }
public virtual List<Measurement> Measurements { get; set; }
}
public class Measurement
{
public int Id { get; protected set; }
[ForeignKey("Batch")]
public long? Batch_Id { get; set; }
[Required]
public virtual Batch Batch { get; set; }
[ForeignKey("Method")]
public long? Method_Id { get; set; }
public virtual Method Method { get; set; }
}
// creation code (just a copy with new IDs for all childs)
Project newProjectVersion = _dbContext.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods)
.AsNoTracking().FirstOrDefault();
_dbContext.Projects.Add(newProjectVersion);
_dbContext.SaveChanges();
Thanks for any help!
The first problem is that your Select statement doesn't connect Measurements to Methods because of the AsNoTracking() addition. Only Projects and Methods are connected because they are explicitly Included off of the Project entity. The Measurements have a Method_id but this is value is not accompanied by a Method in their Method property. You could check that in the debugger if you walk through the object graph (with lazy loading disabled though!). Because of this, when all entities will be Add-ed to the context, EF won't notice that measurements receive new methods.
You could get tempted to fix that by Include-ing Measurement.Method as well:
...
.Include(x => x.Batches.Select(y => y.Measurements.Select(m => m.Method)))
...
Now you'll see that Measurement.Method will be populated everywhere in the object graph.
However, there's a gotcha here. When using AsNoTracking, EF6 doesn't keep track of entities it materialized (duh). This means that for each Measurement it creates a new Method instance, even if an identical Method (by id) was materialized before for another Measurement. (And in this case it will always materialize duplicates, because you already include Project.Methods.)
That's why you can't do this in the quick way with AsNoTracking and Add using one context instance. You'll get an error that EF tries to attach duplicate entities.
You must build the object graph using one context, with tracking, so EF will not materialize duplicates. Then you must Add this object graph to a new context. Which will look like this:
Project project;
using(var db = new MyContext())
{
db.Configuration.ProxyCreationEnabled = false;
project = db.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods).FirstOrDefault();
}
using(var db = new MyContext())
{
db.Projects.Add(project);
db.SaveChages();
}
Three remarks:
Proxy creation is disabled, because you can't attach a proxy to another context without explicitly detaching it first.
No, I didn't forget to include Measurement.Method. All methods are loaded by including them in the Project and now (because of tracking, and assuming that measurement will only have methods of the project they belong to), EF connects them with the Measurements by relationship fixup.
EF-core is smarter here: when adding AsNoTracking it won't track materialized entities, but still, it won't create duplicates either. It seems to have some temporary tracking during the construction of an object graph.
thanks for your answer so far. This works quite fine right now. Unfortunately I noticed that the Measurements entity has another required relationship to a table named 'MeasurementTypes':
[Required]
public virtual MeasurementType MeasurementType { get; set; }
[ForeignKey("MeasurementType")]
public long MeasurementType_Id { get; set; }
In contrast to Batches and Methods these entries must not be copied and the entries already exist in the MeasrementTypes table.
What would be a good way to put the required reference to the Measurements?

EF - how to prevent eager loading to load all nested entities

I've manay-to-many relationship between two entities: Categories <--> Items
public class CategoryMaster
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual List<SubCategoryMaster> SubCategories { get; set; }
public List<ItemMaster> Items { get; set; }
}
public class ItemMaster
{
public long Id { get; set; }
public string Name { get; set; }
public List<CategoryMaster> Categories { get; set; }
}
Whenever I try to explicit load related items to all/certain categories, it gives me
all related items
related categories to those items
related items to those categories and so on...nested/circular references
db.CategoryMaster
.Include(x=>x.Items)
.Include(x=>x.SubCategories.Select(y=>y.Items))
.ToList();
Hence results in below error while serializing it to JSON on *.cshtml with Json.Encode();
A circular reference was detected while serializing an object of type 'GoGreen.Data.Entities.SubCategoryMaster'.
Since I've disabled the lazy loading at property level, I'm not expecting it to load all nested entities(circular references) at any point of time. Is there a way to load all related level one records i.e. Categories and related items.
Related question - But Iodon't want to go with any of the two ways suggested.
NOTE : I'm more interested in knowing why EF behaves like this. It seems a bug to me.
First approach: you can add attribute above properties you don't want to exclude it from being serialized using [ScriptIgnore], you can create partial class and add your customization if your entities are auto generated
Second approach: Create a Model with only properties you need in your view and select only this model and set your properties
EFcontext.Tabel.include(x=>x...).Select(x=>new MyModel { ... });
One workaround, and please don't kill me :-) After object loading and before serializing, just set the loaded objects which are causing the circular reference to null. I tried it and worked like a charm.
use meta data redirection. figured I would help anyone who stumbled here.
[MetadataType(typeof(CategoryMasterMetadata))]
public partial class CategoryMaster
{
}
public class CategoryMasterMetadata
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public virtual List<SubCategoryMaster> SubCategories { get; set; }
public List<ItemMaster> Items { get; set; }
}

One To Many Relationship - Cascading Delete

I'm using EF 4.1 where I'm trying to map my POCO to my existing database. This is working fine until I try to delete an item that the other item has a dependency to. I want to enable cascading deletes, so that when my first item is deleted all dependencies would also be deleted (I believe this is called cascading delete).
I tried to enable this in the OnModelCreating:
modelBuilder.Entity<Component>()
.HasMany(c => c.Specifications)
.WithRequired(s => s.Component)
.Map(m => m.MapKey("ComponentId"))
.WillCascadeOnDelete(true);
However, I still get the The DELETE statement conflicted with the REFERENCE constraint exception.
The database is quite simple:
Component:
ComponentId (PK)
Description
Specification:
SpecificationID (PK)
Description
ComponentID (FK)
I've created the two following classes to match this setup:
public class Specification
{
[Key]
[Required]
public int Id { get; set; }
[MaxLength(50)]
[Required]
public string Description { get; set; }
public virtual Component Component { get; set; }
}
and
public class Component
{
[Key]
[Required]
public int Id { get; set; }
[MaxLength(50)]
[Required]
public string Description { get; set; }
public virtual ICollection<Specification> Specifications { get; set; }
}
Cascading delete in your model requires cascading delete in your DB. If you let the EF recreate the DB for you, it will set this up automatically. If you cannot let the EF do this, then you must either:
Add cascading delete to the FK manually, or
Remove the cascade from the model.

How can I populate a FK field using EF Code First?

I have a class Mailout with a Status that looks like this:
public class Mailout
{
public int Id {get; set; }
public string Name {get; set; }
public MailoutStatus Status { get; set; }
}
public class MailoutStatus
{
public int Id { get; set; }
public string Name { get; set;}
}
When I insert Mailouts and set the Status property, they are inserted correctly. When I fetch them, Status is always null. Since I don't have (and don't want) the status ID on my Mailout class, I have no way to retrieve it after-the-fact. How do I tell EF to populate this field eagerly, rather than lazily?
I'm hoping I can set something up in OnModelCreating() since I want this behavior all the time, not as an option that I can use sometimes by manipulating my LINQ-to-Entities queries.
You need to make your navigation properties virtual.
There is no such option in the ModelBuilder to configure an automatic eager loading of navigation properties in each query. You have to specify it query by query. As a workaround you could encapsulate eager loading in some method or property, for instance in the context:
public class MyContext : DbContext
{
public DbSet<Mailout> Mailouts { get; set; }
public IQueryable<Mailout> MailoutsWithStatus
{
get { return Mailouts.Include(m => m.Status); }
}
// ...
}
And then use in your queries:
context.MailoutsWithStatus.Where(...) ... etc.
Only an idea, it's untested.
Taking from Employee Info Starter Kit - upcoming MVC edition, here is a snippet, that works pretty well, to eager load objects when used:
public class Employee
{
...
public int? ReportsTo { get; set; }
[ForeignKey("ReportsTo")]
public virtual Employee Supervisor { get; set; }
/// <summary>
/// Children object collection of foreign key relation
/// </summary>
public virtual List<Employee> Subordinates { get; set; }
}

One to one relation with EF 4.1 code first

My question is similar to this one :
--> Many to one configuration using EF 4.1 code first
There are some fluent API solutions on google, with overriding "OnModelCreating" method and manually setting the foreign key options. But i would prefer a solution with data annotations if it is possible. Because I'd like to use inverse properties while coding. Such as TypeA object has got a TypeB object. So TypeB object should have a ParentTypeA property. Example :
public class User : IUser
{
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[RegularExpression(#"[A-Za-z0-9_\-\.]{2,32}"), MinLength(2), MaxLength(32)]
[Required(AllowEmptyStrings = false)]
public string UserName { get; set; }
// other props ....
// ....
public virtual UserGallery Gallery { get; set; }
}
public class UserGallery : IUserGallery
{
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserGalleryId { get; set; }
// other props ....
// ....
public virtual User ParentUser { get; set; }
}
A conventions way to do this in Code First is to use the UserID as the Primary Key of the UserGallery object. This is fine if its a true one to one relationship.
public class UserGallery : IUserGallery
{
[Key]
public int UserId {get;set;}
public User User {get;set;}
etc...
}
This has worked fine for me in the past.