how to annotate a parent-child relationship with Code-First - entity-framework

When using the CTP 5 of Entity Framework code-first library (as announced here) I'm trying to create a class that maps to a very simple hierarchy table.
Here's the SQL that builds the table:
CREATE TABLE [dbo].[People]
(
Id uniqueidentifier not null primary key rowguidcol,
Name nvarchar(50) not null,
Parent uniqueidentifier null
)
ALTER TABLE [dbo].[People]
ADD CONSTRAINT [ParentOfPerson]
FOREIGN KEY (Parent)
REFERENCES People (Id)
Here's the code that I would hope to have automatically mapped back to that table:
class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
class FamilyContext : DbContext
{
public DbSet<Person> People { get; set; }
}
I have the connectionstring setup in the app.config file as so:
<configuration>
<connectionStrings>
<add name="FamilyContext" connectionString="server=(local); database=CodeFirstTrial; trusted_connection=true" providerName="System.Data.SqlClient"/>
</connectionStrings>
</configuration>
And finally I'm trying to use the class to add a parent and a child entity like this:
static void Main(string[] args)
{
using (FamilyContext context = new FamilyContext())
{
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred"
};
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
Parent = fred
};
context.People.Add(fred);
var rowCount = context.SaveChanges();
Console.WriteLine("rows added: {0}", rowCount);
var population = from p in context.People select new { p.Name };
foreach (var person in population)
Console.WriteLine(person);
}
}
There is clearly something missing here. The exception that I get is:
Invalid column name 'PersonId'.
I understand the value of convention over configuration, and my team and I are thrilled at the prospect of ditching the edmx / designer nightmare --- but there doesn't seem to be a clear document on what the convention is. (We just lucked into the notion of plural table names, for singular class names)
Some guidance on how to make this very simple example fall into place would be appreciated.
UPDATE:
Changing the column name in the People table from Parent to PersonId allows the Add of fred to proceed. Howerver you'll notice that pebbles is a added to the Children collection of fred and so I would have expected pebbles to be added to the database as well when Fred was added, but such was not the case. This is very simple model, so I'm more than a bit discouraged that there should be this much guess work involved in getting a couple rows into a database.

You need to drop down to fluent API to achieve your desired schema (Data annotations wouldn't do it). Precisely you have an Independent One-to-Many Self Reference Association that also has a custom name for the foreign key column (People.Parent). Here is how it supposed to get done with EF Code First:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOptional(p => p.Parent)
.WithMany(p => p.Children)
.IsIndependent()
.Map(m => m.MapKey(p => p.Id, "ParentID"));
}
However, this throws an InvalidOperationException with this message Sequence contains more than one matching element. which sounds to be a CTP5 bug as per the link Steven mentioned in his answer.
You can use a workaround until this bug get fixed in the RTM and that is to accept the default name for the FK column which is PersonID. For this you need to change your schema a little bit:
CREATE TABLE [dbo].[People]
(
Id uniqueidentifier not null primary key rowguidcol,
Name nvarchar(50) not null,
PersonId uniqueidentifier null
)
ALTER TABLE [dbo].[People] ADD CONSTRAINT [ParentOfPerson]
FOREIGN KEY (PersonId) REFERENCES People (Id)
GO
ALTER TABLE [dbo].[People] CHECK CONSTRAINT [ParentOfPerson]
GO
And then using this fluent API will match your data model to the DB Schema:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOptional(p => p.Parent)
.WithMany(p => p.Children)
.IsIndependent();
}
Add a new Parent record containing a Child:
using (FamilyContext context = new FamilyContext())
{
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
};
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred",
Children = new List<Person>()
{
pebbles
}
};
context.People.Add(fred);
context.SaveChanges();
}
Add a new Child record containing a Parent:
using (FamilyContext context = new FamilyContext())
{
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred",
};
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
Parent = fred
};
context.People.Add(pebbles);
var rowCount = context.SaveChanges();
}
Both codes has the same effect and that is adding a new parent (Fred) with a child (Pebbles).

