How to define nested model relationship with ValueObjects - c#

I have a parent child relationship where the parent has a ValueObject and I cannot determine how to correctly define the relationship.
Adding a migration for the Child/Parent relationship fails with the error...
The entity type 'Address' requires a primary key to be defined.
The following is the current code structure.
public class Address
{
[Required]
public string BuildingNumber { get; private set; }
// other address properties...
}
public class Parent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
[Required]
public Address PrimaryAddress { get; private set; }
}
public class ParentContext : DbContext
{
public ParentContext(DbContextOptions<ParentContext> options) :
base(options)
{
}
public DbSet<Parent> Parents { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress);
// Flatten the ValueObject fields into table
modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress).
Property(b => b.BuildingNumber).IsRequired().
HasColumnName("Primary_BuildingName");
}
}
public class Child
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
[Required]
public int ParentId { get; private set; }
[ForeignKey("ParentId")]
public Parent Parent { get; private set; }
}
public class ChildContext : DbContext
{
public ChildContext(DbContextOptions<ChildContext> options) : base(options)
{
}
public DbSet<Child> Children { get; set; }
}
Using the above code example I can run separate commands to create migrations for Parent and Child and the tables look correct.
add-migration create-parent -c parentcontext
add-migration create-child -c childcontext
Adding in the relationship to the entities and adding the final migration fails.
add-migration add-parent-child-fk -c childcontext
The problem only occurs where I have Child and Parent in a different Context.
I have tried defining the relationship different ways in both the parent and child to map the address fields so that the child 'understands' the mapping but I cannot avoid EF errors with anything I have tried.
Example Project is here
https://github.com/cimatt55/ef-parent-valueobject

The main problem are the separate contexts. Value object (owned entity type) is just a consequence - if there wasn't value object, then you would have another issues.
You seem to base your design on a wrong assumption that only entity classes from publicly exposed DbSet. But that's not true. Referenced entities by navigation properties are also included, as well as referenced entities by them etc.
This is logical because EF Core context represents a database with tables and relationships. EF Core needs to know all the related entities in order to correctly support loading related data, querying (joining), cascade delete, tables, columns, primary and foreign key property/columns and their mappings etc.
This is explained in the Including & Excluding Types section of the EF Core documentation:
By convention, types that are exposed in DbSet properties on your context are included in your model. In addition, types that are mentioned in the OnModelCreating method are also included. Finally, any types that are found by recursively exploring the navigation properties of discovered types are also included in the model.
Adjusting their example for your ChildContext, the following types are discovered:
Child because it is exposed in a DbSet property on the context
Parent because it is discovered via the Child.Parent navigation property
Address because it is discovered via the Parent.PrimaryAddress navigation property
Since ChildContext has no Parent entity configuration, EF assumes everything related to Parent (and Address) to be by convention, hence the exception.
Shorty, using separate contexts containing related entities is not a good idea. The solution is to put and maintain all related entities in a single context.
Looking at the terminology used, you've probably are after DDD and bounded contexts, but these do not fit in EF Core (and generally in relational database) model.

Related

EntityFramework 6 DatabaseFirst add navigation property from code

