.NET C#: How to dislay foreign key property? - c#

I have this model called Station and ServiceLevel.
Station contains a foreign key called ServiceLevelId which refers to a primary key in the ServiceLevel table.
But I can't show the ServiceLevel properties which is a part of the Station.
I can present the station properties in the view by writing:
#Html.DisplayFor(model => model.Name)
But it won't show the foreign object properties if I write:
#Html.DisplayFor(model => model.ServiceLevel.Title)
I would really appreciate if somebody could explain me why it doesn't work.
Station.cs and ServiceLevel.cs:
public class Station
{
public int StationId { get; set; }
public int Number { get; set; }
public string Name { get; set; }
public int? ServiceLevelId { get; set; }
[ForeignKey("ServiceLevelId")]
public ServiceLevel ServiceLevel { get; set; }
}
public class ServiceLevel
{
public int ServiceLevelId { get; set; }
[Required]
public string Title { get; set; }
}
Merry christmas!

Your ServiceLevel property is not virtual, so EF lazy loading won't work. You have to explicity load the ServiceLevel when querying for Station object, by using Include() method. This is called Eager Loading.
context.Stations.Include(x=>x.ServiceLevel).ToList();

In order for this to work you can make ServiceLevel virtual by prefixing it with 'virtual' keyword. This will enable run time creation of a proxy to your POCO class so it will be available when you call for it;
public class Station
{
public int StationId { get; set; }
public int Number { get; set; }
public string Name { get; set; }
public int? ServiceLevelId { get; set; }
[ForeignKey("ServiceLevelId")]
public virtual ServiceLevel ServiceLevel { get; set; }
}
By default your related entity is not loaded if you don't prefix it by 'virtual' keyword so your code fails.
The other way, as previous answer suggested is to Eager load it, i.e. force the related entity to be loaded. In my humble opinion this is not optimal way to go except you have some specific reason to do so.

Related

Entity Framework One-to-Many AND a One-To-One relationship

I currently have a data model where a property can have multiple property images:
public class Property
{
public int ID { get; set; }
public string Name { get; set; }
public Guid PrimaryImageID { get; set; }
public ICollection<PropertyImage> Images { get; set; }
}
public class PropertyImage
{
[Key]
public Guid ID { get; set; }
public int PropertyID { get; set; }
public virtual Property Property { get; set; }
}
However, as you can see, i also want to enable a relationship so that a property can have ONE of those images assigned as a primary image.
I found an article here, that seems to use the Fluent API to configure it, but that's all fairly new to me, so i was wondering if it was possible to do this purely using Entity Framework?
What i REALLY want to achieve, is so that i can just call...
property.primaryimage.url
for example. If a user then wanted to change the primage image of a property, then i just change the PrimaryImageId field to the Guid of a different image
Many thanks
Personally, I wouldn't be messing around with EF to do this, the answer in the link you shared would pretty much agree with me. I would simply add another field to the PropertyImage class
public bool IsPrimaryImage {get;set;}
and just find the image based on the value set in that.
Sometimes the simplest solution is the best. You could end up with a convoluted solution in EF that does what you want but at the end of the day, would it really be better than just assigning true or false to a field?
First, you will add a "PrimaryImage" property to your Property class:
public class Property
{
public int ID { get; set; }
public string Name { get; set; }
public Guid PrimaryImageID { get; set; }
public virtual PropertyImage PrimaryImage { get; set; }
public ICollection<PropertyImage> Images { get; set; }
}
In your class where you inherit Entity's framwork DbContext, you can override the method OnModelCreating, which will lead you to:
protected override OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
Then, after the line base.OnModelCreating(modelBuilder), you can write:
modelBuilder.Entity<Property>().
.HasRequired(x => x.PrimaryImage)
.WithRequiredPrincipal();
modelBuilder.Entity<Property>().
.HasMany(x => x.Images)
.WithRequired(x => x.Property);
If this is what you want, then I believe this code allows you to have this property you need. Hope it helps!
If you need a Property to have a primary PropertyImage that can only be an image that is applicable to that Property, the emphasis needs to be switched:
You cannot set the primary Image for a Property until the images are entered and related to the Property to begin with.
You can't add the images unless the Property exists to relate to.
So, you would need to have the PrimaryImage property nullable until later set.
While a PropertyImage relies on a Property, a Property does not rely on a PropertyImage, and so should not be a foreign key in it's record.
This means that the flag (boolean value) for PrimaryImage needs to be stored with the PropertyImage indicating which one of the images is the primary one.
Remove the PrimaryImageId from Property and place a property on the PropertyImage (IsPrimaryImage) to allow selection of the primary one.
You can handle the unique selection either via the UI or more properly with a Unique Constraint on the table.
public class Property
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<PropertyImage> Images { get; set; }
}
public class PropertyImage
{
[Key]
public Guid ID { get; set; }
public int PropertyID { get; set; }
public bool IsPrimaryImage { get;set; }
public virtual Property Property { get; set; }
}
It isn't good practice to try to structure the data and its relationships around the way you'd like to call a method in code.
You can still call the method the way you want and encapsulate any logic you may need inside.
Think along the lines of if there was a cascade delete applicable here to remove items that no longer have a parent item to relate to:
If you delete a Property, all related PropertyImages would be removed too - correctly so because they relied on that record existing.
If you delete the primary PropertyImage, then the Property would have to be deleted because the record it relates to no longer exists...
So to have your method call the way you would like, do something similar to this:
private void UpdatePrimaryImage(PropertyImage oldImage, PropertyImage newImage)
{
// Pass in the original primary PropertyImage and the new one obtained from the UI.
// Check that we do not have the same image, otherwise no change needs to be made:
if(oldImage.IsPrimary != newImage.IsPrimary)
{
oldImage.IsPrimary = false;
newImage.IsPrimary = true;
Update(oldImage);
Update(newImage);
SaveChanges;
}
}
And to retrieve the current primary image:
Property.PropertyImages.Where(p => p.IsPrimaryImage).Url
Try this:
public class Property
{
public int ID { get; set; }
public string Name { get; set; }
public Guid PrimaryImageID { get; set; }
[ForeignKey("PrimaryImageID")]
public virtual PropertyImage PrimaryImage { get; set; }
public ICollection<PropertyImage> Images { get; set; }
}
public class PropertyImage
{
[Key]
public Guid ID { get; set; }
public int PropertyID { get; set; }
[ForeignKey("PropertyID")]
public virtual Property Property { get; set; }
}