In Entity Framework 6 you can do it like this, note public Guid? ParentId { get; set; }. The foreign key MUST be nullable for it to work.
class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid? ParentId { get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
https://stackoverflow.com/a/5668835/3850405

It should work using a mapping like below:
class FamilyContext : DbContext
{
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Person>().HasMany(x => x.Children).WithMany().Map(y =>
{
y.MapLeftKey((x => x.Id), "ParentID");
y.MapRightKey((x => x.Id), "ChildID");
});
}
}
However that throws an exception: Sequence contains more than one matching element.
Apperently that is a bug.
See this thread and the answer to #shichao question: http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx#10102970

class Person
{
[key()]
public Guid Id { get; set; }
public String Name { get; set; }
[ForeignKey("Children")]
public int? PersonId {get; set;} //Add ForeignKey
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
builder.Entity<Menu>().HasMany(m => m.Children)
.WithOne(m => m.Parent)
.HasForeignKey(m => m.PersonId);

Related

Asp net - Delete record from db using OnDelete()

Trying to create simple CRUD app using Asp Net Core. I have 2 entities:
Department and Employee( one to many ). I need to delete record from Department table. But when Im trying to delete record using OnDelete(DeleteBehavior.Restrict) or OnDelete(DeleteBehavior.ClientSetNull) i have exception:
UPDATE or DELETE in table"Departments" violates foreign key constraint
"FK_Employees_Departments_DepartmentCode" table"Employees"
How can i fix this problem ?
Entity Employee:
public class Employee
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required(ErrorMessage = "Input fullname of employee")]
public string FullName { get; set; }
[Required(ErrorMessage = "Input date of birth")]
public DateTime DateOfBirth { get; set; }
[Required(ErrorMessage = "Input code")]
public string Code { get; set; }
[Required(ErrorMessage = "Input fullname of employee")]
public int Salary { get; set; }
public string DepartmentCode { get; set; }
public Department Department { get; set; }
}
Entity Department:
public class Department
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required(ErrorMessage = "Input name of department")]
public string Name { get; set; }
[Required(ErrorMessage = "Input code of department")]
public string Code { get; set; }
public ICollection<Employee> Employees { get; set; }
public Department()
{
Employees = new List<Employee>();
}
}
Context class settings:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>()
.HasMany<Employee>(d => d.Employees)
.WithOne(e => e.Department)
.HasForeignKey(e => e.DepartmentCode)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Department>()
.HasKey(d => d.Code);
modelBuilder.Entity<Employee>()
.HasKey(e => e.Code);
modelBuilder.Entity<Department>()
.HasIndex(d => d.Name).IsUnique();
}
Of all fields in the DeleteBehavior enum only two actually add cascaded foreign key behavior to the database: Cascade and SetNull. All other options create foreign keys with no action on delete, but differ in what EF will do to its tracked entities.
In your case it should probably be SetNull because I assume that Employees can exist without Department. This setting will allow you to delete a Department object without loading its Employees. The database will set their DepartmentCode to null.
The delete behavior configured in EF can only be applied tho Entities that are tracked by EF change tracking. So you would need to load all Employees that belong to the department to make this work as expected.
BUT The database foreign key definition also defines the on delete action (cascading, set null, do nothing) So even if you code within your context a set null strategy, the DB still might apply different strategy for on delete. EF core defaults to cascade delete.

Entity Framework - Inserting model with many to many mapping

