EntityFrameWork model relationship configuration - c#

I'm coding in ef 5 code first solution and i have a model as follow:
public class User
{
public int Id {get;set;}
public int Role1Id {get; set;}
public Role Role1 {get; set;}
}
and another model:
public class Role
{
public int Id { get; set;}
public string Title {get; set;}
}
also i configure this model in another class as follow:
public class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
ToTable("User", "dbo");
// Here i want introduce Role1 as navigation property for Role1Id property
}
}
here is the question:How can i config User model to introduce Role1 as navigation property for Role1Id property?

You can use annotation:
public class User
{
public int Id {get;set;}
public int Role1Id {get; set;}
[ForeignKey("Role1Id")]
public Role Role1 {get; set;}
}

EF should configure it automatically as long as the id matches the field generated in the database schema.
You could try to configure it in UserConfig with the following:
HasRequired(user => user.Role1)
.WithRequiredDependent()
.Map(mapping => mapping.MapKey("Role1Id");
This configures it to be required. You can use the HasOption method if it's not required as well.

Related

Abstract Class with 1 to 1 relationship Entity Framework

Using entity framework I've been trying to create this relationship. Basically I have 1 object which has a Result. The Result object is abstract, as it has to be one of the 3 classes that inherit from Result, i.e. Approved, Rejected, or Modified:
I'm trying to create the table structure using Entity Framework. Originally I was going for a TPCT (Table Per Concrete Type) structure, so there would be no Result table, but I wanted to keep the link back in the Action table if I wanted to reference the Result, so now I'm attempting just TPT structure. I find TPCT is cleaner, but ultimately if TPT is the only way to achieve what I want, I'm fine with it.
I've tried variations of the following for my model structure:
public class Action
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public int Result_Id {get; set;}
[ForeignKey("Result_Id")]
public virtual Result Result {get; set;}
public string Description {get; set;}
}
public abstract class Result
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[Required]
public int Action_Id {get; set;}
[ForeignKey("Action_Id")]
public virtual Action Action {get; set;}
public string Comment {get; set;}
public class Approved : Result
{
public string Thing {get; set;}
}
public class Rejected : Result
{
public string Stuff {get; set;}
}
public class Modified : Result
{
public string Whatever {get; set;}
}
}
And then I've tried the following 2 strategies in my context file to either implement TPT:
modelBuilder.Entity<Approved>().ToTable("Approved");
modelBuilder.Entity<Rejected>().ToTable("Rejected");
modelBuilder.Entity<Modified>().ToTable("Modified");
Or for TCPT:
modelBuilder.Entity<Approved>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Approved");
});
modelBuilder.Entity<Rejected>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Rejected");
});
modelBuilder.Entity<Modified>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Modified");
});
Everytime I try to add the new migration, whatever I try, I'm faced with this error:
Unable to determine the principal end of an association between the types 'Result' and 'Action'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
The one time I was able to have it work was if I removed this reference from in the Action class:
public int Result_Id {get; set;}
[ForeignKey("Result_Id")]
public virtual Result Result {get; set;}
But I would really like to keep that reference there so then when I go into my DB to grab that Action object, I can immediately tell if there is a Result associated to it, without having to go through all 3 Result tables to see if there is a reference to that Action (which is why I think I need to have TPT...)
Any help to get this working would be greatly appreciated!
With a lot of research and trial and error, I discovered what I needed to get the result I wanted. It's TPCT DB structure, and the Action object is able to keep the reference to Result. Here are the model classes:
public class Action
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public virtual Result Result {get; set;} //just virtual here, as Action is the dependent and Result is the principal-- i.e. this Result isn't required
public string Description {get; set;}
}
public abstract class Result
{
//got rid of the Result_Id, since it's 1:1 the Action_Id can be the Key
[Required, Key] //added "Key"
public int Action_Id {get; set;}
[ForeignKey("Action_Id")]
public Action Action {get; set;} //removed this virtual, as Action is Required for Result, that makes Result the principal
public string Comment {get; set;}
public class Approved : Result
{
public string Thing {get; set;}
}
public class Rejected : Result
{
public string Stuff {get; set;}
}
public class Modified : Result
{
public string Whatever {get; set;}
}
}
And here is the fluent API code from the context:
//this gave me TPCT like I wanted
modelBuilder.Entity<Approved>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Approved");
});
modelBuilder.Entity<Rejected>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Rejected");
});
modelBuilder.Entity<Modified>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Modified");
});
//this defined the principal-dependent relationship I was missing
modelBuilder.Entity<Action>()
.HasOptional(a => a.Result)
.WithRequired(a => a.Action)
.Map(x => x.MapKey("Action_Id"));
And then it worked! Hopefully this example can assist someone else.

