Let's say I have a manufacturer which produces lots of models. Each model has several attributes, and these attributes should come in a certain sort order. Here is what I did before:
Table "ManufacturerModel" with
public virtual ICollection<VehicleAttribute> Attributes { get; set; }
Table "VehicleAttribute" with
public virtual ICollection<ManufacturerModel> Models { get; set; }
and finally ModelBuilder fluent api to connect these two tables so I get a 1:n relationship:
modelBuilder.Entity<ManufacturerModel>()
.HasMany(mm => mm.Attributes)
.WithMany(a => a.Models)
.Map(mv =>
{
mv.ToTable("Model_VehicleAttribute", SchemaNames.Config);
mv.MapLeftKey("ManufacturerModel_Id");
mv.MapRightKey("VehicleAttribute_Id");
});
Works perfect. BUT: where do I put the SortOrder column? In my opinion it should be inside mapping table "Model_VehicleAttribute" so I can select a ManufacturerModel and get all Attributes in a sorted order. How can this be achieved?
Related
In my OnModelCreating method do I have to map every table's relationship like i did with my Tickets and Administrator class?
I read this article and the writer only did one of the relationships but not all of it. If you scroll down in the link I've provided to "Customize the Data Model by adding Code to the Database Context" you will see what I'm talking about.
Source: Click Here
IssueContext.cs
public class IssueContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Ticket> Tickets { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Administrator> Administrators { get; set; }
public DbSet<Depot> Depots { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Ticket>()
.HasMany(c => c.Administrators).WithMany(i => i.Tickets)
.Map(t => t.MapLeftKey("TicketID")
.MapRightKey("AdministratorID")
.ToTable("Adminstrators on Tickets"));
}
}
My Entity Diagram
In my OnModelCreating method do I have to map every table's relationship like i did with my Tickets and Administrator class?
EF Code First came with some default conventions like the one you already disable by calling this line of code:
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
Those conventions help EF Code First to know:
which entity belong to which table,
who is the primary key,
what is the relationship between your entities and create those relationship into your database tables
....
If you follow the conventions, you will not need to write a connfiguration by using fluent API or data annotation attributes.
The following configuration you worte:
modelBuilder.Entity<Ticket>()
.HasMany(c => c.Administrators).WithMany(i => i.Tickets)
.Map(t => t.MapLeftKey("TicketID")
.MapRightKey("AdministratorID")
.ToTable("Adminstrators on Tickets"));
is useful because if you let EF to generate the join table between Ticket and Administrator you will have some issue to know which is the left key and which is the right key. You also tell EF to generate TicketID and AdministratorID as column name instead of Ticket_ID and Administrator_ID which are the default names it will use on the join table.
To answer to your question. No you don't need to add mappings for all entities. Use conventions as much as possible and then use fluent configuration or data annotation attributes to tweak and refine some columns, tables etc like you do with your join table.
Side note: Adminstrators on Tickets is not a good table name for a table just use AdministratorsTickets
I am just to set up a model in which you have news articles and topics they belong to. This is a simple association but now it get's a bit "extended". One of those topic association can be marked as the "mainTopic".
I've set up 3 tables:
avsn_content, containing the newsArticles identified by id
avsn_content_topics, containing the assoc, having assocId, id, topicId and a column indicating the mainTopic
avsn_topics, containing the topic, identified by topicId
Furthermore, I have models for these three tables. My association model is designed as follows:
[Table("avsn_content_topcis")]
public class TopicNewsModel
{
[Key]
[Column("assocId")]
public int Id { get; set; }
public NewsArticleModel NewsArticle { get; set; }
public TopicModel Topic { get; set; }
[Column("mainTopic")]
public bool IsMainTopic { get; set; }
}
My relationship setup looks like this:
modelBuilder.Entity<NewsArticleModel>()
.HasMany(x => x.Topics)
.WithMany()
.Map(m => m.MapLeftKey("id")
.MapRightKey("topicId")
.ToTable("avsn_content_topics"));
I am getting this error:
One or more validation errors were detected during model generation:
NewsArticleModelTopicNewsModel: Name: The EntitySet
'NewsArticleModelTopicNewsModel' with schema 'dbo' and table
'avsn_content_topics' was already defined. Each EntitySet must refer
to a unique schema and table.
Removing the ToTable option does not help it, so what am I doing wrong here?
Well, found the answer right after posting this. Trying to pick the problem from the other end of the rope showed that I had to start off from the TopicNewsModel instead from the NewsModel.
modelBuilder.Entity<TopicNewsModel>()
.HasRequired(topicNews => topicNews.NewsArticle)
.WithMany(news => news.Topics)
.Map(m => m.MapKey("id"));
modelBuilder.Entity<TopicNewsModel>()
.HasRequired(topicNews => topicNews.Topic)
.WithMany()
.Map(m => m.MapKey("topicId"));
I'm facing a problem using EF.
I have the following situation:
From this database schema i'd like to generate the following entity by merge tables data:
// Purchases
public class Purchase
{
//Fields related to Purchases
public int IdPurchase { get; set; }
public string CodPurchase { get; set; }
public int IdCustomer { get; set; }
public decimal Total { get; set; }
//Fields related to Customers table
public string CodCustomer { get; protected set; }
public string CompanyTitle { get; protected set; }
public string CodType { get; protected set; }
//Fields related to CustomersType table
public string DescrType { get; protected set; }
}
As you can see, in my context i don't want 3 separated entities for each table. I want a single one with the fields related to all tables. All fields of Customers and CustomersType tables must be readonly (so i've set the relative setters protected) and the others must be editables so that EF can track changes. In particular, i'd like to have the ability to change the "IdCustomer" field and let EF to automatically update "CodCustomer", "CompanyTitle", "DescrType"....and so on by doing cross table select.
To do that, i wrote this configuration class:
internal class PurchaseConfiguration : EntityTypeConfiguration<Purchase>
{
public PurchaseConfiguration(string schema = "dbo")
{
ToTable(schema + ".Purchases");
HasKey(x => x.IdPurchase);
Property(x => x.IdPurchase).HasColumnName("IdPurchase").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.IdCustomer).HasColumnName("IdCustomer").IsRequired();
Property(x => x.Total).HasColumnName("Total").IsRequired().HasPrecision(19, 4);
Map(mc =>
{
mc.Properties(n => new
{
n.CodCustomer,
n.CompanyTitle,
n.CodType
});
mc.ToTable("Customers");
});
Map(mc =>
{
mc.Properties(n => new
{
n.DescrType,
});
mc.ToTable("CustomersType");
});
}
}
I've tested it but it doesn't work as expected. I always get this message:
Properties for type 'Purchase' can only be mapped once. The non-key
property 'CodCustomer' is mapped more than once. Ensure the
Properties method specifies each non-key property only once.
Maybe there's something wrong or i forget something (for example the join fields of Map<> that i don't know where to specify them).
How can i accomplish in the correct way this task?
I don't want to have "Customers" and "CustomersType" DBSets in my context.
Is there a way to avoid it?
I even thought to add into the "IdCustomer" setter a custom query to update manually "Customers" and "CustomersType" related fields, but i don't want to do that for 2 reasons:
I don't have any DbConnection avaiable into the "Purchases" class, so i can't create a DbCommand to read data from DB.
I want entity class to be persistent-ignorant
EF seems to be a powerfull tool that can do these sort of things and i don't want to reinvent the wheel by writing custom procedures.
I've uploaded the example C# source and the tables CREATE scripts (MS SQLServer) here.
All entities are autogenerated by the "EF reverse POCO generator" T4 template (the T4 template is disabled, to activate it set CustomTool = TextTemplatingFileGenerator).
Do not forget to update the ConnectionString in the app.config.
Thanks in advance.
Not the right mapping
I'm afraid the bad news is that this mapping is not possible with this table structure. What you're trying to achieve here is known as entity splitting. However, entity splitting requires 1:1 associations, because sets of records in the involved tables represent one entity. With this mapping, you can't have a Customer belonging to more than one Purchase. That would mean that you could modify multiple Purchase entities by modifying a Customer property of only one of them.
Maybe the news isn't that bad, because I think you actually want to have 1-n associations. But then you can't have these "flattened" properties in Purchase.
As an alternative you could create delegated properties like so:
public string CodCustomer
{
get { return this.Customer.CodCustomer; }
set { this.Customer.CodCustomer = value; }
}
You'd have to Include() Customers and CustomersTypes when you fetch Purchases.
Another alternative is to use a tool like AutoMapper to map Purchase to a DTO type having the flattened properties.
But what does the exception tell me?
You map the Purchase entity to the Purchases table. But you don't specify which properties you want to map to this table. So EF assumes that all properties should be mapped to it. So that's the first (implicit) mapping of CodCustomer. The second one is the one in the mc.ToTable statement. (EF only reports the first problem.)
To fix this, you should add a mapping statement for the left-over Purchase properties:
Map(mc =>
{
mc.Properties(n => new
{
n.IdPurchase,
n.CodPurchase,
n.IdCustomer,
n.Total,
});
mc.ToTable("Purchases");
});
By the way, you should also remove the mapping configuration classes of Customer and CustomersType, they're redundant.
But, as said, the database schema doesn't match the required structure. If you try to save a Purchase you will get a foreign key constraint exception. This is because EF expects the following table structure:
Where the columns IdPurchase in Customer and CustomersType are both primary key and foreign key to Purchase. I don't think this is what you had in mind when designing the database.
In our MVC4 application we have a WeekDay Model:
public class WeekDay
{
[Key]
public int WeekDayId { get; set; }
[Required]
[Display(Name = "Dag")]
public string Day { get; set; }
[ForeignKey("Shop")]
public int ShopId { get; set; }
public virtual Shop Shop { get; set; }
}
And Shop model:
public class Shop
{
public int ShopId { get; set; }
[Display(Name = "Winkel")]
public string Name { get; set; }
public int WeekDayId { get; set; }
public virtual WeekDay WeekDay { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
We need a one-to-one relationship from WeekDay to Shop and the other way around (so that every shop has a weekday and vice versa). Unfortunately the code above gives the following error:
Multiplicity is not valid in Role 'Shop_WeekDay_Target' in relationship 'Shop_WeekDay'. Because
the Dependent Role properties are not the key properties, the upper bound of the multiplicity of
the Dependent Role must be '*'.
We've tried various things found on the internet to solve this issue but none seem to work (it did build when we added two primary keys, but then it fails while running the seed method and we get this error: The index 'IX_ShopId' is dependent on column 'ShopId'.
ALTER TABLE ALTER COLUMN ShopId failed because one or more objects access this column.).
Does anyone know how a one-to-one relationship can be made in this case?
/Edit Adding:
modelBuilder.Entity<WeekDay>()
.HasRequired(a => a.Shop)
.WithMany()
.HasForeignKey(u => u.ShopId);
modelBuilder.Entity<Shop>()
.HasRequired(a => a.WeekDay)
.WithMany()
.HasForeignKey(u => u.WeekDayId);
To the DbContext makes a succesful migration, but on update-database gives the error:
Introducing FOREIGN KEY constraint 'FK_dbo.WeekDays_dbo.Shops_ShopId'
on table 'WeekDays' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints. Could not create constraint. See previous
errors.
And setting WillCascadeOnDelete to false gives the error:
The index 'IX_ShopId' is dependent on column 'ShopId'. ALTER TABLE
ALTER COLUMN ShopId failed because one or more objects access this
column.
/Edit 3
My database structure looks as follows:
Now I "solved" the issue by not making the one-to-one relationship only one way, so that to match the Shop with the WeekDay I need to query the WeekDay table first and then see which shop matches. Then I can use the ShopId in further queries to only select the Categories from the current shop. It would have been cleaner if you could go from Shop to WeekDay so that I only need to query once.
I do not care if it is one to many if it is hard to make (I will only use one from the many), but it should be the other way around (from shop to weekday instead of weekday to shop). The one to many is the wrong way around as can be seen in the upper right.
The first issue I notice is here:
modelBuilder.Entity<WeekDay>()
.HasRequired(a => a.Shop)
.WithMany()
.HasForeignKey(u => u.ShopId);
modelBuilder.Entity<Shop>()
.HasRequired(a => a.WeekDay)
.WithMany()
.HasForeignKey(u => u.WeekDayId);
Each of the configurations above defines a one-to-many relationship. If you want a one-to-one relationship it should look something like this (check my syntax as I am not at my dev workstation to verify):
modelBuilder.Entity<WeekDay>()
.HasRequired(a => a.Shop)
.WithRequiredDependent();
Or, optionally, you can do this as well:
modelBuilder.Entity<WeekDay>()
.HasRequired(a => a.Shop);
modelBuilder.Entity<Shop>()
.HasRequired(a => a.WeekDay);
I have a situation with EF5 and a complex object. The basics is that I have a parent to child complex object, but the child refers back to the parent, more than once. I have tried various options but am not finding a solution that answers the question. The closest I have got is this answer (option 2c)
My model looks like below:
public class StaffMember
{
public virtual Guid StafId { get; set; }
// other props
// List of leave apps (Approved/Cancelled etc)
public virtual ICollection<StaffLeaveApp> LeaveApps { get; set; }
}
//Staff Leave Application
public class StaffLeaveApp
{
public virtual Guid LeaveId { get; set; }
public virtual Guid StaffId { get; set; }
// other props...
// Leave approved by? (2 approvals required)
public virtual StaffMember ApprovedBy1 { get; set; }
public virtual StaffMember ApprovedBy2 { get; set; }
}
my mappings look like this
public class StaffMap : EntityTypeConfiguration<StaffMember>
{
public StaffMap()
{
ToTable("tblStaffMembers");
HasKey(x => x.StaffId);
// other mappings...
HasMany(x => x.LeaveApps);
}
}
public class StaffLeaveAppMap: EntityTypeConfiguration<StaffLeaveApp>
{
public StaffLeaveAppMap()
{
ToTable("tblStaffMembersLeaveApps");
HasKey(x => x.LeaveId);
Property(x => x.StaffId).HasColumnName("StaffID");
//Child Relationships
HasOptional(x => x.ApprovedBy1).WithMany().Map(m => m.MapKey("LeaveApprovedBy1"));
HasOptional(x => x.ApprovedBy2).WithMany().Map(m => m.MapKey("LeaveApprovedBy2"));
}
}
Table (sorry, no images)
StaffID uniqueidentifier (FK - tblStaffMembers)
LeaveID uniqueidentifier (PK)
LeaveApprovedBy1 uniqueidentifier (FK - tblStaffMembers)
LeaveApprovedBy2 uniqueidentifier (FK - tblStaffMembers)
The business rule says: a staff member has "many" leave applications and a leave application belongs to a single staff member. Each application requires the approval of 2 staff members (managers) before it is "approved".
How would I map the above using EF so that a single staff member has a "many" leave applications (working already) and a leave application is mapped back to a staff member whom approved it for the first approval and then again for the seond approval. If I use the one mapping for "ApprovedBy1" only then EF is happy and all works as expected. The moment I add the second approval mapping EF struggles with the SQL queries it generates.
I am not sure how to tell EF to map back to the StaffMembers table to specify whom approved the application at level 1 and whom approved it at level 2. It almost ends up being a many to many relationship.
Any ideas?
You are looking for the inverse property, which is the property at the other end of an association. In EF, there are two way to mark a property as inverse.
Data annotations: InversePropertyAttribute.
Fluent mapping
As you already have fluent mapping I'll show you how you'd do it there:
HasOptional(x => x.ApprovedBy1).WithMany(x => x.LeaveApps)
.HasForeignKey(s => s.StaffId);
HasOptional(x => x.ApprovedBy2).WithMany()
.Map(m => m.MapKey("LeaveApprovedBy2"));
The HasOptional(...).WithMany(...) pair is a way to map inverse properties. Coming from the other side you can use e.g. HasMany(....).WithOptional(...).