Entityframework with an existing database - navigation properties not working - entity-framework

I have a set of Entities:
public class Board : EntityBase
{
public ICollection<Slot> Slots { get; set; }
}
public class Slot : EntityBase
{
public ICollection<Card> Cards { get; set; }
public string Header { get; set; }
public int BoardId { get; set; }
}
public class Card : EntityBase
{
public string Description { get; set; }
public string Title { get; set; }
public int SlotId { get; set; }
}
And corresponding database tables:
CREATE TABLE Boards
(
Id INT PRIMARY KEY,
UserId INT NOT NULL,
CONSTRAINT FK_Users_UserId FOREIGN KEY (UserId)
REFERENCES Users(Id)
)
CREATE TABLE Slots
(
Id INT PRIMARY KEY,
Header NVARCHAR(MAX),
BoardId INT NOT NULL,
CONSTRAINT FK_Slots_BoardId FOREIGN KEY (BoardId)
REFERENCES Boards(Id)
)
CREATE TABLE Cards
(
Id INT PRIMARY KEY,
Title NVARCHAR(MAX),
Description NVARCHAR(MAX),
SlotId INT NOT NULL,
CONSTRAINT FK_Cards_SlotId FOREIGN KEY (SlotId)
REFERENCES Slots(Id)
)
When attempting retrieving and instantiate a 'Board' from the database it's not populating the 'Slots' property. It seems that Entity framework is unable to recognise that there's a foreign key constraint. My understanding is that if the properties are not virtual they will be eager loaded, please correct me if i'm wrong.
Is there something that I'm missing / need to setup to make navigation properties work?
The calling code:
Context.Boards.Find(id);
My DbContext:
public class SampleContext : DbContext, IUnitOfWork
{
public SampleContext() : base("name=SampleApplication") { }
public void Save()
{
SaveChanges();
}
public DbSet<Board> Boards { get; set; }
public DbSet<Card> Cards { get; set; }
public DbSet<Slot> Slots { get; set; }
}
I have made the navigation properties virtual and loaded as follows, this is now working:
public Board GetBoard(int id)
{
var board = Context.Boards.Find(id);
Context.Entry(board)
.Collection(b => b.Slots)
.Load();
return board;
}

You must make navigation properties virtual for EF proxy to be able to override it.
And you're wrong about non-virtual properties to be eager loaded. They do not. You must load them explicitly with Include methods. Read here about it: https://msdn.microsoft.com/en-us/data/jj574232.aspx

Eager loading does not happen automatically like lazy loading does when you include the virtual keyword. You will need to use the Include() method
so something like
var graph = context.Boards.Include("Slots");
foreach(var board in graph)
{
Console.Writeline("Slot value {0}",board.Slots);
}

Related

Ef core and multiple parent entities use list of the same child entity?

Is there an easy way to have a setup like this in EF Core?
ProjectEntity
Id
Name
List<Notes>
CustomerEntity
Id
Name
List<Notes>
NotesEntity
Id
Date
Note
Every parent entity would have a one-to-many relation to same child entity. So I can not use normal behavior as
NotesEntity
Id
ParentId
Date
Note
I have some idea to have like above but also add one field that said what the parent entity is, is that the right way to do it or is there a better way? If I use this way I can't use EF Core normal behavior with one-to-many relationship? I need to make more manual work for search / add and so on?
Edit :
Entity Framework multiple parent tables I found this solution, but there I need to make a connection from my child to every parent I use, it could be alot of them.
Did also find a solution like :
BaseEntity
List<Notes>
ProjectEntity:BaseEntity
NotesEntity
Id
BaseEntityId
...
This last solution maybe is the best way to do it if I have alot of parent entities?
[EDIT 220922]
Could [Owned] type has collection of other Items? Or this feature won't work on owned entitys? I guess this behavior isn't supported?
[Owned]
public class Note
{
public int Id { get; set; }
public string Text { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public ICollection<string> Tags { get; set; }
}
I got an error on ICollection-row when I try to add-migration.
Unabel to determine the relationshop represented by navigation ... of
typ 'ICollection' Either manually configure the relationship, or
ignore this property using the '[NotMapped]' attribute.....
Maybe I could have one middleentity like :
public class NoteTagsEntity
{
public int Id { get; set; }
public ICollection<string> Tags { get; set; }
}
And then :
[Owned]
public class Note
{
public int Id { get; set; }
public string Text { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int NoteTagsId { get; set; }
public NoteTagsId NoteTagsId { get; set; }
}
Edit
I solved the Note functionality with having more FK's, one that point to Id of parent and one FK Id that point to what module that use that particular note. Here I don't have parent - child relation in my entities, I need to do this connection by myself but in this way it's easy to apply more modules that use note's later.
Use Owned Entity Types, and each entity will get its own notes table.
eg
public abstract class Entity
{
public int Id { get; set; }
}
public abstract class EntityWithNotes: Entity
{
public virtual ICollection<Note> Notes { get; set; }
}
[Owned]
public class Note
{
public int Id { get; set; }
public string Text { get; set; }
}
public class Project : EntityWithNotes
{
public string Name { get; set; }
}
public class Customer : EntityWithNotes
{
public string Name { get; set; }
}
creates
CREATE TABLE [Customer_Notes] (
[Id] int NOT NULL IDENTITY,
[CustomerId] int NOT NULL,
[Text] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Customer_Notes] PRIMARY KEY ([CustomerId], [Id]),
CONSTRAINT [FK_Customer_Notes_Customer_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customer] ([Id]) ON DELETE CASCADE
);
CREATE TABLE [Project_Notes] (
[Id] int NOT NULL IDENTITY,
[ProjectId] int NOT NULL,
[Text] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Project_Notes] PRIMARY KEY ([ProjectId], [Id]),
CONSTRAINT [FK_Project_Notes_Project_ProjectId] FOREIGN KEY ([ProjectId]) REFERENCES [Project] ([Id]) ON DELETE CASCADE
);