Can you have AutoMapper only map properties that match explicitly

Is there a way to have AutoMapper only map properties that match explicitly? My Model has a property UserAccountId and also has a navigation property UserAccount that has an Id property, if the user posts UserAccountId, I want AutoMapper to map UserAccountId and leave UserAccount.Id null; I'd love to avoid using Ignore since that will prevent me from explicitly posting UserAccount.Id as well. I can't find any information on controlling AutoMapper's name matching strategy...
public class Role {
public int Id {get; set;}
public int UserAccountId {get; set;}
public UserAccount UserAccount {get; set;}
...
}
public class UserAccount {
public int Id {get; set;}
...
}
public class RoleViewModel {
public int Id {get; set;}
public int UserAccountId {get; set;}
}
public ActionResult AddRole(RoleViewModel viewModel) {
var model = GetModel(viewModel.Id);
Mapper.Map(viewModel, model);
//Do not infer and map UserAccount.Id
}
Don’t use AutoMapper for this case. It wasn’t built to support this type of scenario.
Or use ForPath(...).Ignore, but I just wouldn’t use my library for this scenario.

C# One-To-Zero-Or-One relationship using CodeFirst

So I have the following code:
ParentModel.cs
public class ParentModel {
public int ParentModelID {get; set;}
...other fields here
public ChildModel ChildModel {get; set;}
}
ChildModel.cs
public class ChildModel{
[ForeignKey("ParentModel")]
public int ChildModelID {get; set;}
...other fields and navigation properties here
public int ParentModelID {get; set;}
public ParentModel ParentModel {get; set;}
}
So the database gets generated successfully. The problem occurs when I try to save data. For example I save data to the ParentModel first and it gets save successfully. But when I save inside ChildModel, even when my data contains the ParentModel's id, it gives me the following error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.ChildModels_dbo.ParentModels_ChildModelID". The conflict occurred in database "MyDatabaseName", table "dbo.ParentModels", column 'ParentModelID'. The statement has been terminated.
use following with fluent api
public class ParentModel {
public int ParentModelID {get; set;}
...other fields here
public virtual ChildModel childModel {get; set;}
}
public class ChildModel{
public int ParentModelID {get; set;}
public int ChildModelID {get; set;}
...other fields and navigation properties here
public virtual ParentModel parentModel {get; set;}
}
Then i will use fluent api to create relationship
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configure ParentModelID as PK for ChildModel
modelBuilder.Entity<ChildModel>()
.HasKey(e => e.ParentModelID);
// Configure ParentModelID as FK for ChildModel
modelBuilder.Entity<ParentModel>()
.HasOptional(s => s.childModel)
.WithRequired(ad => ad.ParentModelID);
}
You almost had it; you want a shared primary key, which you have set up. The problem is you are presumably setting the the ChildModel.ParentModelID property with the parent's ID. Remove that property and set ChildModel.ChildModelID to the ParentModelID of the Parent entity - ChildModelID is the FK to the ParentModel entity. You'll also need to make ChildModel.ParentModel required.
public class ParentModel
{
public int ParentModelID {get; set;}
public ChildModel ChildModel {get; set;}
}
public class ChildModel
{
[ForeignKey("ParentModel")]
public int ChildModelID {get; set;}
[Required]
public ParentModel ParentModel {get; set;}
}
var parent = new ParentModel();
dbContext.Set<ParentModel>().Add( parent );
dbContext.SaveChanges();
var child = new ChildModel()
{
ChildModelID = parent.ParentModelID
};
dbContext.Set<ChildModel>().Add( child );
dbContext.SaveChanges();
See this answer for another example.