Data annotations not create one-to-many object references

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".

double relationship between entities in EntityFramework 6

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.

How to make proper code-first relations

I'm fairly new to Entity Framework and feel more in control using the Code-First pattern rather than DB-First.
I was wondering what is more preferred when it comes to programmatically setting up ForeignKey relations between the entities.
Is it better to declare a FK_ property in the class which relates to the another class or is it better to declare an IEnumerable<> property in the class that gets related to?
public class IRelateToAnotherClass
{
...
public int FK_IGetRelatedToByAnotherClass_ID { get; set; }
}
or
public class IGetRelatedToByAnotherClass
{
...
public IEnumerable<IRelateToAnotherClass> RelatedTo { get; set; }
}
It all depends on what type of relationships you want between your entities (one-to-one, one-to-many, many-to-many); but, yes, you should declare foreign key properties. Check out this site for some examples.
Here's a one-to-many for your two classes:
public class IRelateToAnotherClass
{
public int Id { get; set; } // primary key
public virtual ICollection<IGetRelatedToByAnotherClass> IGetRelatedToByAnotherClasses { get; set; }
}
public class IGetRelatedToByAnotherClass
{
public int Id { get; set; } // primary key
public int IRelateToAnotherClassId { get; set; } // foreign key
public virtual IRelateToAnotherClass IRelateToAnotherClass { get; set; }
}
and with some Fluent API mapping:
modelBuilder.Entity<IGetRelatedToByAnotherClass>.HasRequired<IRelateToAnotherClass>(p => p.IRelateToAnotherClass).WithMany(p => p.IGetRelatedToByAnotherClasses).HasForeignKey(p => p.Id);
If I understand what you're asking correctly, you'd want both. You want an int FK property and an object property to use as the navigation property.
The end result would look something like this:
public class Employee
{
[Key]
public int EmployeeID { get; set; }
[ForeignKey("Store")]
public int StoreNumber { get; set; }
// Navigation Properties
public virtual Store Store { get; set; }
}
public class Store
{
[Key]
public int StoreNumber { get; set; }
// Navigation Properties
public virtual List<Employee> Employees { get; set; }
}
If you haven't already, take a look at navigation properties and lazy-loading. Note that EF is clever enough to figure out that an int StoreID property corresponds to an object Store property, but if they are named differently (such as without the ID suffix), you must use the [ForeignKey] annotation.

EF Code first - Lazy Loading How to set up and access the joining table

public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public bool IsInStock { get; set; }
public string ImageUrl { get; set; }
public List<ProductOption> ProductOptions { get; set; }
public virtual Category Category { get; set; }
}
public class ProductOption
{
[Key]
public int Id { get; set; }
public string ProductOptionName { get; set; }
public string ProductOptionDescription { get; set; }
}
Now I know when your using Code First EF, so that the tables are created correctly. You need to do something like this.
modelBuilder.Entity<Product>().HasMany(p => p.ProductOptions).WithMany().Map(m =>
{
m.MapLeftKey("ProductId").MapRightKey("ProductOptionId").ToTable("SelectedProductOptionsInOrderedItem");
});
So....
Does this mean that if I do something like Product.ProductOptions I will be able to access all associated productoptions.
Is this the best way to set it up, or is there another way?
To enable lazy load and EF can create derived proxy types for your collection, that property should be declared this way:
public virtual ICollection<ProductOptions> ProductOptions { get; set; }
That should be enought. Other aspect is the mapping approach that you use. You choose fluent api, i prefer mapping by convention, but that is a matter of personal taste anyway.
Ok, Mapping by Conventions:
Is the ability of EF that from the name of entities and their properties along with their types, to map our model with the underlying data without providing any other information.
for example
public class Customer {
public long CustomerID {get; September;}
public string CustomerName {get; September;}
public Employee AssignedTo {get; September;}
}
With the previous model EF will map database with a table named Customer with:
. CustomerID bigint primary key column
. CustomerName nvarchar column
. Customer_EmployeeID foreign key to Employee table, with the datatype Corresponding to EmployeeID in that table.
You can read more Here

Categories