EF zero-to-one FLUENT API Foreign Key on a NON Key

I have a legacy database which has broken all the rules of Codd. Here are the entities
class Item {
[Key]
public int ItemId {get;set;}
public string ItemNo {get;set; }
[ForeignKey("ItemId")]
public virtual NumericItem {get;set;} //navigation
}
class NumericItem { //This is a subset of the Item entity
[ForeignKey("ItemId")]
public Item Item {get; set;}
[Key]
public int ItemNo { get; set; } //this is a primary key, different type
public int ItemId { get; set; } //this is also a primary key and a foreign key
}
How do I tell EF Code first using Fluent API that NumericItem always has a Item and Item may or may not have a NumericItem. The cardinality is always zero/one
This is the case of the foreign unique key.
Normally, when you have a principal entity (like Item) and an optional dependent (NumericItem) in a relationship of 0 or 1, the dependent primary key is also the foreign key for the principal. In your case, since database is already like that, you could do like this:
public class Item
{
public int ItemId { get; set; }
public string ItemNo { get; set; }
public virtual NumericItem NumericItem {get;set;} //navigation
}
public class NumericItem
{ //This is a subset of the Item entity
public Item Item { get; set; }
public int ItemNo { get; set; } //this is a primary key, different type
}
public class NumericItemConfiguration : EntityTypeConfiguration<NumericItem>
{
public NumericItemConfiguration()
{
HasKey(n => n.ItemNo);
HasRequired(n => n.Item).WithOptional(i => i.NumericItem).Map(m => m.MapKey("ItemId"));
}
}
public class MyContextContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// do your stuff, and add your configuration here...
modelBuilder.Configurations.Add(new NumericItemConfiguration());
}
}
or you can do it without this NumericItemConfiguration class, doing the config directly in your OnModelCreating method:
public class MyContextContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// do your stuff, and add your configuration here...
modelBuilder.Entity<NumericItem>().HasKey(n => n.ItemNo);
modelBuilder.Entity<NumericItem>().HasRequired(n => n.Item).WithOptional(i => i.NumericItem);
}
}
Take note I had to remove your ItemId property from NumericItem class, otherwise EF would complain like this:
ItemId: Name: Each property name in a type must be unique. Property
name 'ItemId' is already defined.

Entity Framework - Code First - Update won't change Foreign Key