Dbset<TEntity>.Add(entity) assigns an ID and this results in a exception

For the following classes :
public Car
{
public int ID { get; set; }
public string Brand {get; set; }
}
Normally when we do :
Car c = new Car { Brand = "Jaguar" } ; // Point A
context.Cars.Add(c); // Point B
context.SaveChanges() // Point C
At point B, the ID should remain 0, and an ID should only be assigned at point C. However, I have found that for one of my classes, an ID is assigned at point B and this results in this exception being thrown :
Cannot insert explicit value for identity column in table
'Cars' when IDENTITY_INSERT is set to OFF.
I have played with Fluent API and I'm 99% sure my relationships are correctly defined. I can't figure out why this DbSet tries to assign an ID for this entity.
Update
Thank you for your help, so here is a more detailed illustration of my situation :
public Car
{
public int ID { get; set; }
public string Brand {get; set; }
public int Driver1ID {get; set;}
public Person Driver1 {get; set;}
public int Driver2ID {get; set;}
public Person Driver2 {get; set;}
}
public Person
{
public int ID { get; set; }
public string Name { get; set; }
}
And here is my fluent configuration :
modelBuilder.Entity<Car>().HasKey(x => x.ID);
modelBuilder.Entity<Car>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); // Added following Igor's suggestion
modelBuilder.Entity<Car>().HasRequired(x => x.Driver1).WithOptional().WillCascadeOnDelete(false);
modelBuilder.Entity<Car>().HasRequired(x => x.Driver2).WithOptional().WillCascadeOnDelete(false);
Edit 2
Well, I have found that actually Migrations messed up. For some reason EF put the 2nd foreign key (Driver2), on the primary key column. This is why, DbSet.Add() was populating the ID column with a value that was actually the Driver 2 ID.
I really don't know why EF got confused like that. And the weird thing is that I didn't see this FK when I looked in SQL Management Studio. It looks like EF applied some relashionships that were not actually in the DB.
I reset the whole migrations (deleted the migration folder and the _migrationhistory table, then executed Enable-Migrations and Add-Migration Init in PowerShell), and I have been able to see the problematic lines in the initial migration file.
Or course I have modified them and It seems to have solved the problem.
In your fluent (and also declaritivly) mapping you can do specify if the ID is assigned by the database using Identity or if its not. If you specify that it IS assigned your code should not also assign it because you will get an exception. In fluent you can do it like this:
public Car
{
public int ID { get; set; }
public string Brand {get; set; }
public int Driver1ID {get; set;}
public Person Driver1 {get; set;}
public int Driver2ID {get; set;}
public Person Driver2 {get; set;}
}
public Person
{
public int ID { get; set; }
public string Name { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
// ....
modelBuilder.Entity<Car>().HasKey(x => x.ID);
modelBuilder.Entity<Car>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); // set it on
modelBuilder.Entity<Car>().HasRequired(x => x.Driver1).WithMany().HasForeignKey(x => x.Driver1ID).WillCascadeOnDelete(false);
modelBuilder.Entity<Car>().HasRequired(x => x.Driver2).WithMany().HasForeignKey(x => x.Driver2ID).WillCascadeOnDelete(false);
// ....
}
You can attribute the Id column with
public Car
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Brand {get; set; }
}

Should foreign Id properties be mapped from Model to Dto?

If I have the following model:
public class Customer
{
public int Id {get; set;}
public int CustomerTypeId {get; set;}
public virtual CustomerType {get; set;}
}
Should the Dto exclude foreign Id's to look like this:
public class CustomerDto
{
public int Id {get; set;}
public virtual CustomerType {get; set;}
}
And when using Graphdiff to update the object graph, will EF know that CustomerType maps to CustomerTypeId?
Yes, you need to use it but you can avoid virtual member declaration. If you use AutoMapper, then the mapping will be done automatically. So, your Dto will look like this:
public class CustomerDto
{
public int Id {get; set;}
public int CustomerTypeId {get; set;}
}
And the mapping:
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CustomerDto, Customer>();

Categories