I got 2 classes i want to be able to create a ForeignKey to a field that is not the primarykey of the class
my BeerDispense need to point to my Master with an int with the field called MacAdrInt
This way i make my MacAdrInt field point to the primarykey of my master, but i want it to point to MacAdrInt, is this possible or do i need to make it my primarykey?
public class Master
{
[Key]
public long Id { get; set; }
public string MacAdr { get; set; }
public long MacAdrInt { get; set; }
...
}
public class BeerDispense
{
public long Id { get; set; }
[ForeignKey("MacAdrInt")]
public Master Master { get; set; }
...
}
i want it to point to MacAdrInt, is this possible or do i need to make it my primarykey?
In EF Core this is possible using Alternate Keys. In EF6 you need configure MacAdrInt as your Key property. It does not have to be the real primary key, but EF6 only supports one Key per entity.
Related
I´m currently learning to use EF and I have the following relationships:
An Alert has 1 to n occurences.
Each occurence can have 0 to n values (additional information).
public class Alert
{
// PK
public int AlertId { get; set; }
// Attributes
public int CurrentAlertLevel { get; set; }
public DateTime TimeRaised { get; set; }
public DateTime TimeLastRaised { get; set; }
// Some other attributes ommitted...
// Relations
public ICollection<AlertOccurrence> Occurrences { get; set; }
}
public class AlertOccurrence
{
// Relations which are part of the primary key
public int AlertId { get; set; }
// Attributes
public int Ordinal { get; set; }
// some ommited attributes
// Relations
public ICollection<AlertDetailValue> AlertDetailValues { get; set; }
}
public class AlertDetailValue
{
public int AlertDetailValueId { get; set; }
public int Order { get; set; }
public string Value { get; set; }
}
In the DB Context OnModelCreating I´m setting the combined PK for AlertOccurence:
modelBuilder.Entity<AlertOccurrence>().HasKey(ao => new {ao.AlertId, ao.Ordinal});
While it seems that this is working - what I would actually like to archive is the same relationship without the need to have the AlertDetailValueId as PK. The table that EF generates also includes AlertOccurrenceAlertId and AlertOccurrenceOrdinal which seems a waste of space to me.
So what I would like to do is:
Have a combined primary key for AlertDetailValue consisting of AlertDetailValue.Order and the (already combined) PK of AlertOccurence instead of the "artificial" AlertDetailValueId. Is that even possible ?
Part of my problem might be that the PK defined using the fluent api is not part of the data classes. So probably another question to ask would be: Is there a way to use a key defined in fluent api in a entity class ?
Or do I need to include AlertOccurrenceAlertId and AlertOccurrenceOrdinal in my entity class AlertDetailValue - but how do I link them then ?
As I said I´m still trying to get my head around EF so while there might be better ways to do this I´m interested in this special kind of relation / combined(combined) PK even if it might be academic... Any help would be highly appreciated.
Trying to explain what I try to do and what my problem is - and taking a good shower - helped me to ask different questions to google and focus more on the foreign key.
It´s not that I did not try to google it before... I just asked the wrong questions..
So I found this:
Mapping composite foreign key to composite primary key where the foreign key is also a primary key
(While trying the new approch #atiyar also hinted on the missing foreign key...)
My new solution was to change AlertDetailValue to intentionally include the parts that the Occurence PK is build of:
public class AlertDetailValue
{
// relations will be set up in fluent api in OnModelCreating of db context
public int AlertOccurenceAlertId { get; set; }
public int AlertOccurenceOrdinal { get; set; }
public int Order { get; set; }
public string Value { get; set; }
}
And then to tell EF that there is a combined PK and also a combined foreign key:
modelBuilder.Entity<AlertDetailValue>().HasKey(adv => new { adv.AlertOccurenceAlertId, adv.AlertOccurenceOrdinal, adv.Order });
modelBuilder.Entity<AlertOccurrence>().HasMany<AlertDetailValue>(adv => adv.AlertDetailValues).WithOne()
.HasForeignKey(adv => new {adv.AlertOccurenceAlertId, adv.AlertOccurenceOrdinal});
I'm trying add migration using EF core 2 code first method. The issue is that, the entities with foreign key relationship are created with a foreign key id suffixed with '1' at the end and a redundant column with the same name but without the 1 at the end which is not a foreign key.
Examples are my 2 classes, Store and StoreVisit as shown below:
Store
[Table("Store")]
public class Store
{
public Store()
{
StoreVisits = new HashSet<StoreVisit>();
}
[Key]
public int StoreId { get; set; }
[StringLength(30)]
public string ShopName { get; set; }
[StringLength(50)]
public string ShopKeeper { get; set; }
public string ContactNo { get; set; }
[StringLength(70)]
public string Address { get; set; }
[StringLength(20)]
public string Street { get; set; }
[StringLength(50)]
public string City { get; set; }
public IEnumerable<StoreVisit> StoreVisits { get; set; }
}
Store Visit
[Table("StoreVisit")]
public class StoreVisit
{
[Key]
public int StoreVisitId { get; set; }
[StringLength(50)]
public string Location { get; set; }
[StringLength(50)]
public string Notes { get; set; }
[DataType(DataType.Time)]
public DateTime StartTime { get; set; }
[DataType(DataType.Time)]
public DateTime EndTime { get; set; }
public Store Store { get; set; }
}
The Visit class is created in the database with the column shown in the image below:
As you can see, the StoreVisit table has columns "StoreId1" which is the actual foreign key and "StoreId" which is not a foreign key.
I have even configured the relationship with Fluent API as below:
modelBuilder.Entity<Store>()
.HasMany(c => c.StoreVisits)
.WithOne(e => e.Store)
.IsRequired();
Can someone help.
Note that Entity Framework Core is smart enough to detect relationships among your classes which will be turned into database tables with relationships if you use its conventions. So this is redundant to use annotations like [Key] above StoreId property.
Second thing, As an advice, try to use simple and clean names for classes or properties as they can be potentially similar to those automatically created by EF. For example, in your case I prefer to avoid using store inside StoreVisit class name again (e.g in case of many to many relationship, derived table is named StoreVisit like one that you employed just without 's', Although your case is one to many),
And Final tip is the reason for appearing redundant StoreId column. Actually, In your case, this is not necessary to use Fluent API as EF can detect the relationship. In addition, you've written wrong configuration for modelBuilder. So remove it and let EF to generate it (unless you plan to have fully defined relationship to consume its advantages in your code).
The StoreId is one that you told EF to generate it (as required)
in modelBuilder.
The StoreId1 is EF Auto generated column (Foreign Key) based on one
to many relationship. '1' is appended in order to avoid column name duplication.
A foreign key needs to be defined on the class.
[Table("StoreVisit")]
public class StoreVisit
{
[Key]
public int StoreVisitId { get; set; }
public int StoreId { get; set; }
[StringLength(50)]
public string Location { get; set; }
[StringLength(50)]
public string Notes { get; set; }
[DataType(DataType.Time)]
public DateTime StartTime { get; set; }
[DataType(DataType.Time)]
public DateTime EndTime { get; set; }
public Store Store { get; set; }
}
It also would hurt to add the foreign key reference to the Fluent API.
modelBuilder.Entity<Store>()
.HasMany(c => c.StoreVisits)
.WithOne(e => e.Store)
.HasForeignKey(e => e.StoreId)
.IsRequired();
I'm continuing my venture with Entity Framework, but now I'm stuck again. I've two small classes:
public class Machine
{
public int MachineID { get; set; }
public string Name { get; set; }
[Required]
[ForeignKey("InstalledByID")]
public virtual Employee InstalledBy { get; set; }
public int InstalledByID { get; set; }
[Required]
[ForeignKey("LastServicedID")]
public virtual Employee LastServiced { get; set; }
public int LastServicedID { get; set; }
}
public class Employee
{
public int EmployeeID { get; set; }
public string Name { get; set; }
}
I'm trying to set two properties: InstalledBy and LastServiced. Both of these properties are instances of an Employee class. Additionally, I could care less that there are two REVERSE properties on the Employee class that tells a list of machines installed as well as machines repaired. When I try to Update-Database after a Migration, I get an error. I thought the ForeignKeyAttribute was the key, but apparently it's not working for me. Here is the error:
Introducing FOREIGN KEY constraint 'FK_dbo.Machines_dbo.Employees_InstalledByID' on table 'Machines' 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.
Also, I've tried dabbling in the OnModelCreating and FluentAPI, but my attempts did not work. Any pointers as to what I'm doing wrong and how to fix it would be greatly appreciated. Thank you, all.
Is it possible to do foreign key without blablaId?
I mean next situation
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; } // Why should I have this ugly property?
public virtual Blog Blog { get; set; }
}
My models are core project. But I should play EF rules. It means core project depend on DAL project.
It's not required declare the FK property in your entity. If you delete it, EF will create it for you in your DB.
However, imagine if the Blog is not in memory, this would require you to first execute a query on the database to retrieve that Blog so that you can set the property. There are times when you may not have the object in memory, but you do have access to that object’s key value. With a foreign key property, you can simply use the key value without depending on having that instance in memory:
currentPost.BlogId=2;
This is making me feel like an idiot. Entity Framework is supposed to be fairly simple, yet I can't sort this out myself and clearly I've got a fundamental misunderstanding. I hope it doesn't turn out to be an idiot-question - sorry if it is.
Three code-first objects, related to one another.
public class Schedule
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
public class Charge
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public decimal Rate { get; set; }
public Type Type { get; set; }
}
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
}
When I query this, I want all related types, so:
Schedule currentSchedule = _Context.Schedules
.Include("Charges.Type")
.Where(cs => cs.Start < dateWindow && cs.End > dateWindow)
.First();
In C#, this has been working fine.
The problem arises because we're not stopping at C#, but passing the data onto a javascript library called Breeze with smooths out data operations at the client end. Breeze has a bug/feature which demands that EF relationships between objects be specified at BOTH ENDS. So when I do my query above, I don't end up with any Types, because their relationship with Charge isn't directly specified.
So I change it to this:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
Because virtual is a navigation property, so that should enable Breeze should now to go both ways across the relationship without changing the data structure. But EF doesn't like this. It tells me:
Unable to determine the principal end of an association between the
types 'Core.Charge' and 'Core.Type'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations
Fair enough. I can see how this could be confusing. Now, my understanding is that if you define a foreign key in a dependent class, it has to be that classes' primary key. So we change it to:
public class Type
{
[Key, ForeignKey("Charge"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
And that seems to work but ... it's stopped loading any Type information when you ask for a schedule. Messing around with the includes doesn't seem to do anything at all.
What's going on, and what have I done wrong?
You haven't only added a navigation property (Type.Charge) to an existing model/relationship. Instead you have changed the relationship completely from a one-to-many to a one-to-one relationship because by default if a relationship has only one navigation property EF assumes a one-to-many relationship. With your change you have configured a one-to-one relationship.
Those relationships have different foreign keys: The original one-to-many relationship has a separate foreign key in the Charge table (probably named Type_RowId or similar). Your new relationship has the foreign key at the other side in table Type and it is the primary key RowId. The Charges you are loading together with the Schedule probably don't have any related Type with the same primary key, hence no Type is loaded.
If you actually want to reproduce the old (one-to-many) relationship with just a navigation property at the other side you must add a collection to Type instead of a single reference:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
Are you sure that you want to put ForeignKey on RowId, I think you may want to define some relationship like this
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public int ChargeId { get; set; }
[ForeignKey("ChargeId")]
public virtual Charge Charge { get; set; }
}