I just can't find any code that changes the foreign key. How do I tell the context that the foreign key has changed so that it updates the database? I have been attempting to get this to work for 2 months now:
These are the models:
namespace MyApp.WebApi.Models
{
public class Project
{
public int ProjectId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
// Foreign Key - Project Status
public virtual ProjectStatus ProjectStatus { get; set; }
}
public class ProjectStatus
{
public int ProjectStatusId { get; set; }
public string Name { get; set; }
}
public class HerculesWebApiContext : DbContext
{
public HerculesWebApiContext() : base("name=HerculesWebApiContext") { }
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectStatus> ProjectStatuses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) { }
}
}
This is the controller for the Web API:
namespace MyApp.WebApi.Controllers
{
public class ProjectController : ApiController
{
private HerculesWebApiContext db = new HerculesWebApiContext();
public void PutProject(int id, [FromBody]Project project)
{
if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest);
if (id != project.ProjectId) throw new HttpResponseException(HttpStatusCode.BadRequest);
// I need some code here to tell EF that the FK has changed
db.Entry(project).State = EntityState.Modified;
// Try catch around this
db.SaveChanges();
}
The above code will not update the Project Status Foreign Key even if it has changed in the JSON object that is passed to the function. The object is returned in the same format that the Web API provided it but with the Project Status object updated:
[{"ProjectStatus":{"ProjectStatusId":2,"Name":"Started"},"ProjectId":3,"Description":"test description","Name":"test project"}]
I have tried:
db.Projects.Attach(db.Projects.Single(c => c.ProjectId == project.ProjectId));
((IObjectContextAdapter)db).ObjectContext
.ApplyCurrentValues("Projects", project);
I have also tried:
db.Entry(project.ProjectStatus).CurrentValues.SetValues(new ProjectStatus { ProjectStatusId = project.ProjectStatus.ProjectStatusId });
I have also tried this:
var per = new ProjectStatus { ProjectStatusId = project.ProjectStatus.ProjectStatusId }; // create the stub
db.ProjectStatuses.Attach(per);
db.Entry(project.ProjectStatus).CurrentValues.SetValues(per);
These are the table T-SQL files that are automatically created from my models:
Projects Table:
CREATE TABLE [dbo].[Projects] (
[ProjectId] INT IDENTITY (1, 1) NOT NULL,
[Description] NVARCHAR (MAX) NULL,
[Name] NVARCHAR (MAX) NULL,
[ProjectStatus_ProjectStatusId] INT NOT NULL,
CONSTRAINT [PK_dbo.Projects] PRIMARY KEY CLUSTERED ([ProjectId] ASC),
CONSTRAINT [FK_dbo.Projects_dbo.ProjectStatus_ProjectStatusId] FOREIGN KEY ([ProjectStatus_ProjectStatusId]) REFERENCES [dbo].[ProjectStatus] ([ProjectStatusId]) ON DELETE CASCADE
Project Statuses Table
CREATE TABLE [dbo].[ProjectStatus] (
[ProjectStatusId] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_dbo.ProjectStatus] PRIMARY KEY CLUSTERED ([ProjectStatusId] ASC)
);
You need to do two things. First explicitly state your primary keys for each class.
Add the [Key] attribute to
Project class, above the line public int ProjectId { get; set; }
ProjectStatus class, above the line public int ProjectStatusId { get; set; }
Secondly add the property for your foreign key to your Project class. Add this line:
public int ProjectStatusId { get; set; }

How to use Code first Migration for Primary Key?

At the biginning, I defined the model like this:
public class Category
{
public long CategoryId { get; set;}
public string CategoryName { get; set; }
public virtual ICollection<ContentInfo> Contents { get; set; }
}
Public class Article
{
public int ContentId { get; set; }
public string Content { get; set; }
[ForeignKey("Category")]
public long CategoryId { get; set; }
public virtual Category Category { get; set; }
}
After generating the database from the model with Automatic-Migration, I changed the CategoryId's type from "long" to "int", and update the database with Automatic-Migration again.
This time an exception was throwed, telling me The column "CategoryId" was referenced by Primary key and Foreign Key, so the migration is failed. If i delete the Primary key and Foreign Key manually, everything is ok. but i want the Automatic-Migration to do this for me, can it?
Havent tries such a migration myself, but i recall seeing an option
public class MYMigrationConfiguration : DbMigrationsConfiguration
{
public MyMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true; //have you tried this ?
}
Otherwise you may need to do a code based migration for this type of change
You can use the "Sever Explore" and manually manipulate the table structure, e.g., dropping the key constraints, or delete the whole table. And let EF to generate the brand new table definitions.

How do I setup a foreign key relationship in codefirst without using a navigation property?

Say you have an order class with an order status, I want to declare the OrderStatusId inside the OrderStatus class. However, no foreign key relationship is set-up by default. If I use [ForeignKey] attribute on the column it seems to demand a navigation property which I don't want as this would mean having to carry out joins on the navigation property in all of my queries just to check the status.
How do I accomplish this in EF codefirst? Define a property as a foreign key without using a navigation property.
public class Order
{
public int OrderId;
public int OrderStatusId;
// properties...
}
public class OrderStatus
{
public int OrderStatusId;
public string Status;
}
You always need navigation property on at least one side to build a relation. If you don't have navigation properties you have nothing to bind your your foreign key with and it will remain as common column.
Create your model like this instead
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string StreetAddress { get; set; }
//etc...
//navigation properties
public virtual List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public string OrderStatus { get; set; }
//navigation properties
public virtual Customer OrderedBy { get; set; }
//etc..
}
EF will create your foreign keys on its own using you navigation properties
no reason to expose them to the model as it is unnecessary you can access the id if necessary using the navigation properties