I am a newbie in the EF. I read http://msdn.microsoft.com/en-us/data/gg193958.aspx and still confused.
I have an existing database and I'm writing a Code First model. I have Operators table with op_code Char(6) Primary Key. In the Operator class I named it OperatorCode, e.g.
[Key]
[Column("op_code",TypeName = "char")]
[DisplayName("Operator")]
public virtual string OperatorCode { get; set; }
In several of my tables I have EnteredBy and in some ModifiedBy columns that are FK to the op_code.
Say, for the Clients table I have both of these fields.
So, I added to the Operator class at the bottom:
[InverseProperty("EnteredBy")]
public virtual ICollection<Client> ClientsEnteredBy { get; set; }
[InverseProperty("ModifiedBy")]
public virtual ICollection<Client> ClientsUpdatedBy { get; set; }
and I added the following into the Client class:
public virtual Operator EnteredBy { get; set; }
public virtual Operator ModifiedBy { get; set; }
and I am getting a run-time error about EnteredBy_OperatorCode and ModifiedBy_OperatorCode columns.
What should I fix /add to let EF know my column names?
Thanks in advance.
Your foreign column names in the database do not match the default convention for FK names which is NavigationPropertyName_PrimaryKeyNameinTargetClass. Because your navigation properties are called EnteredBy and ModifiedBy and the primary key property is called OperatorCode EF expects - according to the mentioned convention - EnteredBy_OperatorCode and ModifiedBy_OperatorCode as foreign key columns. But those do not exist in the database which is the reason for your exception. Instead your FK columns are EnteredBy and ModifiedBy.
So, to fix the problem you must override the convention.
If you don't have FK properties in your model use Fluent API:
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsEnteredBy)
.WithRequired(c => c.EnteredBy) // or HasOptional
.Map(m => m.MapKey("EnteredBy")); // mapping for the FK column name
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsUpdatedBy)
.WithRequired(c => c.ModifiedBy) // or HasOptional
.Map(m => m.MapKey("ModifiedBy")); // mapping for the FK column name
(With this mapping you can remove the InverseProperty attribute.)
An alternative approach is to expose the FKs as properties in the model. Rename the navigation properties and use their names for the FK properties. The mapping is then possible with data annotations.
In Client class:
[ForeignKey("EnteredByOperator")]
public string EnteredBy { get; set; }
[InverseProperty("ClientsEnteredBy")]
public virtual Operator EnteredByOperator { get; set; }
[ForeignKey("ModifiedByOperator")]
public string ModifiedBy { get; set; }
[InverseProperty("ClientsUpdatedBy")]
public virtual Operator ModifiedByOperator { get; set; }
And remove the InverseProperty attributes in the Operator class.
Instead of the data annotations you can also use Fluent API:
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsEnteredBy)
.WithRequired(c => c.EnteredByOperator) // or HasOptional
.HasForeignKey(c => c.EnteredBy);
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsUpdatedBy)
.WithRequired(c => c.ModifiedByOperator) // or HasOptional
.HasForeignKey(c => c.ModifiedBy);
If both relationships are required you will need to disable cascading delete for at least one of the relationships (append .WillCascadeOnDelete(false) at the end of one of the mappings), otherwise SQL Server will throw an error that multiple cascading delete paths between the tables are not allowed.
I would suggest to use the "alternative approach" (expose foreign keys as properties) because it is easier to work with in most cases.
Related
I have two entities:
public class EntityA
{
public int? Id { get; set; }
public string Name { get; set; }
public EntityB { get; set; }
}
public class EntityB
{
public int? Id { get; set; }
public string Version { get; set; }
}
I have existing records for EntityB already in the database. I want to add a new EntityA with reference to one of the EntityB records.
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
var entityA = new EntityA { Name = "Test", EntityB = entityB };
_dbContext.Add(entityA);
_dbContext.SaveChanges();
When the above code runs I get the following error:
System.InvalidOperationException: The property 'Id' on entity type 'EntityB' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
This seems to me, that the save is trying to also add EntityB, not just a reference to it. I do have the relationship specified in the database as well as in Entity Framework, e.g. when querying for EntityA if I include EntityB in the select, I get the referenced entity as well (so the relationship works).
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithOne()
.HasForeignKey<EntityB>(p => p.Id);
}
modelBuilder.Entity<EntityB>(e =>
{
e.HasKey(p => p.Id);
}
How can I save a new EntityA, with only a reference to the selected EntityB, rather than saving both entities?
It looks like you are trying to Extend EntityB with an optional 1:1 reference to a Row n the new table EntityA. You want both records to have the same value for Id.
This 1:1 link is sometimes referred to as Table Splitting.
Logically in your application the record from EntityB and EntityA represent the same business domain object.
If you were simply trying to create a regular 1 : many relationship, then you should remove the HasOne().WithOne() as this creates a 1:1, you would also not try to make the FK back to the Id property.
The following advice only applies to configure 1:1 relationship
you might use Table Splitting for performance reasons (usually middle tier performance) or security reasons. But it also comes up when we need to extend a legacy schema with new metadata and there is code that we cannot control that would have broken if we just added the extra fields to the existing table.
Your setup for this is mostly correct, except that EntityA.Id cannot be nullable, as the primary key it must have a value.
public class EntityA
{
public int Id { get; set; }
public string Name { get; set; }
public EntityB { get; set; }
}
If you want records to exist in EntityA that DO NOT have a corresponding record in EntityB then you need to use another Id column as either the primary key for EntityA or the foreign key to EntityB
You then need to close the gap with the EntityA.Id field by disabling the auto generated behaviour so that it assumes the Id value from EntityB:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne()
.HasForeignKey<EntityB>(p => p.Id);
}
I would probably go one step further and add the Reciprocating or Inverse navigation property into EntityB this would allow us to use more fluent style assignment, instead of using _dbContext.Add() to add the record to the database:
public class EntityB
{
public int Id { get; set; }
public string Version { get; set; }
public virtual EntityA { get; set; }
}
With config:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne(p => p.EntityA)
.HasForeignKey<EntityB>(p => p.Id);
}
This allows you to add in a more fluent style:
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
entityB.EntityA = new EntityA { Name = "Test" };
_dbContext.SaveChanges();
This will trip up because you are using EntityA's PK as the FK to Entity B, which enforces a 1 to 1 direct relation. An example of this would be to have something like an Order and OrderDetails which contains additional details about a specific order. Both would use "OrderId" as their PK and OrderDetails uses it's PK to relate back to its Order.
If instead, EntityB is more like an OrderType reference, you wouldn't use a HasOne / WithOne relationship because that would require Order #1 to only be associated with OrderType #1. If you tried linking OrderType #2 to Order #1, EF would be trying to replace the PK on OrderType, which is illegal.
Typically the relationship between EntityA and EntityB would require an EntityBId column on the EntityA table to serve as the FK. This can be a property in the EntityA entity, or left as a Shadow Property (Recommended where EntityA will have an EntityB navigation property) Using the above example with Order and OrderType, an Order record would have an OrderId (PK) and an OrderTypeId (FK) to the type of order it is associated with.
The mapping for this would be: (Shadow Property)
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithMany()
.HasForeignKey("EntityBId");
}
An OrderType can be assigned to many Orders, but we don't have an Orders collection on OrderType. We use the .HasForeignKey("EntityBId") to set up the shadow property of "EntityBId" on our EntityA table. Alternatively, if we declare the EntityBId property on our EntityA:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithMany()
.HasForeignKey(p => p.EntityBId);
}
On a side note, navigation properties should be declared virtual. Even if you don't want to rely on lazy loading (recommended) it helps ensure the EF proxies for change tracking will be fully supported, and lazy loading is generally a better condition to be in at runtime than throwing NullReferenceExceptions.
EF Core 3.0... I can't find a precise answer for this completely normal mapping.
Principal to Dependent with no back pointer to Principal, 1:0 relationship, a Type Object / Lookup table set up. The problem is that the Object Key Name "RunId" is different than the EFCore generated key name "ServiceRunId"
How can I use Fluent API to replace the [ForeignKey("aServiceRun")] annotation?
This is my current Fluent set up, but I don't know where to put the ForeignKey mapping.
aBuilder.Entity<ServiceRun>().HasKey(new string[] { "RunId "});
aBuilder.Entity<Service>().HasOne(s => s.aServiceRun);
Class Service {
public int ServiceId {get; set;}
[ForeignKey("aServiceRun")]
public int RunId { get; set; }
public virtual ServiceRun aServiceRun { get; set; }
}
Class ServiceRun {
public int RunId { get; set; }
public string description {get ;set; }
}
Tables:
Service {
ServiceId int
RunId int
}
SerivceRun {
RunId int
Description string
}
How can I use Fluent API to replace the [ForeignKey("aServiceRun")] annotation?
You are seeking for HasForeignKey fluent API. But in order to get access to it (and other relationship configuration APIs), you need to define the relationship by using Has{One|Many} followed by With{One|Many}. For one-to-one relationships you also need to provide the generic type argument to HasForeignKey:
When configuring the relationship with the Fluent API, you use the HasOne and WithOne methods.
When configuring the foreign key you need to specify the dependent entity type - notice the generic parameter provided to HasForeignKey in the listing below. In a one-to-many relationship it is clear that the entity with the reference navigation is the dependent and the one with the collection is the principal. But this is not so in a one-to-one relationship - hence the need to explicitly define it.
Note that the entity containing the FK is always the dependent, so with your model the ServiceRun is the principal, Service is the dependent, and the fluent configuration is a follows:
modelBuilder.Entity<Service>()
.HasOne(s => s.aServiceRun) // navigation property
.WithOne() // no navigation property
.HasForeignKey<Service>(s => s.RunId); // foreign key
I found my answer to the above problem - I had a back-pointing list on my ServiceRun object that was not configured or ignored. I decided to leave this here as another example. Perhaps it will provide some worth to someone.
This is a 1:0 from Service to ServiceRunType where table names and property/field names don't match perfectly.
Tables
ServiceRun { //Does not match object name
int Id,
string Desc
}
Service {
int Id,
int RunId //Does not match object
}
Objects
Class ServiceRunType{ //Does not match table name
public int Id {get; set;}
public String Desc {get; set;}
}
Class Service{
public int Id {get; set;}
public int RunTypeId {get; set;} //Does not match table
public virtual ServiceRunType aServiceRunType { get; set; }
}
Fluent Code
modelBuilder.Entity<ServiceRunType>()
.ToTable("ServiceRun", schema: "load")
.HasKey(new string[] { "Id" });
modelBuilder.Entity<Service>()
.ToTable("Service", schema: "load") //Had to specify schema
.HasKey(new string[] { "Id" });
modelBuilder.Entity<Service>()
.Property("RunTypeId")
.HasColumnName("RunId");
modelBuilder.Entity<Service>()
.HasOne(s => s.aServiceRunType)
.WithOne()
.HasForeignKey<Service>(s => s.RunTypeId);
Using EF Core 3.0 I am trying to configure a one-to-many navigation between the following two classes.
public class Organization
{
public decimal Id{get;set;}
public int ClientId{get;set;}
public virtual ICollection<Contact> Contacts { get; set; }
}
public class Contact
{
public int Id{get;set;}
public int? ClientId{get;set;}
public virtual Organization Organization { get; set; }
}
I need to have the navigation use the ClientId fields rather than any the Primary Keys.
I have tried both annotations and Fluent
modelBuilder.Entity<Contact>()
.HasOne<Organization>(o => o.Organization)
.WithMany(c => c.Contacts)
.HasForeignKey("ClientId");
But for some reason it keeps going back to connection the ClientId to the Id of the Organization.
How can this be configured?
Yes the Id field in the legacy db is a decimal but I can not change that.
Assuming the database already has a Foreign Key declared from Contact.ClientId to Organization.ClientId, try:
modelBuilder.Entity<Contact>()
.HasOne<Organization>(o => o.Organization)
.WithMany(c => c.Contacts)
.HasForeignKey(c => c.ClientId)
.HasPrincipalKey(org => org.ClientId);
For more details: Alternate Keys in EF Core
This should be fairly easy but I am struggling quite a bit.
My situation is as follows:
I have a table (Commodity) with a foreign key to another table (CommodityMaterial). In my GET endpoint I want to retrieve all the column data from Commodity and all the columns from CommodityMaterial as well.
I have set it up as follows:
public class Commodity
{
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
public ICollection<OmsCommodityMaterial> OmsCommodityMaterials { get; set; }
}
The Foreign key table:
public class CommodityMaterial
{
public long? CommodityMaterialID { get; set; }
public string Name { get; set; }
public long? SortOrder { get; set; }
public Commodity Commodity { get; set; }
}
The FLUENT API:
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.OmsCommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
This creates the proper relationship, a CommodityMaterial (ID) is needed when creating a Commodity.
Now I want to get all values / columns of the Commodity table, but also the related columns of the CommodityMaterial table (CommodityMaterialID, Name and SortOrder). After reading some other posts i found out I should/could use Include to get related data, my GET operation looks as follow:
[HttpGet]
public async Task<IEnumerable<Commodity>> GetTest()
{
return await Context.Commodity.Include(x => x.OmsCommodityMaterials).ToListAsync();
}
However, this still only returns the 3 columns of the Commodity table (CommodityID, CommodityMaterialID, SpecficWeight)
Anyone has a clue what I am doing wrong?
SOLUTION
Ok, I am a bit ashamed, the problem was the fact that I was using a DataContract annotation in my Commodity POCO class, which was omitted in the code above, the OmsCommodityMaterial reference was missing a DataMember attribute thus making it's fields invisible...
Although not an answer per se maybe you would find it yourself here.
Include()/ThenInclude() (Eager loading) is one of three possible solutions. I suggest you explore the other two as well - Explicit and Lazy.
EDIT: Further investigating your code it seems a bit strange to have a collection of entities in a one-to-one relationship. Maybe you would want to use just the class and (this is important!) specify class names in < and > when calling HasOne(). If you use ReSharper, or something similar, such tool suggest name simplification, that is to get rid of explicitly specifying the types when templating fluent methods. This is the next special place when EF gives slightly different meaning to known C# features. The first one is the very well-known virtuals.
HTH
I am a bit ashamed by this, the problem was the fact that I was using a DataContract annotation in my Commodity POCO class, which was omitted in the code above, the OmsCommodityMaterial reference was missing a DataMember attribute thus making it's fields invisible...
I have the following table being created (by using DNX commands in EF7, now EF Core)
[Table("FishGrade")]
public partial class FishGrade
{
public FishGrade()
{
FishPrices = new HashSet<FishPrice>();
}
[HiddenInput]
[Column("FishGradeId")]
public int Id { get; set; }
[Column("GradeCode")]
[MaxLength(5), Required]
public string Code { get; set; }
[Column("GradeName")]
public string Name { get; set; }
public string IsActive { get; set; }
public virtual ICollection<FishPrice> FishPrices { get; set; }
}
But when it the table is created, the Code column (column named=GradeCode), is created as a 1 character long column.
I also have the following in the OnModelCreating method
modelBuilder.Entity<FishGrade>(entity =>
{
entity.Property(e => e.Code)
.HasColumnType("char");
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
entity.Property(e => e.IsActive)
.HasMaxLength(1)
.HasColumnType("char");
});
Why is this happening? How can I get the column to be created with length = 5?
You can define your model in three ways:
conventions
attributes
fluent API
Conventions are applied first, then attributes, and finally the fluent API in your model builder. Your model builder is resetting the attribute configuration.
You should try to simplify your model configuration and use always the same method.
NOTE: take into account that, if you use something like MVC client side validation, it only understands the configuration made via attributes. In all other regards it doesn't mind how you configure your EF model.