Entity Framework Code First - Many-to-many with a single class - entity-framework

I have a navigation menu provider that I am trying to move over to EF Code First (EF4, CPT5) using a MenuItem object. This is more of an exercise to get comfortable with building different relationships with EF Code First than anything else.
My MenuItem has a field called SubMenuItems that is a collection of MenuItems. When I use EF Code First (without modifying the classes) the table that is created for the MenuItems adds a column for the parent menu item. The menu will display properly, but this removes any ability to have a menu item appear in more than one sub menu. What is the proper way to tell EF Code First that I want each MenuItem to be a standalone item and to create another table that links a menu item's SubMenuItems to other MenuItems?
public class MenuItem
{
public int ID { get; set; }
public virtual SubMenuItems { get; set; }
public string Text { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public bool IsRoot { get; set; }
public bool RequiresAuthentication { get; set; }
public virtual ICollection<MenuPermission> RequiredPermissions { get; set; }
public bool HiddenIfAuthenticated { get; set; }
public int DisplayOrder { get; set; }
}
...
public class MyDbContext : DbContext
{
public DbSet<MenuItems> MenuItems { get; set; }
public DbSet<MenuItemPermission> MenuItemPermissions { get; set; }
...
}
I have attempted to override OnModelCreating but each attempt ended in either being broken or doing the exact same thing that it does without me touching OnModelCreating. I'm sure I'm just "doing it wrong."
Thanks!

You need to setup a Many-to-Many Self Referencing Association for MenuItem entity so that each MenuItem could have multiple Parent Items:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MenuItem>()
.HasMany(m => m.SubMenuItems)
.WithMany();
}

Related

Scaffolding MVC Controller - how to indicate dataValueField and dataTextField?

One of the overload methods of SelectList (from the Microsoft.AspNetCore.Mvc.Rendering namespace) is defined as:
public SelectList(IEnumerable items, string dataValueField, string dataTextField);
When I scaffold an "MVC Controller with view, using Entity Framework" and I create my CRUD pages, I may see the following method inside of my Controller:
public IActionResult Create()
{
ViewData["Continent"] = new SelectList(_context.Continent, **"ContinentID", "ContinentID"**);
ViewData["Country"] = new SelectList(_context.Country, **"CountryID", "CountryName"**);
return View();
}
The field supplied to the dataTextField parameter is different between Continent/Country. How does MVC/EntityFramework decide which field to supply to dataTextField when scaffolding a Controller? Is there something in the individual models or in the DbContext that I am overlooking? I'd like for the dataTextField of Continent to be "ContinentName" so that I don't have to change it manually in the future when I need to delete and then re-scaffold the Controller.
Edit:
Here are the model definitions:
The Model of the Controller that I posted above:
using System;
using System.Collections.Generic;
namespace Project.Models
{
public partial class ProjectForm
{
public int ProjectFormID { get; set; }
public int ContinentID { get; set; }
public int CountryID { get; set; }
public virtual Continent ContinentNavigation { get; set; }
public virtual Country CountryNavigation { get; set; }
}
}
The one that displays the "CountryName" in the dataTextField the way that I want to see it:
namespace Project.Models
{
public partial class Country
{
public int CountryID { get; set; }
public string CountryName { get; set; }
public virtual ICollection<ProjectForm> ProjectForm { get; set; }
}
}
The one that displays the "ContinentID" in the dataTextField the way that I do NOT want to see it:
namespace Project.Models
{
public partial class Continent
{
public int ContinentID { get; set; }
public string ContinentName { get; set; }
public virtual ICollection<ProjectForm> ProjectForm { get; set; }
}
}
There is nothing obviously different to me in the model definitions unfortunately.
I stumbled across this post today (a bit late), but see it still hasn't been answered.
While I can't say why the scaffolding chose to use one field over another in your scenarios (unless you initially had your class/model written differently the last time you cleaned/built your project), I can say how to force it to use a specific column.
Add the DisplayColumn attribute to your class. You will need to rebuild before scaffolding again for the change to take.
namespace Project.Models
{
[DisplayColumn("ContinentName")]
public partial class Continent
{
public int ContinentID { get; set; }
public string ContinentName { get; set; }
public virtual ICollection<ProjectForm> ProjectForm { get; set; }
}
}

Navigation Properties Null until DbSet is called

