I'm new to Entity Framework and I have probably a simple question.
I have simplified the structure at a maximum to be clear (I hope I am).
Imagine that I just need to create a simple "Enterprise" class, with only a Name.
Then another class named "Worker" with also just a Name for the worker.
A worker should belong to an Enterprise.
An Enterprise must have a manager (who is a Worker).
So here is how I imagine these simple classes :
public class Worker
{
public int WorkerId { get; set; }
public String Name { get; set; }
public int EnterpriseId { get; set; } // ForeignKey for Enterprise
public Enterprise Enterprise { get; set; }
}
public class Enterprise
{
public int EnterpriseId { get; set; }
public String Name { get; set; }
public Worker Manager { get; set; }
public List<Worker> Workers { get; set; }
}
I'd like these classes to result in the following DB structure :
Table Worker
WorkerId (PK, int, not null)
Name (varchar(128), not null)
EnterpriseId (FK, int)
Table Enterprise
EnterpriseId (PK, int, not null)
Name (varchar(128), not null)
Manager (FK, int)
I tried many things with modelBuilder, but I never obtain what I want.
Is there a solution with Fluent API to do what I want to do?
Thank you very much for your help.
This will not get you what you want (in Db) - but is what I recommend...
public ICollection<Worker> Workers { get; set; } // instead of List<>
// ...
modelBuilder.Entity<Worker>()
.HasRequired(x => x.Enterprise)
.WithMany(x => x.Workers)
.HasForeignKey(x => x.EnterpriseId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Enterprise>()
.HasOptional(x => x.Manager)
.WithOptionalPrincipal() // x => x.DefaultForEntity)
.WillCascadeOnDelete(false);
You can use it like:
var enterprise = new Enterprise { Manager = new Worker { Name = "Manager", }, };
enterprise.Workers = new[]
{
enterprise.Manager,
new Worker{ Name = "Worker1", },
new Worker{ Name = "Worker2", },
new Worker{ Name = "Worker3", },
new Worker{ Name = "Worker4", },
new Worker{ Name = "Worker5", },
};
db.Enterprises.Add(enterprise);
db.SaveChanges();
var enterprises = db.Enterprises.ToList();
This is exactly what you want...
modelBuilder.Entity<Worker>()
.HasRequired(x => x.Enterprise)
.WithMany(x => x.Workers)
.HasForeignKey(x => x.EnterpriseId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Enterprise>()
.HasKey(x => x.EnterpriseId);
modelBuilder.Entity<Enterprise>()
.HasOptional(x => x.Manager)
.WithOptionalDependent() // x => x.DefaultForEntity)
.WillCascadeOnDelete(false);
...but will not work - due to cyclical references (EF error).
Here is a pretty detailed example for a similar / identical solution...
Entity Framework One-to-Many with Default
I don't know how you intend to get the manager object, but my guess is you need to use Inheritance to make your design optimal. Try this:
public abstract class Employee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EmployeeId{ get; set; }
public String Name { get; set; }
[ForeignKey("Enterprise"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int EnterpriseId { get; set; } // ForeignKey for Enterprise
public Enterprise Enterprise { get; set; }
}
[Table("Workers")] // Table per Type (TPT), This will be your Table name in your database
public class Worker : Employee
{
//Add properties only related to workers
}
[Table("Managers")] // Table per Type (TPT). This will be your Table name in your database
public class Manager : Employee
{
//Add properties only related to Managers
}
public class Enterprise
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EnterpriseId { get; set; }
public String Name { get; set; }
public virtual ICollection<Employee> Employees{ get; set; }
}
Note: Sorry this is done using Property Mapping
Link: Here is a link to simple Fluent Mapping example
Link : Read about Table per Type (TPT) Inheritance here
Related
I am working with some entities which are connected by foreign keys using the Entity Framework code-first approach. When I try to include one from another I get an error that says:
The expression 'user.Organization' is invalid inside an 'Include' operation, since it does not represent a property access
These are my classes:
public class User : Person
{
public StaffRole? Role { get; set; } = null;
[ForeignKey(nameof(Organization))]
public Guid? OrganizationId { get; set; }
[NotMapped]
public virtual Organization Organization { get; set; }
}
public class Organization : Auditable
{
public Organization()
{
Staffs = new List<User>();
}
[Required]
public string Name { get; set; }
public virtual ICollection<User> Staffs { get; set; }
}
I have removed some properties in order to be clear.
This is how I am trying to include
var owner = userRepository.GetAll(user => user.Id == currentUser.Id &&
user.Role == StaffRole.Owner).Include(user => user.Organization).FirstOrDefault();
I have looked around the Web to find the answer but I think I need a little bit of individual help.
I like to create a DB model in ASP.Net Core 3.1. I am using Code First approach with EF Core 3.1.
I like to create a model for this relationship-
So, there is one Employee table and every employee has multiple bosses and each has multiple sub-ordinates. But every boss and every subordinate are employees also. What I have done is something like this-
Employee Model-
public class Employee
{
[HiddenInput(DisplayValue = false), Display(Name = "ID")]
[Key()]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column(Order = 1)]
public Guid? Id { get; set; } = Guid.NewGuid();
[Column("Name"), Required(ErrorMessage = "Term Name should be given"), Display(Name = "Term Name", Prompt = "Please Give Term Name")]
[DataType(DataType.Text)]
public string Name { get; set; }
public ICollection<Boss> Bosses { get; set; }
public ICollection<Subordinate> Subordinates { get; set; }
............
............
}
But I am getting this error during creating the DB model by the command Add-Migration <MigrationName>-
Unable to determine the relationship represented by navigation property 'Employee.Bosses' of type 'ICollection'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Can anyone please help?
direct many-to-many relations are not supported with ef core 3.1.
See this: https://learn.microsoft.com/de-de/ef/core/what-is-new/ef-core-5.0/whatsnew
If you cannot use ef core >= 5, than you have to create a navigation property to the EmployeeBoss entity.
Try this:
public partial class Employee
{
public Employee()
{
EmployeeBossEmployees = new HashSet<EmpoyeeBoss>();
EmployeeBossBosses = new HashSet<EmpoyeeBoss>();
}
[Key]
public int Id { get; set; }
[InverseProperty(nameof(EmpoyeeBoss.Employee))]
public virtual ICollection<EmpoyeeBoss> EmployeeBossEmployees { get; set; }
[InverseProperty(nameof(EmpoyeeBoss.Boss))]
public virtual ICollection<EmpoyeeBoss> EmployeeBossBosses { get; set; }
}
public partial class EmpoyeeBoss
{
[Key]
public int Id { get; set; }
public int BossId { get; set; }
public int EmployeeId { get; set; }
[ForeignKey(nameof(EmployeeId))]
[InverseProperty("EmployeeBossEmployees")]
public virtual Employee Employee { get; set; }
[ForeignKey(nameof(BossId))]
[InverseProperty("EmployeeBossBosses")]
public virtual Employee Boss { get; set; }
}
and include in your dbcontext:
public virtual DbSet<Employee> Employees { get; set; }
public virtual DbSet<EmpoyeeBoss> EmployeeBosses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EmpoyeeBoss>(entity =>
{
entity.HasOne(d => d.Employee)
.WithMany(p => p.EmployeeBossEmployees)
.HasForeignKey(d => d.EmployeeId)
.OnDelete(DeleteBehavior.ClientSetNull);
entity.HasOne(d => d.Boss)
.WithMany(p => p.EmployeeBossBosses)
.HasForeignKey(d => d.BossId);
});
I use EF code first.
It's my modelbuilder
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Market>()
.HasRequired(s => s.State)
.WithMany()
.HasForeignKey(s => s.StateId)
.WillCascadeOnDelete(false);
}
and State Class :
public class State
{
public State()
{
Markets = new HashSet<Market>();
}
[Key]
public int StateId { get; set; }
public string StateName { get; set; }
// navigation property
public virtual ICollection<Market> Markets { get; set; }
}
and Market class :
public class Market
{
[Key]
public int MarketId { get; set; }
public string MarketName { get; set; }
public int StateId { get; set; }
// navigation property
public virtual State State { get; set; }
}
Of course I remove extra code.
Problem is when I use this code , an State_StateId column add to my Market table in database, and when I do not use modelbuilder an error occurred with message loop code and ... (I say that I remove extra code), so how can I use code first without this "State_StateId" extra column.
excuse me for bad english writing.
If you want to remove State_StateId column set the configuration completely like the code below and don't let WithMany empty:
modelBuilder.Entity<Market>()
.HasRequired(s => s.State)
.WithMany(p => p.Markets)
.HasForeignKey(s => s.StateId)
.WillCascadeOnDelete(false);
Or you can just remove the Fluent API configuration and let EF use the default configuration convention and will set all tables, primary keys, foreign keys and column name for you.
I searched on site for solving my problem, but I still haven't solved it.
I have 2 entities:
public class Article
{
[Key]
public Guid ID { get; set; }
public Guid? ApprovedBy_ID { get; set; }
public Guid CreatedBy_ID { get; set; }
public virtual Profile ApprovedBy { get; set; }
public virtual Profile CreatedBy { get; set; }
//New guid for new article
public Article()
{
ID = Guid.NewGuid();
}
}
public class Profile
{
[Key]
public Guid ID { get; set; }
[Required]
[StringLength(100)]
public string FullName { get; set; }
public Profile()
{
ID = Guid.NewGuid();
}
}
And here is my insert-logic:
private readonly iContext context;
public ArticleLogic()
{
context = new iContext();
}
public IEnumerable<Article> GetAllArticle()
{
return context.Articles.Include("Categories").Include("Pictures").Include("ApprovedBy").Include("CreatedBy").Include("Template");
}
public Article AddArticle(Article article)
{
try
{
Profile pf = context.Profiles.First();
context.Profiles.Attach(pf);
Article art = new Article();
art.Title = article.Title;
art.Description = article.Description;
art.Content = article.Content;
art.Tag = article.Tag;
art.Template = article.Template;
//pf has ID = '0816f19c-31c1-4103-8f51-ba422beab1c0' (first row in database)
art.CreatedBy = pf;
art.CreatedBy_ID = pf.ID;
context.Articles.Add(art);
context.SaveChanges();
return article;
}
catch (Exception ex)
{
//But ex throw error duplicate ID = '7aa1d064-54ff-47b9-807d-db422fa71f8c' (second row in database)
Debug.WriteLine(ex.StackTrace);
throw ex;
}
}
DB Context:
public myContext()
: base("name=abc")
{
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = false;
}
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<Template> Templates { get; set; }
public virtual DbSet<Article> Articles { get; set; }
public virtual DbSet<Picture> Pictures { get; set; }
public virtual DbSet<Profile> Profiles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//One-to-many: Article - Picture (nullable)
modelBuilder.Entity<Picture>()
.HasOptional<Article>(c => c.Article)
.WithMany(p => p.Pictures)
.HasForeignKey(f => f.ArticleId);
////One-to-many: Role - Profile
modelBuilder.Entity<Profile>()
.HasRequired<Role>(i => i.Role)
.WithMany(i => i.Profiles)
.HasForeignKey(f => f.RoleID);
///Many-to-many: Article - Category
modelBuilder.Entity<Article>()
.HasMany(t => t.Categories)
.WithMany(t => t.Articles)
.Map(m => {
m.ToTable("ArticleCategories");
m.MapLeftKey("ArticleId");
m.MapRightKey("CategoryId");
});
//
modelBuilder.Entity<Article>()
.HasOptional(a => a.ApprovedBy)
.WithMany(a => a.ArticleApprovedBy)
.HasForeignKey(f=>f.ApprovedBy_ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Article>()
.HasRequired(a => a.CreatedBy)
.WithMany(a => a.ArticleCreatedBy)
.HasForeignKey(f => f.CreatedBy_ID)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
I am using CodeFirst EF 6.1.3 and I am getting an error: Violation of PRIMARY KEY constraint 'PK_Profiles'. Cannot insert duplicate key in object 'dbo.Profiles' like this link Entity Framework Code First - Cannot insert duplicate key in object 'dbo.T_CRProviders'?
Can anyone help me? Thanks.
context thinks pf is new and should be inserted into the DB. Show the instantiation and configuration of context - do you have change tracking disabled?
You need to tell EF that a Profile can be referenced multiple times by Article:
modelBuilder.Entity<Article>()
.HasOptional(a=>a.ApprovedBy)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Article>()
.HasOptional(a=>a.CreatedBy)
.WithMany()
.WillCascadeOnDelete(false);
The HasOptional could also be HasRequired if the property is mandatory (not null in the DB).
Adding WithMany is a key here if you want (as you do in your sample) be able to associate a profile to multiple properties of Article (and multiple Articles too).
PS: I wrote this code by memory, so something could need adjustment.
Your line:
context.Profiles.Attach(pf);
will attach the pf, however it is already tracked by EF. You just got it from the database. The pf will be reinserted by EF, and this fails. Just remove the line and you will be fine.
Add/Attach and Entity States
I'm trying to define a one to many relationship, as well as a one to one relationship between the same 2 entities "UserProfile" and "Blog". I think I have succeeded with the following code, however, it results in creating a new column in "Blog" table called "UserProfile_UserId" (FK). I don't understand why it does this.
The relationships in English are:
1. "A UserProfile has many Blogs"
2. "A UserProfile has one main optional (nullable) Blog"
So ultimately I'd like to see a FK from Blog.UserId to UserProfile.UserId
And a nullable FK from UserProfile.BlogId to Blog.Id
And I think that is all... I especially don't want additional columns added by EF.
public class UserProfile
{
[Key]
public int UserId { get; set; }
public int? BlogId { get; set; }
public virtual Blog Blog { get; set; } // This is a user's main blog
public virtual ICollection<Blog> AllUsersBlogs { get; set; }
}
public abstract class Blog
{
[Key]
public int Id { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual UserProfile User { get; set; }
}
That's pretty tricky thing to make - by default CF puts all the relationships / FK-s on one side. And that's for a reason, because it simplifies things, avoid cyclical references and contradicting 'constraints' on two sides
what often happens is the error reporting that from one FK ir requires
to be of multiplicity '1' and from the other FK it has to be * -
resulting in an exception.
But this works all you want I think - you just have to 'feed it' the data carefully...
public class UserProfile
{
[Key]
public int UserId { get; set; }
[ForeignKey("Blog")]
public int? BlogId { get; set; }
public virtual Blog Blog { get; set; } // This is a user's main blog
public virtual ICollection<Blog> AllUsersBlogs { get; set; }
}
//abstract
public class Blog
{
[Key]
public int Id { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
// [ForeignKey("UserId")]
public virtual UserProfile User { get; set; }
}
In your fluent config...
modelBuilder.Entity<Blog>()
.HasRequired(x => x.User)
.WithMany(x => x.AllUsersBlogs)
.HasForeignKey(x => x.UserId)
.WillCascadeOnDelete(false);
And use it like this...
var user = db.UserProfiles.Add(new UserProfile
{
//Blog = mainblog,
AllUsersBlogs = new List<Blog>
{
new Blog{},
new Blog{},
new Blog{},
new Blog{},
new Blog{},
}
});
db.SaveChanges();
var mainblog = new Blog { User = user, };
user.Blog = mainblog;
db.SaveChanges();
Note that for the main blog - you have to explicitly specify the User for your blog now - plus set it as the user's main blog.
That's because you have two different relationships now - one is mandatory (User in the Blog) - and another is the optional main blog.
Anyhow, if this doesn't satisfy your demands (though it looks it
should I think) - then I'd suggest that you let it create things by
default and have FK-s on the Blog side, you lose the BlogId but it
simplifies things a lot.