How can I insert a model Tag that belongs to a model Post when I have the models setup like this:
Post
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new List<Tag>();
}
}
Tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
This question suggests to create a Post object then add Tags to the Tags collection, I couldn't get it working:
Insert/Update Many to Many Entity Framework . How do I do it?
I want to add Tag to Post already in the database, how can I do that with EF. I'm new to EF.
This is what I've tried, if I send this to the API it doesn't insert any records and I can see that the new tag Id = 0 which doesn't exist in the database, but I'd think that'd cause a foreign key constraint error, not sure If I need to do something to auto generate Id for the tag:
{
Name: "test"
}
API
[ResponseType(typeof(Tag))]
public IHttpActionResult PostTag(Tag tag)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var post = new Post();
var tags = new List<Tag>();
tags.Add(tag);
post.Tags.Add(tag);
post.Id = 10;
db.Entry(post).State = EntityState.Modified;
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = tag.Id }, tag);
}
If you said there is Many-To-Many relation which the PostTag is connection table between Tag and Post then your models don't show any many-to-many relation, so from what I have seen there is
one-to-many between Post and Tag because of your model definition.
if you want to make many-to-many relation between them you have to something like below:
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public Tag()
{
Posts = new HashSet<Post>();
}
}
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new HashSet<Tag>();
}
}
and in OnModelCreating make relation by fluent api as below :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>()
.HasMany(s => s.Posts)
.WithMany(c => c.Tags)
.Map(cs =>
{
cs.MapLeftKey("TagId");//TagId
cs.MapRightKey("PostId");//PostId
cs.ToTable("PostTag");
});
}
or vice versa
modelBuilder.Entity<Post>()
.HasMany(s => s.Tags)
.WithMany(c => c.Posts)
.Map(cs =>
{
cs.MapLeftKey("PostId");//PostId
cs.MapRightKey("TagId");//TagId
cs.ToTable("PostTag");
});
as you can see and know there should be a table named PostTag in database which have two columns as keys which have a script like :
CREATE TABLE [dbo].[PostTag](
[TagId] [int] NOT NULL,
[PostId] [int] NOT NULL,
CONSTRAINT [PK_PostTag] PRIMARY KEY CLUSTERED
(
[TagId] ASC,
[PostId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[PostTag] WITH CHECK ADD CONSTRAINT [FK_PostTag_Post] FOREIGN KEY([PostId])
REFERENCES [dbo].[Post] ([Id])
GO
ALTER TABLE [dbo].[PostTag] CHECK CONSTRAINT [FK_PostTag_Post]
GO
ALTER TABLE [dbo].[PostTag] WITH CHECK ADD CONSTRAINT [FK_PostTag_Tag] FOREIGN KEY([TagId])
REFERENCES [dbo].[Tag] ([TagId])
GO
ALTER TABLE [dbo].[PostTag] CHECK CONSTRAINT [FK_PostTag_Tag]
GO
take a look at here for more info.
UPDATE:
if you want establish zero-to-many relation between Post and Tag then the models should be like below:
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public int? PostId { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new HashSet<Tag>();
}
}
and one to many relation with fluent api :
modelBuilder.Entity<Post>()
.HasMany(o1 => o1.Tags);
as your comment you don't want Tag have navigate property so you should define a property as Nullable in Tag which is feign key, if there is a relation between them you should establish relation at least by navigate property or Nullable property.
The correct answer came from here (with no change):
You want to assign a tag to exist post you should find that Post firstly then add a tag to it, if that tag does exist in DB a relation will be made between Tag and found Post if that Tag does not exist then Tag will be inserted to DB,
Take a look at this :
var post = context.Posts.SingleOrDefault(x => x.Id == 4);//That Id would be you specific Post Id
var existTag = context.Tags.SingleOrDefault(x => x.Id == 1); //Exist Tag in DB
post.Tags.Add(existTag);
context.SaveChanges();
//Inserting new tag to DB and assign it to Post also
Tag newTag = new Tag // Does not exist in DataBase
{
Name = "tag2"
};
post.Tags.Add(newTag);// By this tag2 will be insert into Tag table first and then added to post (insert new record to PostTag)
context.SaveChanges();

Adding an entity without knowing the parent id

I need to insert a new entity without knowing it's PK. The parent entity has another property which is a guid and unique which is what we use to do cross db references and this is all I have. I have done it in the past but can't find a reference on how to do it again.
[Table("School")]
public class SchoolEntity
{
public SchoolEntity()
{
Students = new HashSet<StudentEntity>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public Guid ExternalId { get; set; }
[ForeignKey("SchoolId")]
public virtual ICollection<StudentEntity> Students { get; set; }
}
[Table("Student")]
public class StudentEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public SchoolEntity School { get; set; }
}
//ExternalId won't work cause is not the primary key.
var school = new School { ExternalId = Guid.Parse('68e05258-550a-40f3-b68a-5d27a0d825a0') };
context.Attach(school);
context.Schools.Add.Add(new Student());
context.SaveChanges();
Well, the PK of the referenced entity is required in order to set properly the FK of the referencing entity.
If you don't have it, apparently you should find it (get it from the database) based on what you have (secondary identifier in your case). For instance:
var school = context.Schools.Single(e => e.ExternalId == externalId);
var student = new Student { School = school, ... };
context.Students.Add(student);
context.SaveChanges();
There is no way to get that working without fetching. If you don't want to fetch the whole referenced entity (and you are sure it's not tracked by the context), then you can fetch the PK only and Attach a stub entity:
var schoolId = context.Schools.Where(e => e.ExternalId == externalId)
.Select(e => e.Id).Single();
var school = new School( Id = schoolId);
context.Attach(school);
// ...

Conflict with relationship and foreign key 1-1

I'm trying to do this relationship
public class Company {
public int Id { get; set; }
public Configuration Configuration { get; set; }
}
public class Configuration {
public int Id { get; set; }
// public int CompanyId { get; set; } -> can't do this
public Company Company { get; set; }
}
public class ConfigurationMapping : EntityTypeConfiguration<Configuration> {
public ConfigurationMapping {
HasRequired(configuration => configuration.Company)
.WithOptional(company => company.Configuration)
// .HasForeignKey(configuration => configuration.CompanyId) -> this doesn't exist
.Map(f => f.MapKey("CompanyId")); // that's why I can't use the property above
}
}
I can't understand how I can add a Configuration and set the IdCompany.
There's another approach?
How can I do this?
db.Configurations.Add(new Configuration {
IdCompany = idCompany
});
db.SaveChanges();
You cannot. One-to-one relation is currently supported only if dependent entity has FK to principal entity as its primary key. It means that Configuration.Id is PK of Configuration but also FK to Company.Id.
The reason why you cannot use CompanyId is that database would require it to use unique index (otherwise it would not be one-to-one relation but one-to-many) and EF currently doesn't support unique indexes.
Edit:
Sorry. Now I better understand your question. If you know the Id of the company and you want to add it a new configuration you can try to do something like this:
var company = new Company { Id = companyId };
context.Companies.Attach(company); // Existing company, EF know thinks it was loaded from DB
var configuration = new Configuration { Company = company }; // Create relation with existing company. It must not be related to other configuration!
context.Configurations.Add(configuration);

Multiplicity constraint violated SQL Server 2008 - CodeFirst

I'm working to solve a very tedious problem.
I have a class called Nation and a class called NationAlly
public class Nation
{
public int ID {get; set;}
public int name {get;set;}
public List<NationAlly> NationAllies {get;set;}
}
public class NationAlly
{
public int ID {get; set;}
public int level {get;set;}
public Nation toNation {get;set;}
}
I'm using EF 4 and CodeFirst with a DbContext called NationsDB to manage my database on SQL Server 2008.
If I create a new object of type Nation and I try to call nationsDB.SaveChanges, I got the following exception:
"Multiplicity constraint violated. The role 'NationAlly_toNation_Target' of the relationship 'CodeFirstNamespace.NationAlly_toNation' has multiplicity 1 or 0..1."
I tried to save a Nation with NationAllies field null and this exception is not thrown, the nation table in the database gets all the correct values.
In my database the table Nation has 2 fields: ID(primary key), name
The table NationAlly has 3 fields: ID(primary key), level, NationID
The two tables are linked with a relationship where NationAlly.NationID is foreign key and Nation.ID is primary key.
Isn't strange? In my eyes the table NationAlly should have a field called NationID1 and another called NationID2 to create the "relationship" between a nation and a list of other nations.
What did I do wrong?
You are perhaps a victim of the EF Code-First mapping conventions which create automatically a relationship between NationAllies and toNation you don't want to have.
If I understand you correctly (but I am not 100 percent sure, if I do), you actually want to have two relationships and you have exposed only one end of the relationship in each of the entities. So, NationAllies does NOT point to toNation but to an "invisible" Owner nation in your NationAlly entity.
If that is the case you need to explicitly overwrite the convention mappings. In the Fluent API of EF 4.1 this could look like:
public class MyContext : DbContext
{
public DbSet<Nation> Nations { get; set; }
public DbSet<NationAlly> NationAllies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Nation>()
.HasMany(n => n.NationAllies)
.WithRequired()
.Map(conf => conf.MapKey("OwnerID"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<NationAlly>()
.HasRequired(a => a.toNation)
.WithMany()
.Map(conf => conf.MapKey("NationID"))
.WillCascadeOnDelete(false);
}
}
This mapping would create the two foreign keys OwnerID and NationID in the NationAllies table, both pointing to the primary key ID in the Nations table.
Edit
Here is the application I have tested with:
Create a new Console App in VS2010 / .NET 4.0, name it "NationsApp"
Add a reference to "EntityFramework.dll"
Clear the content of "Program.cs" and paste instead the following in:
Content of Program.cs:
using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace NationsApp
{
public class Nation
{
public int ID { get; set; }
public int name { get; set; }
public List<NationAlly> NationAllies { get; set; }
}
public class NationAlly
{
public int ID { get; set; }
public int level { get; set; }
public Nation toNation { get; set; }
}
public class NationsContext : DbContext
{
public DbSet<Nation> Nations { get; set; }
public DbSet<NationAlly> NationAllies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Nation>()
.HasMany(n => n.NationAllies)
.WithRequired()
.Map(conf => conf.MapKey("OwnerID"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<NationAlly>()
.HasRequired(a => a.toNation)
.WithMany()
.Map(conf => conf.MapKey("NationID"))
.WillCascadeOnDelete(false);
}
}
class Program
{
static void Main(string[] args)
{
using (var context = new NationsContext())
{
try
{
// We have three Nations and two Allies
Nation nation1 = new Nation() {
NationAllies = new List<NationAlly>() };
Nation nation2 = new Nation() {
NationAllies = new List<NationAlly>() };
Nation nation3 = new Nation() {
NationAllies = new List<NationAlly>() };
NationAlly ally1 = new NationAlly();
NationAlly ally2 = new NationAlly();
// Nation1 has two Allies
// (Nation1 is the "owner" of both Allies)
nation1.NationAllies.Add(ally1);
nation1.NationAllies.Add(ally2);
// toNation of ally1 refers to Nation2
ally1.toNation = nation2;
// toNation of ally2 refers to Nation3
ally2.toNation = nation3;
context.Nations.Add(nation1);
context.Nations.Add(nation2);
context.Nations.Add(nation3);
context.SaveChanges();
}
catch (Exception e)
{
throw;
}
}
}
}
}
You can set a breakpoint on "throw" to watch possible exceptions in e in the debugger.
This creates a database called NationsApp.NationsContext if you are using SQL Server Express and don't have any further connection strings defined.
It gives two relationships Nation_NationAllies (FK is "OwnerID") and NationAlly_toNation (FK is "NationID"). All columns are non-nullable. The result in the DB is the following:
In case this helps someone getting this error... I was getting this message while doing queries rather than saving to the database. My data design:
public class Base {
public int Id {get; set;}
}
public class Child {
[Key][ForeignKey("Base")] public int Id {get; set;}
public virtual Base Base {get; set;}
public Child() {
Base = new Base();
}
}
The problem was in the constructor. Turns out EF4.1 doesn't like when you initialize associations there! I removed that constructor and things started working again.