Setup
I'm getting into Entity Framework Core (2.1) and I'm having trouble working with the relationships set up. To keep it simple I've set up a simple one-to-one relationship between a person and address table:
public class Person
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public PersonAddress Address { get; set; }
}
public class PersonAddress
{
public int ID { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public int ZipCode { get; set; }
public int PersonID { get; set; }
public virtual Person Person { get; set; }
}
With a simple explicit link between the two (to make sure this isn't an issue in how things are connected):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOne(p => p.Address)
.WithOne(a => a.Person);
modelBuilder.Entity<PersonAddress>()
.HasOne(a => a.Person)
.WithOne(p => p.Address);
}
Here's my issue
This project is set up in an asp.net core app and I've got a breakpoint set up at the end of my "PeopleController" constructor. I've also added a line to take the first person out of the DbSet and assign it to a variable:
public PeopleController(SmallGroupsSiteContext context)
{
_context = context;
Person miles = context.Person.First();
Debug.Print(miles.Address.ToString());
}
When I look into my locals and look at the variable "miles" its address field's value is Null. Furthermore if I were to make a call to the DbSet Addresses in my immediate window, the value for Address in the "miles" object gets set to the correct value.
What's going on? When does Entity actually set Navigation Properties? Should I be making calls to other tables as I'd like the data populated? Is there something lazy going on in the background that I need to change?
Oops it looks like I was just a few Google searches away...
It looks like I should have been using the .Include Extension as I was loading the DbSet like so:
public PeopleController(SmallGroupsSiteContext context)
{
_context = context;
Person miles = context.Person.Include(person => person.Address).First();
Debug.Print(miles.Address.ToString());
}

Entity Framework modelling one-to-many relationship [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I don't know how to configure following relations in EF:
Imagine I need to create some language dictionary model. I have an item (word for example) in one language and in another language. There is some relation between these two items.
For instance: "hund" (German) -> "dog" (English), relation type is "Translate"
public enum Language
{
English,
German,
}
public class Item
{
public long ID { get; set; }
[Required]
public string Value { get; set; }
[Required]
public Language Language { get; set; }
public virtual ICollection<ItemRelation> ItemRelations { get; set; }
}
public enum ItemRelationType
{
Translate,
Synonym,
}
public class ItemRelation
{
public long ID { get; set; }
[ForeignKey("ItemID")]
public Item Item { get; set; }
[ForeignKey("RelativeItemID")]
public Item RelativeItem { get; set; }
[Required]
public ItemRelationType Type { get; set; }
}
EF standard migration throw some error in one case or creates columns or FKs I don't wont (Item_ID etc.) in the other.
I guess I need to configure some fluent api - but I am not sure how...
You are missing the actual FK fields ItemID and RelativeItemID:
And to configure the navigation property you can use InverseProperty attribute, along with disabling a bit of EF convention(shown below).
public class ItemRelation
{
public long ID { get; set; }
public long ItemID { get; set; } // Missing
[ForeignKey("ItemID")]
[InverseProperty("ItemRelations")]
public virtual Item Item { get; set; }
public long RelativeItemID { get; set; } // Missing
[ForeignKey("RelativeItemID")]
[InverseProperty("RelativeItemRelations")]
public virtual Item RelativeItem { get; set; }
[Required]
public ItemRelationType Type { get; set; }
}
The declaration above uses virtual so that lazy loading works. It's not necessary and you can remove it. The consequence is that lazy loading won't work, which is ok too.
Assuming you want a navigation property for the second relation you need to add the property:
public class Item
{
...
public virtual ICollection<ItemRelation> RelativeItemRelations { get; set; }
...
}
And then disable the cascading delete convention by overriding OnModelCreating, if you haven't already, in your context class as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
...
This solution should work but it is effectively disabling cascade delete to all one to many relationships. The upside is that you can get it back on a case by case basis by using fluent api.
The second way to achieve what you want is to just use fluent api as follows:
Add the second navigation property to your Item entity:
public class Item
{
...
public virtual ICollection<ItemRelation> RelativeItemRelations { get; set; }
...
}
ItemRelation entity is missing the FKs, so here it is:
public class ItemRelation
{
public long ID { get; set; }
public long ItemID { get; set; } // Missing
public virtual Item Item { get; set; }
public long RelativeItemID { get; set; } // Missing
public virtual Item RelativeItem { get; set; }
[Required]
public ItemRelationType Type { get; set; }
}
And to configure the navigation property, and avoid the cascading issue, you just define the relationship using fluent api:
public TheContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<ItemRelation> ItemRelations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ItemRelation>()
.HasRequired(e => e.Item)
.WithMany(t => t.ItemRelations)
.HasForeignKey(e => e.ItemID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<ItemRelation>()
.HasRequired(e => e.RelatedItem)
.WithMany(t => t.RelativeItemRelations)
.HasForeignKey(e => e.RelativeItemID)
.WillCascadeOnDelete(false);
// Uncomment the following if you want to disable all cascading deletes and automatic fk creation conventions
// modelBuilder.Conventions.Remove<ForeignKeyIndexConvention>();
// modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
...
}
}
Read here for an opinion on why you might consider disabling those conventions.
I think you might be able to get away with this:
public class ItemRelation
{
public long Id { get; set; }
[ForeignKey("PrimaryItemId")]
public Item Item { get; set; }
public long PrimaryItemId { get; set; }
[ForeignKey("RelatedItemId")]
public Item RelatedItem { get; set; }
public long RelatedItemId { get; set; }
public ItemRelationType RelationType;
}
Notice that this class now has TWO relations to the Item entity, resulting in two foreign keys. Notice that each Item property has a [ForeignKey] attribute, with the string argument specifying the long to use as the foreign key column.
View this answer as a nudge on a different track. Research this topic more to see if it fits your use case.

EF6, DTO, update navigation property

I am using Entity Framework 6 with Generic Repository and DTOs.
I want to create new entities via navigation property.
Here is my model:
public partial class Project
{
public Project()
{
this.ProjectAssets = new List<ProjectAsset>();
}
public int ProjectID { get; set; }
public string Name { get; set; }
public virtual ICollection<ProjectAsset> ProjectAssets { get; set; }
}
public partial class Asset
{
public Asset()
{
this.Revisions = new List<Revision>();
}
public int AssetID { get; set; }
public string Name { get; set; }
public short Type { get; set; }
public virtual ICollection<Revision> Revisions { get; set; }
}
public partial class ProjectAsset
{
public int MappingID { get; set; }
public int ProjectID { get; set; }
public int AssetID { get; set; }
public virtual Asset Asset { get; set; }
}
I have already created Project. And if i am creating new Asset, then create Project Asset with AssetID from just created Asset, it's OK, but i have to re-fetch Project from DB.
I want to do it in one transaction, like that:
Project.ProjectAssets.Add(new ProjectAsset(new Asset((short)type, fileName)));
ServiceLocator.Default.ResolveType<IPipeLine>().Update(Project);
public void Update<TEntity>(TEntity entity) where TEntity : class
{
var fqen = GetEntityName<TEntity>();
object originalItem;
var key = ((IObjectContextAdapter)DbContext).ObjectContext.CreateEntityKey(fqen, entity);
if (((IObjectContextAdapter)DbContext).ObjectContext.TryGetObjectByKey(key, out originalItem))
((IObjectContextAdapter)DbContext).ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
//DbContext.Entry(entity).State = EntityState.Modified;
}
But after SaveChanges there is no record in DB, and MappingID still 0.
I thought that ApplyCurrentValues must work with Navigation Properties.
Is there any good way to solve that problem?
EDIT:
I accessing DAL throughBusiness Entities with contain the same properties, but they also implement INotifyPropertyChanged and other WPF stuff. So i think i can subscribe to CollectionChanged event and manualy create/delete entities from navigation property. And in property setters i can call update, but i think it can strongly decrease perfomance.

Entity Framework Many-to-many relationships, with the same link entity

I am trying to get some reuse out of a class which holds the many-to-many relationship data between a number of entities.
You can do this easily enough where the relationship is simply the pks of the two entities using Map
class A{
icollection<Item> Items
}
class B{
icollection<Item> Items
}
class Item{
string Text
}
then in the config you do something like this
Entity<A>().HasMany(e=>e.Items).WithMany().Map(..);
Entity<B>().HasMany(e=>e.Items).WithMany().Map(..);
this produces a new link table for A and B
Now, I would like to store more info on the link tables, but this would be common
class A{
icollection<LinkItem> Items
}
class B{
icollection<LinkItem> Items
}
class LinkItem{
int ExtraInfo
Item Text
}
class Item{
string Text
}
This fails as the LinkItem is created in a single table and complains about relationships
"Entities in 'DataContext.LinkItem' participate in the 'A_Items' relationship. 0 related 'A_Items_Source' were found. 1 'A_Items_Keywords_Source' is expected."
You can specify a table in the Map method, m.ToTable("AItems"), but this fails too
"The specified table 'AItems' was not found in the model. Ensure that the table name has been correctly specified."
Am I allowed to reuse my LinkItem class somehow?
(It doesn't need to be accessed as a set from the DataContext)
I believe what you want can be done, I've just tried using EF5, with the following model.
public class UsersContext : DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
public DbSet<LinkItem> LinkItems { get; set; }
public DbSet<Item> Items { get; set; }
}
public class A{
public virtual int Id { get; set; }
public virtual ICollection<LinkItem> Items { get; set; }
}
public class B{
public virtual int Id { get; set; }
public virtual ICollection<LinkItem> Items { get; set; }
}
public class LinkItem{
public virtual int Id { get; set; }
public virtual DateTime ExtraInfo { get; set; }
public virtual Item Text { get; set; }
}
public class Item{
public virtual int Id { get; set; }
public virtual string Text { get; set; }
}
This resulted in the following DB model
I've not added any custom mappings just used what is inferred by the model. Does that work for you?
If you want to avoid the proliferation of the foreign keys on the LinkItems table, you can make it a many-2-many relationship with entities A and B with the following DbModelBuilder commands which will add the implicit link tables.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<A>()
.HasMany(t => t.Items)
.WithMany();
modelBuilder.Entity<B>()
.HasMany(t => t.Items)
.WithMany();
}