In EF6 we have two ways to declare relationship between two tables:
Annotations Attributes
Fluent API
Today (by accident) I removed one relationship between two tables and everything kept working well. I was very surprised because there was no way EF would know how those two tables are connected.
Tables look like that:
[Table("item")]
public class Item
{
public Item()
{
ItemNotes = new HashSet<ItemNote>();
}
[Key]
[Column("itemID", TypeName = "int")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int itemID { get; set; }
public ICollection<ItemNote> ItemNotes { get; set; }
}
[Table("itemNotes")]
public class ItemNote
{
[Key]
[Column("id", TypeName = "int")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("itemID", TypeName = "int")]
public int ItemId { get; set; }
[Column("note", TypeName = "varchar")]
[MaxLength(255)]
public string Note { get; set; }
}
Fluent API:
public class MyContext : DbContext
{
public MyContext()
: base("name=MyContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MyContext>(null);
//I removed this relationship:
//modelBuilder.Entity<Item>().HasMany(i => i.ItemNotes).WithRequired().HasForeignKey(i => i.ItemId);
base.OnModelCreating(modelBuilder);
}
}
Here is the test I made: It's an integration test, that connects to the real database, gets items with notes and tests the EF:
[TestCase]
public void QueryItemWithItemNotesTest()
{
FniContext fniContext = new FniContext();
int itemId = fniContext.Database.SqlQuery<int>("SELECT TOP(1) itemId FROM item WHERE itemId IN (SELECT itemId FROM dbo.itemNotes)").FirstOrDefault();
var item = fniContext.Items.AsNoTracking()
.Include(i => i.ItemNotes)
.Where(i => i.itemID == itemId).FirstOrDefault();
Assert.IsNotNull(item);
Assert.Greater(item.ItemNotes.Count, 0);
}
It passes! It loads all notes! How come?!
I kept investigating and it turned out that in case of 1:many relationship I totally don't have to have any relationship in the code. The only time I need it is with 1:1 relationship. Am I'm missing something? Most of relationships are 1:many, so does it mean Fluent API is used for 1:1 most of the time?
Entity Framework has some conventions that you do not need to define explicitly.
From https://msdn.microsoft.com/en-us/library/jj679962(v=vs.113).aspx#Anchor_2
In addition to navigation properties, we recommend that you include
foreign key properties on the types that represent dependent objects.
Any property with the same data type as the principal primary key
property and with a name that follows one of the following formats
represents a foreign key for the relationship: '<navigation property
name><principal primary key property name>', '<principal class
name><primary key property name>', or '<principal primary key property
name>'. If multiple matches are found then precedence is given in the
order listed above. Foreign key detection is not case sensitive. When
a foreign key property is detected, Code First infers the multiplicity
of the relationship based on the nullability of the foreign key. If
the property is nullable then the relationship is registered as
optional; otherwise the relationship is registered as required.
Related
Odd issue that I've been looking at all day. I am working with Entity Framework 6. The issue I have is that I have three entities:
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
// other properties and relationships here
public int? OrderProfileId { get; set; }
public int OrderTemplateId { get; set; }
public virtual OrderProfile Profile { get; set; } // optional property
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderProfile
{
public int Id { get; set; }
// other properties
// added here 6/15/2021
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderTemplate : EntityMetaData
{
public int Id { get; set; }
// other properties
public int? OrderProfileId{ get; set; }
public OrderProfile OrderProfile { get; set; }
}
In our model builder, we have these definitions:
modelBuilder.Entity<Order>()
.HasOptional(x => x.OrderProfile)
.WithMany(x => x.Orders)
.HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
But even with the above fluent api model, we get the error
Invalid column name 'OrderProfile_Id'
Throughout various testing I was unable to find why this issue was occurring, so I looked at our logs and found when this error started popping it's head up and then was able to find the changes associated to OrderProfile and found that the only change that was made was adding the relationship from OrderProfile to OrderTemplate.
When I removed that fluent api relationship OrderProfile to OrderTemplate, it worked as expected... I don't need that relationship to OrderTemplate, but would like it to be there, how can I establish a optional 1 to optional 1 relationship without breaking other relationships? Also, why would additional relationships be effected by this?
UPDATE 6/15/2021
So I found I had a reverse navigation property in the OrderProfile model:
public virtual OrderTemplate OrderTemplate{ get; set; }
removing that and the associated fluent relationship
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
Doing the above resolved the issue, but for some reason, the issue seems to have cascaded down to another relationship that has a circular reference like the above. The Order class is involved with this cascaded issue. I guess this is a pretty big cause for concern since this application worked fine for the last 4 years and for these relationships to be decaying like this is worrisome. Does anyone know why this is happening?
if you use the right naming convention, EF will do magic. in this sample, you don't need fluent API to relate entities.
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
public int? OrderProfileId { get; set; } //means HasOptional (nullable) and ForeignKey
//variable name must be OrderProfile not Profile
public virtual OrderProfile OrderProfile { get; set; }
}
public class OrderProfile
{
public OrderProfile()
{
Orders = new HashSet<Order>();
}
public int Id { get; set; }
//be aware circular reference at any conversion or mapping
public virtual ICollection<Order> Orders {get; set;} //means WithMany
}
I've got an error like this too. It's caused by unmatching OrderProfileId property in OrderTemplate class with the fluent api model
If I'm not wrong, you want the OrderProfile model a many to many relation between Order and OrderTemplate. Then if it was the case, add the nvaigation property in OrderProfile.
public class OrderProfile
{
public int Id { get; set; }
// other properties
public virtual ICollection<Order> Orders { get; set; }
public virtual OrderTemplate OrderTemplate { get; set; }
}
Then change the fluent api model to be like this
// the EF has modelled the relation for normal 1 to many relation
// modelBuilder.Entity<Order>()
// .HasOptional(x => x.OrderProfile)
// .WithMany(x => x.Orders)
// .HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderTemplate>()
.HasOptional(x => x.OrderProfile)
.WithOptional(x => x.OrderTemplate);
You're working database-first, which always leaves room for a mismatch between the actual database model and the model EF infers from class and property names and mapping code (= conceptual model). If this happens, it may help to make EF generate a database from the conceptual model and see where it creates the column it expects, OrderProfile_Id.
This is what you'll see when logging the SQL statements:
CREATE TABLE [dbo].[OrderTemplates] (
[Id] [int] NOT NULL IDENTITY,
[OrderProfileId] [int],
[OrderProfile_Id] [int],
CONSTRAINT [PK_dbo.OrderTemplates] PRIMARY KEY ([Id])
)
...
ALTER TABLE [dbo].[OrderTemplates]
ADD CONSTRAINT [FK_dbo.OrderTemplates_dbo.OrderProfiles_OrderProfile_Id]
FOREIGN KEY ([OrderProfile_Id]) REFERENCES [dbo].[OrderProfiles] ([Id])
There you see the expected nullable column OrderProfile_Id which is the FK to OrderProfiles. It's noteworthy to see that EF does not use OrderProfileId as a foreign key field. It's just a field that could be used for anything.
That's because EF6 doesn't support 1:1 associations as foreign key associations (reference property and primitive FK property).
Knowing this, the remedy is simple: remove the property OrderTemplate.OrderProfileId and tell EF to use the field OrderTemplate.OrderProfileId in the database:
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile)
.Map(m => m.MapKey("OrderProfileId"));
That said, I wonder why Order has a foreign key to OrderProfile. Isn't its OrderProfile determined by its OrderTemplate? If it's a redundant relationship it may be better to remove it.
I`d like to add foreign key on unique not null attribute in second table. Here how it look like:
public class T_AlarmTresholds
{
[Key]
public int Id { get; set; }
....
....
public Guid MeasurementGuid { get; set; }
[ForeignKey("MeasurementGuid")]
public virtual T_Measurements Measurement { get; set; }
}
public partial class T_Measurements
{
public int Id { get; set; }
[Index("UC_Guid", IsUnique = true)]
public Guid GUID { get; set; }
}
Here is model builder:
modelBuilder.Entity<T_Measurements>()
.HasMany(x => x.T_AlarmTresholds)
.WithRequired(x => x.Measurement)
.HasForeignKey(x => x.MeasurementGuid);
Entity framework throws error while SQL Server accept this solution. Here is error in Visual Studio while debugging:
{"One or more validation errors were detected during model
generation:\r\n\r\nT_Measurements_T_AlarmTresholds_Source_T_Measurements_T_AlarmTresholds_Target:
: The types of all properties in the Dependent Role of a referential
constraint must be the same as the corresponding property types in the
Principal Role. The type of property 'MeasurementGuid' on entity
'T_AlarmTresholds' does not match the type of property 'ID' on entity
'T_Measurements' in the referential constraint
'T_Measurements_T_AlarmTresholds'.\r\n"}
You don't put the foreign key on the virtual object itself you need to create a new property in T_AlarmTresholds:
public int T_MeasurementsId{get; set;}
to act as the foreign key. The dependency property (the virtual one) will then link up automatically. You also need to remove the ForiegnKey attribute from the virtual property.
Edit:
Just spotted that your code doesn't use the Id column as the primary key but instead uses the Guid, so you'll instead want to add the foreign key attribute to the MeasurementGuid property.
There are two entities:
Group
Yuvak - Person
"Other" person is designed in back-end who has no group. (null)
A Yuvak - Person will always have one HomeGroup. (1=>1) And will have no groups to control.
A Nirikshak(Head) - Person will always have one HomeGroup. (1=>1) But he will also have multiple groups to control - GroupsOfNirikshak. (1=>Many)
A Group will have multiple Yuvaks (1=>Many)
and all groups mostly will have only one Head. (Initially a new group might not have any head but zero or more yuvaks-persons.)
[Table("Group")]
public class Group
{
[Key]
public int Id { get; set; }
.....
public virtual List<Yuvak> Yuvaks { get; set; }
[ForeignKey("Nirikshak")]
public int? NirikshakId { get; set; }
public virtual Yuvak Nirikshak { get; set; }
}
[Table("Yuvak")]
public class Yuvak
{
[Key]
public int Id { get; set; }
.....
[ForeignKey("HomeGroup")]
public int? HomeGroupId { get; set; }
public virtual Group HomeGroup { get; set; }
public virtual List<Group> GroupsOfNirikshak { get; set; }
}
I already has provided two foreign keys for 1=>1 relationships (nullable) in both entities.
And now to manage many to many relationship it should automatically create a third table with "Yuvak_Id" and "Group_Id" columns if they are not null. But as here the FKs are null; instead of creating a third table it adds a foreign key column in both the tables.(Group:"Yuvak_Id" and Yuvak:"Group_Id")
What should I do so that to maintain Yuvak-HomeGroup it should use above provided foreign keys only and for many to many relationship (Head-GroupsOfNirikshak & Group-Yuvaks )it should create a seperate table.
Can I create a separate table for many to many relationship like : (ID,YuvakID,GroupID) How can I do that?
I tried to do that but got different errors like below :
The navigation property 'HomeGroup' declared on type
'YTKGoshthiManagment.Models.Yuvak' has been configured with
conflicting foreign keys.
Multiplicity is not valid in Role 'Yuvak_HomeGroup_Target' in
relationship 'Yuvak_HomeGroup'. Because the Dependent Role
properties are not the key properties, the upper bound of the
multiplicity of the Dependent Role must be '*'.
.....
and so on.
Use the "Fluent Api" !
In your context class write (for example) :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>()
.HasOptional(t => t.Nirikshak)
.WithMany(t => t.GroupsOfNirikshak)
.HasForeignKey(t => t.NirikshakId);
}
You can remove the annotations on the classes and properties. Once you have a Model-based Class on another, Entity Framework will automatically create a foreign key relationship on it. It will process Yuvak as a node on the Group Graph object. You need not declare the annotations since EF will do that for you automatically.
I'm developing an Entity Framework Code First (v. 4.4.0.0) C# library with .Net Framework 4.0.
I don't know how to set zero-to-one relationship. My model is the following:
A Talk can be created by only one user (StarterUserId).
A Talk can have only one recipient user (RecepientUserId) or only one group (RecipientGroupId).
Note: That means that RecepientUserId is null if RecipientGroupIdis not null; or RecepientUserId is not null if RecipientGroupIdis null.
A user can be a recipient of zero or n Talks, but a group can have zero or one Talk.
This is Talk class:
[DataContract]
public class Talk
{
[DataMember]
public int TalkId { get; set; }
[DataMember]
public int StarterUserId { get; set; }
[DataMember]
public int? RecipientUserId { get; set; }
[DataMember]
[ForeignKey("RecipientGroup")]
public int? RecipientGroupId { get; set; }
public DateTime DateUtcStarted { get; set; }
[DataMember]
public string DateStarted
{
get
{
return DateUtcStarted.ToString("dd/MM/yyyy HH:mm");
}
set
{
DateUtcStarted = DateTime.Parse(value);
}
}
public User StarterUser { get; set; }
public User RecipientUser { get; set; }
public Group RecipientGroup { get; set; }
}
With this TalkConfiguration class:
class TalkConfiguration : EntityTypeConfiguration<Talk>
{
public TalkConfiguration()
{
Property(t => t.StarterUserId).IsRequired();
Property(t => t.RecipientUserId).IsOptional();
Property(t => t.RecipientGroupId).IsOptional();
Property(t => t.DateUtcStarted).IsRequired();
Ignore(t => t.DateStarted);
HasRequired(t => t.StarterUser).
WithMany(u => u.TalksStarted).
HasForeignKey(t => t.StarterUserId);
HasOptional(t => t.RecipientUser).
WithMany(u => u.InTalks).
HasForeignKey(t => t.RecipientUserId);
HasOptional(t => t.RecipientGroup).WithOptionalDependent(g => g.GroupTalk);
}
}
And this is the Group class:
[DataContract]
public class Group
{
[DataMember]
public int GroupId { get; set; }
[ ... ]
public Talk GroupTalk { get; set; }
}
And the GroupConfiguration class:
class GroupConfiguration : EntityTypeConfiguration<Group>
{
public GroupConfiguration()
{
[ ... ] // Nothing related to GroupTalk
}
}
With these classes and configurations I get this Talk table at database:
I want to make Talk.RecipientGroupId as a FOREIGN KEY to Group.GroupId. But this model creates another column, Talk.RecipientGroup_GroupId as FOREIGN KEY to Group.GroupId. And, I don't want that.
How can I do it?
Optional:optional one-to-one relationships are mapped as independent associations, not as foreign key associations which means that you can't have a foreign key property in your model class. That's why you can't chain HasForeignKey after WithOptionalDependent. And I'm pretty sure that the [ForeignKey] attribute on RecipientGroupId is simply ignored and EF considers RecipientGroupId as an ordinary scalar property with no relationship purpose.
In the database schema itself the relationship has a foreign key. That's the one you are seeing with an autogenerated default name: RecipientGroup_GroupId. But it's not supported to map this foreign key to a property. However, I think you can rename the column using MapKey
HasOptional(t => t.RecipientGroup)
.WithOptionalDependent(g => g.GroupTalk)
.Map(m => m.MapKey("RecipientGroupId"));
If you do that you must remove the RecipientGroupId property from the Talk class, otherwise EF will complain about two conflicting columns with the same name.
I believe, optional:optional are the only one-to-one relationships that are independent associations, all other are foreign key associations where the foreign key property is the primary key property at the same time (according to Arthur Vickers' answer at the bottom of this thread). With optional:optional relationships this would be impossible because a primary key property cannot be nullable.
Since your RecipientGroupId has a [DataMember] attribute it looks that you want to transmit the value over some service boundary and therefore need the foreign key as property value for some reason. In this case the workaround that I would choose is mapping the Talk<->Group relationship as one-to-many relationship with either no navigation property in the Group class at all (mapping it with a parameterless WithMany() call then) or with a collection navigation property and ensure then in business logic that this collection cannot contain more than one element.
I want a straightforward foreign key relationship between two entities, with EF properties going both ways. However, at the moment EF is generating 2 foreign keys representing each direction of the relationship. How can I make EF treat them as a single property? The below generates two database foreign key constraints named Script_ScriptRuns and ScriptRun_Script, using foreign key field Script_Id and Script_Id1 respectively
public class Script
{
[Key]
public Guid Id { get; set; }
public virtual ICollection<ScriptRun> ScriptRuns { get; set; }
}
public class ScriptRun
{
[Key]
public Guid Id { get; set; }
[Required]
public virtual Script Script { get; set; }
}
Using the code you posted, it only created a single FK for me - Script_Id in the ScriptRun class.
Are you simply wanting to rename the FK that's getting created in that class? You can do that by mapping the column in your DbContext. This would map it to ScriptId:
modelBuilder.Entity<Script>()
.HasMany(s => s.ScriptRuns)
.WithRequired(sc => sc.Script)
.Map(x => x.MapKey("ScriptId"));
If not, can you post your entire model?