My scenario is for investment in properties (buildings). An investor makes an investment in a property group, and that group can contain several individual properties. As investors can invest in multiple groups, and any group can have multiple investors, the investment itself is a many-to-many relationship between the investors and property groups.
Ignoring the simple properties for clarity, the models look like this...
public class Investor {
public int ID { get; set; }
public ObservableCollection<Investment> Investments { get; set; }
}
public class Investment {
public int ID { get; set; }
public int InvestorID { get; set; }
public virtual Investor Investor { get; set; }
public int PropertyGroupID { get; set; }
public virtual PropertyGroup PropertyGroup { get; set; }
}
public class PropertyGroup {
public int ID { get; set; }
public ObservableCollection<Property> Properties { get; set; }
public ObservableCollection<Investment> Investments { get; set; }
}
public class Property {
public int ID { get; set; }
public int PropertyGroupID { get; set; }
public virtual PropertyGroup PropertyGroup { get; set; }
}
This all works fine.
I now have the requirement to generate and store documents. These will always be associated with a specific investor, but may also be associated with a property or a group.
I added the following class...
public class Document {
public int ID { get; set; }
// Other simple properties omitted for clarity
public int InvestorId { get; set; }
public Investor Investor { get; set; } = null!;
public int? PropertyGroupId { get; set; }
public PropertyGroup? PropertyGroup { get; set; }
public int? PropertyId { get; set; }
public Property? Property { get; set; }
}
In order to provide the navigation links into the documents, I added the following line into the Investor, PropertyGroup and Property models...
public ObservableCollection<Investment> Documents { get; set; }
When I try to add a migration, I get an error "Unable to determine the relationship represented by navigation 'Investment.Investor' of type 'Investor'."
Reading the output from the migration, I see a message "No relationship from 'Investment' to 'Investor' has been configured by convention because there are multiple properties on one entity type - {'Investor'} that could be matched with the properties on the other entity type - {'Documents', 'Investments'}."
I don't understand either message, as I do have an explicit relationship defined from Investment to Investor as you can see above. I have an int property called InvestorId and a navigation property of type (and name) Investor. I'm not sure what the last bit of the second message means.
However, I added the following to OnModelCreating...
builder.Entity<Investment>()
.HasOne<Investor>()
.WithMany(i => i.Investments)
.HasForeignKey(i => i.InvestorID)
.OnDelete(DeleteBehavior.Restrict);
This got rid of that error, but gave a similar one, "Unable to determine the relationship represented by navigation 'Investment.PropertyGroup' of type 'PropertyGroup'." I added the following...
builder.Entity<Investment>()
.HasOne<PropertyGroup>()
.WithMany(i => i.Investments)
.HasForeignKey(i => i.PropertyGroupID)
.OnDelete(DeleteBehavior.Restrict);
This allowed the migration to be added, but it included the following in the Up method...
migrationBuilder.AddColumn<int>(
name: "InvestorID1",
table: "Investments",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "PropertyGroupID1",
table: "Investments",
type: "int",
nullable: false,
defaultValue: 0);
I can see the migration output contains a warning "The foreign key property 'Investment.InvestorID1' was created in shadow state because a conflicting property with the simple name 'InvestorID' exists in the entity type, but is either not mapped, is already used for another relationship, or is incompatible with the associated primary key type." but I don't understand what it means.
Anyone able to explain what I'm doing wrong?
Thanks
There is something missed(typo), to make relation with Document you have to define collection props of Document not Investments so change
public ObservableCollection<Investment> Documents { get; set; }
to
public ObservableCollection<Document> Documents { get; set; }
Related
I have cross relations with the same table, in 3rd object.
When I try to insert new object, got an error :
Multiplicity constraint violated. The role
'OrgOwners_Organisation_Target' of the relationship
'GBankDataSource.OrgOwners_Organisation' has multiplicity 1 or 0..1.
I've tried to annotate [ForeignKey("...")] in any of the classes, but nothing happend. EF allways choose one field (OrgRefID in this sample) and use it or both relations, while OrgID are not used.
public class OrganisationInfo
{
[Key]
public int OrgID { get; set; }
...
public virtual List<OrgOwners> OrgOwners { get; set; } // object that throws error
}
public class OrgOwners
{
[Key]
public int OrgOwnerID { get; set; }
public int OrgID { get; set; } //Suppose to be a ForeignKey for (OrganisationInfo OrgOwners List)
public int? OrgRefID { get; set; }
...
[ForeignKey("OrgRefID")]
public virtual OrganisationInfo Organisation { get; set; } //(Suppose to use OrgRefID as ForeignKey)
}
When I add a record to OrgOwners without Organisation ( Organisation =null) - it is OK. But when I do
var first = new OrganisationInfo(); //First organisation DB.OrganisationInfoes.Add(first);
var nextOrg = new OrganisationInfo(); //second organisation
first.OrgOwners = new list();
var Owner = new OrgOwners(); Owner.Organsiation = nextOrg;
first.OrgOwners.Add(Owner); // Add Owner with the second organisation to the First one.
I got an error.
Multiplicity constraint violated.
OrgOwner.Organisation - is NOT the same OrganisationInfo as in root of OrgOwners list. It must be different OrganisationInfo items, related to OrgRefID ForeignKey.
It's because EF by default automatically "pairs" the navigation properties where possible to form a relationship. In your case, it pairs OrganizationInfo.OrgOwners collection navigation property with OrgOwners.Organization reference navigation property, hence takes and uses the associated with it OrgRefID FK.
One way to resolve the issue is to add a second reference navigation property to OrgOwners and associate it with the OrgID property via ForeignKey attribute and OrganizationInfo.OrgOwners collection navigation property via InverseProperty attribute:
public int OrgID { get; set; } //Suppose to be a ForeignKey for (OrganisationInfo OrgOwners List)
[ForeignKey("OrgID")]
[InverseProperty("OrgOwners")]
public virtual OrganisationInfo OwnerOrganization { get; set; }
To do that without changing the entity model, you should configure the relationship via fluent API:
modelBuilder.Entity<OrganisationInfo>()
.HasMany(e => e.OrgOwners)
.WithRequired() // no inverse navigation property
.HasForeignKey(e => e.OrgID); // <--
Full worked example:
public class OrganisationInfo
{
[Key]
public int OrgID { get; set; }
public virtual List<OrgOwners> OrgOwners { get; set; }
}
public class OrgOwners
{
[Key]
public int OrgOwnerID { get; set; }
public int OrgID { get; set; }
public int? OrgRefID { get; set; }
[ForeignKey("OrgRefID")]
public virtual OrganisationInfo Organisation { get; set; }
}
modelBuilder.Entity<OrganisationInfo>()
.HasMany(e => e.OrgOwners)
.WithRequired()
.HasForeignKey(e => e.OrgID);
How can Entity Framework Code First models be configured so collections with strings as IDs/foreign keys cascadingly delete when parents are deleted?
Geofence => Collections doesn't contain OnDelete(DeleteBehavior.Cascade) in the generated code. Vehicles => Trips, on the other hand, contains OnDelete(DeleteBehavior.Cascade). The only pertinent difference is that Vehicles' ID is an int, while Geofence's ID is a string.
public class Geofence
{
[JsonProperty(PropertyName = "id")]
[Key]
public string ID { get; set; }
[JsonProperty(PropertyName = "color")]
public string Color { get; set; }
[JsonProperty(PropertyName = "coordinates")]
[Required]
public List<Coordinate> Coordinates { get; set; }
}
public class Coordinate
{
[JsonIgnore]
[Key]
public string ID { get; set; }
[JsonIgnore]
public string GeofenceID { get; set; }
[JsonProperty(PropertyName ="lat")]
[Required]
public double Latitude { get; set; }
[JsonProperty(PropertyName = "lng")]
[Required]
public double Longitude { get; set; }
}
public class Vehicle
{
[Key]
public int ID { get; set; }
public string VehicleName { get; set; }
public List<Trip> Trips { get; set; }
}
public class Trip
{
[Key]
public int ID { get; set; }
public int VehicleID { get; set; }
public bool InProgress { get; set; }
public DateTime Start { get; set; }
}
Generates the configuration code:
modelBuilder.Entity("VTWeb.Models.Coordinate", b =>
{
b.HasOne("VTWeb.Models.Geofence")
.WithMany("Coordinates")
.HasForeignKey("GeofenceID");
});
modelBuilder.Entity("VTWeb.Models.VehicleViewModels.Trip", b =>
{
b.HasOne("VTWeb.Models.VehicleViewModels.Vehicle")
.WithMany("Trips")
.HasForeignKey("VehicleID")
.OnDelete(DeleteBehavior.Cascade);
});
The only pertinent difference is that Vehicles' ID is an int, while Geofence's ID is a string
This is quite significant difference, because string is a reference type, hence is nullable by default. Thus, without additional configuration the relationship is considered optional, and the default delete behavior of optional relationships is to not cascade.
There are several ways you can configure the cascade delete, with the most obvious being the fluent API. However, the simplest is to make the relationship required. The only thing you need to know is that [Required] attribute has no effect when applied to collection navigation property - it has to be applied to either reference navigation property or FK property.
In your example, there is no reference navigation property, so it has to be on the FK property:
public class Coordinate
{
// ...
[JsonIgnore]
[Required] // <--
public string GeofenceID { get; set; }
//..
}
Note that applying [Required] attribute on value type properties (int, double like in your example etc.) doesn't hurt, but is redundant since they cannot hold null values. For value types the requiredness is basically controlled by whether you use nullable type or not. So the main usage of [Required] attribute is for string and reference navigation properties.
For completeness, or if you want to keep the relationship optional and still have cascade delete, here is the minimal fluent configuration needed:
modelBuilder.Entity<Geofence>()
.HasMany(e => e.Coordinates)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
I'm missing something when using the data annotations.
This is my first class
[Table("PriceFeed")]
public class PriceFeed : History
{
public PriceFeed()
{
this.Votes = new List<PriceVote>();
this.History = new List<PriceFeed__History>();
}
[Key]
public long Id { get; set; }
[ForeignKey("Store")]
public long Store_Id { get; set; }
[ForeignKey("Item")]
public long Item_Id { get; set; }
[Required]
public decimal Price { get; set; }
public Store Store { get; set; }
public Item Item { get; set; }
public virtual ICollection<PriceFeed__History> History { get; set; }
}
And this is my second class
[Table("PriceFeed__History")]
public class PriceFeed__History : History
{
[Key]
public long Id { get; set; }
[ForeignKey("PriceFeed")]
public long PriceFeed_Id { get; set; }
[Required]
public decimal Price { get; set; }
public virtual PriceFeed PriceFeed { get; set; }
}
When I run the add-migration, it creates the database correctly but when I try to access PriceFeed.History it gives me an error
{"Message":"An error has occurred.","ExceptionMessage":"A specified Include path is not valid. The EntityType 'Verdinhas.Web.Contexts.PriceFeed' does not declare a navigation property with the name 'PriceFeed__History'."
I always worked with API Fluent and typed by myself the code like
.Entity<Student>()
.HasRequired<Standard>(s => s.Standard)
.WithMany(s => s.Students)
.HasForeignKey(s => s.StdId);
But now I'm using the data annotations and when I generate the migration, it does not create the "withmany" like the above.
What am I doing wrong?
The issue has nothing to do with Data Annotations which seems to be correct in your model.
As mentioned in the comments, the exception is caused by a code that tries to use Include method with string "'PriceFeed__History" - you seem to think that you should specify the related entity types, but in fact you need to specify the navigation property names, which in your case is "History".
I am having an issue mapping my tables together. I get the error:
Invalid column name 'Film_Id'.
Here are my Entities:
public class Film
{
[Key]
public Int32 Id { get; set; }
public String Title { get; set; }
public virtual ICollection<NormComparableFilm> NormComparableFilms { get; set; }
}
public class NormComparableFilm
{
[Key]
public Int32 Id { get; set; }
public Int32 FilmId { get; set; }
public Int32 ComparableFilmId { get; set; }
[ForeignKey("FilmId")]
public virtual Film Film { get; set; }
[ForeignKey("ComparableFilmId")]
public virtual Film ComparableFilm { get; set; }
}
Is there a custom mapping in the OnModelCreating() function that I need? I tried adding the following but it fails with a slightly different error:
modelBuilder.Entity<Film>()
.HasMany(f => f.NormComparableFilms)
.WithMany().Map(t => t.MapLeftKey("FilmId")
.MapRightKey("ComparableFilmId")
.ToTable("NormComparableFilms"));
The above gives this error:
Invalid object name 'dbo.NormComparableFilms1'.
I think I'm close but can't seem to get it just right. Any help would be appreciated.
The first error happened because you are creating two relationships between the same entities and Code First convention can identify bidirectional relationships, but not when there are multiple bidirectional relationships between two entities.The reason that there are extra foreign keys (Film_ID) is that Code First was unable to determine which of the two properties in NormComparableFilm that return a Film link up to the ICollection<NormComparableFilm> properties in the Film class. To resolve this Code First needs a little of help . You can use InverseProperty data annotation to specify the correct ends of these relationships, for example:
public class NormComparableFilm
{
[Key]
public int Id { get; set; }
public int FilmId { get; set; }
public int ComparableFilmId { get; set; }
[ForeignKey("FilmId")]
[InverseProperty("NormComparableFilms")]
public virtual Film Film { get; set; }
[ForeignKey("ComparableFilmId")]
public virtual Film ComparableFilm { get; set; }
}
Or remove the data annotation you already are using and add just these configurations:
modelBuilder.Entity<NormComparableFilm>()
.HasRequired(ncf=>ncf.Film)
.WithMany(f=>f.NormComparableFilms)
.HasForeignKey(ncf=>ncf.FilmId);
modelBuilder.Entity<NormComparableFilm>()
.HasRequired(ncf=>ncf.ComparableFilm)
.WithMany()
.HasForeignKey(ncf=>ncf.ComparableFilmId);
If in the second relationship, the ComparableFilm navigation property is optional, you need to change the type of the corresponding FK as nullable:
public class NormComparableFilm
{
//...
public int? ComparableFilmId { get; set; }
}
And use this configuration:
modelBuilder.Entity<NormComparableFilm>()
.HasOptional(ncf=>ncf.ComparableFilm)
.WithMany()
.HasForeignKey(ncf=>ncf.ComparableFilmId);
About the second error, you are trying to call the Film table as NormComparableFilms that is the default name that EF will give by convention to the table represented by the NormComparableFilm entity.
if you need to rename one of your tables, you can use this configuration:
modelBuilder.Entity<Film>().ToTable("Films"));
My problem looks simple. I need to implement a relationships between items in the database. For example: relationship between entities like computer and software shows users that computer stores a specific software and similarly - a software is installed in the specific computer. I think I should implement an entity with source id and target id or something similar. I wrote some code using code first in EntityFramework 6. Here are two classes:
public class ConfigurationItem
{
public int Id { get; set; }
public String Name { get; set; }
public String DeploymentState { get; set; }
public String IncidentState { get; set; }
[DataType(DataType.MultilineText)]
public String Description { get; set; }
[DataType(DataType.MultilineText)]
public String Note { get; set; }
public virtual ICollection<Relationship> Relationship { get; set; }
}
public class Relationship
{
[Key]
public int RelationshipId { get; set; }
[ForeignKey("ConfigurationItem")]
public int SourceId { get; set; }
[ForeignKey("ConfigurationItem")]
public int TargetId { get; set; }
public String Type { get; set; }
public virtual ConfigurationItem Source { get; set; }
public virtual ConfigurationItem Target { get; set; }
}
This solution doesn't work. I need a tip or something what should I try to make it work properly. EF throws an error about foreign key:
The ForeignKeyAttribute on property 'SourceId' on type 'cms_1.Models.Relationship' is not valid. The navigation property 'ConfigurationItem' was not found on the dependent type 'cms_1.Models.Relationship'. The Name value should be a valid navigation property name.
When I try to resolve it EF throws an error about cascade deleting. I know how to disable it but I just don't want to. I need a proper solution with that feature but I think I don't know how to do a model representing given scenario.
Simply - I need to store two foreign keys from entity "A" in the entity "B". How is it possible?
from a quick review , I can tell that you need 3 tables :
first : Computer
second : Software
third : a table , lets call it ComputerSoftware which tell which software has in what computer ( or you can also see it - which computer use what software ), which has ComputerID column and SoftwareID column.
example (source)
class Country
{
public int Id { get; set; }
public virtual ICollection<CountryCurrency> CountryCurrencies { get; set; }
}
class Currency
{
public int Id { get; set; }
}
class CountryCurrency
{
[Key, Column(Order=0)]
public virtual int CountryId { get; set; }
[Key, Column(Order=1)]
public virtual int CurrencyId { get; set; }
public virtual Country Country { get; set; }
public virtual Currency Currency { get; set; }
}
Your issue could be that in the migration file creating those tables, it will have something like
.ForeignKey("dbo.Relationship", t => t.Id, cascadeDelete: true)
This will be set on both tables, ConfigurationItem and Relationship of their Primary Key fields. When you delete one, that config tells SQL Server to delete the relationships as well and the relationship probably has a cascadeDelete: true to the parent. This will cause the cyclical cascading delete issue you are experiencing.
After the migration has been generated, go in and change one or all to cascadeDelete: false and this will fix that issue. This is what EF generates by default if I recall.