I have (can`t change) EF DataBase first project without navigation property in models.
I want extend autogenerated models and add navigation property
Generated model
//generated.cs
public partial class company
{
public int id { get; set; }
public string name { get; set; }
}
public partial class user
{
public int id { get; set; }
public int company_id { get; set; }
}
I want add navigation property from code
//model_extension.cs
public partial class user
{
public company Company { get; set; }
}
I have exception "The specified type member 'Company' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."
I work with CodeFirst before.
I understand, I must link user.company_id to Company
But not understand how make this with code (not designer)
In Database First Approach, You are generating your POCO objects from database schema via Entity Framework Designer/ADO.NET Entity Data Model so it is not flexible as Code-First, you need to go on database, and change the schema yourself and update your .edmx file. while adding properties to these Models are possible in c# side, but they are not going to be added to your database schema,
I suggest your reverse your database schema and go as Code-First Approach, This nuget package can do this for you.
After Reversing It's all about Code-First then, creating your own DbContext and OnModelCreating and let the Migration handle the rest. Then you can use Eager Loading of EF to load your data,

Entity Framework 6 Fluent Mapping - One to Many Code First

Given the following SQL tables:
EntityGroup:
Id int, (PK)
GroupName nvarchar(100)
Entity:
Id int, (PK)
EntityGroupId int, (FK Non-nullable)
Description nvarchar(100)
And the following POCOs
public class Entity
{
public int Id { get; set; }
public int EntityGroupId { get; set; }
public int RefNumber { get; set; }
}
public class EntityGroup
{
public int Id { get; set; }
public virtual IList<Entity> Entities { get; set; }
}
How do I configure the fluent mapping correctly? I want Entity.EntityGroupId to remain as an int rather than an EntityGroup object.
I want to be able to .Include() optionally Include("Entities"). The closest I got is this, but that seems to eager-load all entities even if I dont use .Include("Entities"), which is not the behaviour I want:
modelBuilder.Entity<EntityGroup>()
.HasMany(x => x.Entities);
You must set off the lazy loading,
you can do this for just a specific unit of work or for all by setting your dbContext Like
dbContext.Configuration.LazyLoadingEnabled = false;
dbContext.Configuration.ProxyCreationEnabled = false;
or set it in Ctor of your DbContext.
The way I understand it, you want to configure one-to-many relationship between EntityGroup and Entity without navigation property in Entity class and using Entity.EntityGroupId as a FK. All that with Fluent API.
It's possible, but you have to start the configuration from the class having a navigation property (EntityGroup in your case) because Has methods require property accessor while With methods have parameterless overloads. As usual, for the last part you will use the HasForeignKey method:
modelBuilder.Entity<EntityGroup>()
.HasMany(e => e.Entities)
.WithRequired()
.HasForeignKey(e => e.EntityGroupId);
But note that EF recognizes the naming convention used in your sample classes (in particular the EntityGroupId), so you'll get the same mapping w/o any fluent configuration or data annotations.
The problem is lazy loading is enabled by default, so it will load the related entities every time you try to get access to them.Two options to solve your issue could be:
Disabling lazy loading in your context:
public YourContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
Removing virtual from your navigation property, which is one of
the requirements to work lazy loading and the tracking change:
public ICollection<Entity> Entities { get; set; }
If you want to learn more about the supported ways you can load related entities in EF I suggest you to read this article

One-To-Many AND Recursive relation - Force value to be set

Environment
Framework 4.5
Entity Framework 6 (code First)
Model
MainEntity One to many ChildEntity relationship
ChildEntity One to many recursive ChildEntity relationship
Model in code Snippet
public class MainEntity
{
public long Id { get; set; }
public virtual Collection<ChildEntity> ChildEntities { get; set; }
}
public class ChildEntity
{
public long Id { get; set; }
public MainEntity ParentMainEntity { get; set; }
public virtual Collection<ChildEntity> ChildEntities { get; set; }
}
Note : A ChildEntity can only ONE level deep again ChildEntities as childeren.
Problem
I am able to persist this model. This seems to work fine. Only one issue. When I store a ChildEntity that has a parent ChildEntity. The MainEntity_id field is NULL, only the ParentChildEntityId is set. The reason I want to have the MainEntity_Id field always set is for performance queries.
How can I force that the MAINENTITY_ID field has always a value set in the deeper level?
First:
You are giving yourself a hard time by leaving the foreign keys out of your POCO's. Adding a MainEntityId property to your ChildEntity will enable you to set the relation of new ChildEntities to a MainEntity.
Second:
var newChild =new ChildEntity();
parentEntity.ChildEntities.Add(newChild);
parentEntity.ParentMainEntity.ChildEntities.Add(newChild);
should work depending on how you have loaded the entities and which entities are or will be attached to the dbContext.
You just need to define your relationship between MainEntity and ChildEntity to be required. You may do it in two ways:
place [Required] attribute over ParentMainEntity property in ChildEntity
use fluent api. In your DbContext class override OnModelCreating method and in it place code:
modelBuilder.Entity().HasRequired(e => e.ParentMainEntity).WithMany(e => e.ChildEntities);
I would like as well to recommend you to make all your entities properties virtual. When all will be virtual then ef instead of working with your entity classes will create its own DynamicProxy classes deriving from your classes. They will provide additional tracking functionalities, they automatically change values of navigation properties if related objects changes etc. EF seems to deal much better with them. To use that functionality for newly created objects you will need to create them with context.ChildEntities.Create() method instead of using constructor. Of course as this adds constrain on your ChildEntity objects you may encounter exception during persisting data to db in SaveChanges. If the above change is the only one, that you've applied it is very probable that there is at least one ChildEntity object that do not have MainEntity object assigned to it.

Entity Framework Code First Relationships; what am I missing?

I'm experimenting with EF5 Code First and I am using the models (show below).
When I look at the database that is created, I am confused because I do not see anything in the Track table that points to the Category table. Category has a FK pointing back to Track but that means that there are going to be duplicates of the categories?
A little background: I am trying to build a model that has tracks and every track can have 1 to N Categories. All of the categories are already defined, that is they are basically a lookup and I plan to create them in the seed method when database is created.
I think I am not understanding something obvious... When I query a track, how will I know what category it contains?
Thx
public class Track : IAuditInfo
{
public Int32 Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public String Data { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
public ICollection<Category> Categories { get; set; }
public Track()
{
Categories = new List<Category>();
}
}
public class Category
{
public Int32 Id { get; set; }
public Boolean IsVisible { get; set; }
public String DisplayName { get; set; }
}
Your current model is a one-to-many relationship between tracks and categories.
This usually implemented, as you have noted that entity framework does, using a foreign key on the many side (category) to the one side (track).
If I understand you correctly, what you want is a many-to-many relationship. Many tracks can be related to the same category, and a single track can belong to many categories.
To let entity framework understand that you want a many-to-many relationship you can simply add a ICollection property to your category class.
So both your classes should have a collection of the other class.
I.e. tracks have many categories and categories have many tracks.
For more information you can also see: http://msdn.microsoft.com/en-us/data/hh134698.a.nospx
Olav is right, your data model at the moment is not telling Entity Framework that there is a many-to-many relationship in there.
The simplest way to resolve this is to add
public virtual ICollection<Track> Tracks { get; set; }
to your Category class.
However... You may not want to pollute your domain model with artefacts that are not relevant to your domain. More importantly, when you do it this way, it is up to Entity Framework to figure out what to call the binding table. Prior to EF6 this naming is non deterministic (see http://entityframework.codeplex.com/workitem/1677), which may mean that two different machines compiling the same code will decide on different names for that table and cause some interesting migration problems in your production system.
The answer to both problems is to always explicitly manage many-to-many relationships with Fluent Configuration.
In your Data Context class, override the OnModelCreating, something like this:
public class MyDb : DbContext
{
public IDbSet<Track> Tracks { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Track>()
.HasMany(t => t.Categories)
.WithMany()
.Map(c => c.ToTable("CategoriesForTracks"));
}
}
If you do this, you don't need to add a navigation property to your Category class, though you still can (if you do, you should use the overload for WithMany that allows you to specify a property).
Relationships between entities and how to map that to a relational database is inherently hard. For anything other than the simplest parent-child relationships you will want to use the fluent API to make sure you actually get what you want.
Morteza Manavi has a really good blog series describing relationships in EF Code First in exhaustive detail.
NOTE
You should usually make navigation properties virtual. So, you should change your Category class like this:
public virtual ICollection<Category> Categories { get; set; }
In theory, not making it virtual should just cause eager loading rather than lazy loading to happen. In practice I have always found lots of subtle bugs appearing when my navigation properties are not virtual.

Why is EF code-first generating an extraneous foreign key column?

I'm using entity framework code-first to create my database schema automatically, and one of my entities looks like this:
public class AssessmentsCaseStudies {
#region Persisted fields
[Required]
[Key, Column(Order=0)]
[ForeignKey("Assessment")]
public int AssessmentId { get; set; }
[Required]
[Key, Column(Order=1)]
[ForeignKey("CaseStudy")]
public int CaseStudyId { get; set; }
[Required]
public int Score { get; set; }
[ForeignKey("Follows")]
public int? FollowsCaseStudyId { get; set; }
#endregion
#region Navigation properties
public virtual Assessment Assessment { get; set; }
public virtual CaseStudy CaseStudy { get; set; }
public virtual CaseStudy Follows { get; set; }
#endregion
}
When EF auto-generates my database, it generates a table with the following columns:
AssessmentId (PK, FK, int, not null)
CaseStudyId (PK, FK, int, not null)
Score (int, not null)
FollowsCaseStudyId (FK, int, null)
CaseStudy_CaseStudyId (FK, int, null)
This is all fine apart from the CaseStudy_CaseStudyId column. Why has that been generated? What is it for? How can I stop it being generated? My suspicion is that EF can no longer automatically match up CaseStudy's ICollection<AssessmentsCaseStudies> with the CaseStudyId column, so it creates its own column to link the two together for that navigation property.
Because you have two navigation properties of type CaseStudy in your AssessmentsCaseStudies entity and an AssessmentsCaseStudies collection in your CaseStudy entity EF cannot decide which of the two CaseStudy navigation properties this collection refers to. Both could be possible and both options would result in a valid but different entity model and database schema.
In such an ambiguous situation the EF convention is to create actually three relationships, i.e. your collection in CaseStudy does not refer to any of the two CaseStudy navigation properties but has a third (but not exposed and "invisible") endpoint in AssessmentsCaseStudies. This third relationship is the reason for the third foreign key your are seeing in the database - the one with the underscore. (The underscore is always a strong indication that something happend by mapping convention and not by your explicit configuration or data annotations.)
To fix the problem and to override the convention you can apply the [InverseProperty] attribute, thereby specifying the CaseStudy navigation property the AssessmentsCaseStudies collection belongs to:
[InverseProperty("AssessmentsCaseStudies")] // the collection in CaseStudy entity
public virtual CaseStudy CaseStudy { get; set; }
You can also (alternatively, you don't need both) put the attribute on the collection side:
[InverseProperty("CaseStudy")] // the CaseStudy property in AssessmentsCaseStudies entity
public virtual ICollection<AssessmentsCaseStudies> AssessmentsCaseStudies { get; set; }
For some reason, Slauma's InverseProperty attribute suggestion didn't work. What did work was me specifying the relationship between the two CaseStudy navigation properties in AssessmentsCaseStudies, and the CaseStudy entity, via the Fluent API in my database context's OnModelCreating method:
modelBuilder.Entity<AssessmentsCaseStudies>()
.HasRequired(acs => acs.CaseStudy)
.WithMany(cs => cs.AssessmentsCaseStudies)
.HasForeignKey(acs => acs.CaseStudyId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AssessmentsCaseStudies>()
.HasOptional(acs => acs.Follows)
.WithMany() // No reverse navigation property
.HasForeignKey(acs => acs.FollowsCaseStudy)
.WillCascadeOnDelete(false);
Once that's added, the migration code that's generated when I Add-Migration no longer tries to add the CaseStudy_CaseStudyId column and I just get the FollowsCaseStudyId column added, with the appropriate foreign key relationship.
For anyone else landing here looking for a solution, if you've tried the previous answers and are still getting an extra foreign key column, look for any properties you may have defined further down your POCO class that you did not intend to map to DB fields. Even if they contain code blocks, as with complex get accessors, Entity Framework will try to map them to the database somehow. This may result in extra foreign key columns if your properties return entities. To be safe, either decorate such properties with the [NotMapped] attribute or convert them to methods.

Categories

Resources