I'm using EF6 (Code First) in a project.
By having below class:
public class State
{
public int ID { get; set; }
[Required]
[StringLength(10)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
public Country Country { get; set; }
}
I expect to have Code as nvarchar(10) in database but I get nvarchar(3). I see the correct length for Name column but can't figure out why Code is not created correctly.
Edit:
I have the Country class as below:
public class Country
{
[Key]
[StringLength(3)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
}
I think, EF thinks the Code in State class is the Code in Country class as there is association between them.
Now, question is how should I tell EF that the Code in State class is not the Foreign Key to Country class?
Use MaxLength instead, EF will decide how large to make a string value field when it creates the database.
StringLength is data annotation, that will used to validate user input.
MSDN:
MaxLength - Specifies the maximum length of array or string data allowed in a property.
StringLength - Specifies the minimum and maximum length of characters that are allowed in a data field.
Since Question Updated:
Use [ForeignKey("CountryCode")] atribute, change your Code in Country class to CountryCode (or whatever you prefer) and specify your column name by Column["Code"] attribute:
public class State
{
public int ID { get; set; }
[Required]
[StringLength(10)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[ForeignKey("CountryCode")]
public Country Country { get; set; }
}
public class Country
{
[Key]
[StringLength(3)]
[Column["Code"]]
public string CountryCode { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
}
MSDN Links: Column Attribute, ForeignKey Attribute
Or just change your Codes to StateCode and CountryCode and use [ForeignKey("CountryCode")] attribute.
Even after working with it for a long time, EF is still surprising me. Until now I was thinking that by default EF is searching for property named {Navigation Property Name}{Referenced Entity PK Property Name} as default explicit FK property. But with your sample (verified), seems like it also does the same for property named {Referenced Entity PK Property Name}.
Since ForeignKey attribute cannot be used to specify the table column name (it can only specify FK/navigation property name), if you want to keep the model classes exactly as they are, you should use the MapKey fluent configuration, for instance:
modelBuilder.Entity<State>()
.HasRequired(s => s.Country)
.WithMany(s => s.States)
.Map(s => s.MapKey("CountryCode"));
Since you are referring to another class in the State model it will create a foreign key for you based on the name of the properties , so to avoid making the EF decide what the name of the column for you add the following in the state class :
public string CountryId { get; set; }
if you want to choose another name other than CountryId , suppose you want to change it to CountryForeignKey you can use the following:
using System.ComponentModel.DataAnnotations.Schema;
.
.
.
[ForeignKey("CountryForeignKey")]
public Country Country { get; set; }
public string CountryForeignKey { get; set; }
and this is what you get in the database
Related
I am adding to an entity a property called DateCreated, which will be set programmatically.
I want the update-database process to create a table column for it, as it does for all other properties. But I do not want it to create a date picker for it in the corresponding create view.
I believe the NotMapped attribute will exclude the property from the DB table completely
Example:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
//dont want this to show on Person create page, but should appear is DB table column
public DateTime DateCreated { get; set; }
}
Create a DTO for it and exclude the date property
public class PersonDTO
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Now in your C# code you can simply convert it back to the person and set the date automatically.
Person p = dtoperson.Adapt<Person>(); //mapster example, you can use automapper optionally
p.DateCreated = DateTime.Now;
You can also set a default value to "CURRENT_TIMESTAMP" in your dbconfig, now when you create a new record, you shouldn't have to set it manually.
You could try [ScaffoldColumn] attribute, to avoid automatic generation of field, but also [Bindable(false)], to make sure that model binding engine never binds property coming from the (malicious) client:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
public class Person
{
[HiddenInput]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ScaffoldColumn(false)]
[Bindable(false)]
public DateTime DateCreated { get; set; }
}
Another, often used attribute for scaffolding is [HiddenInput], usually used for auto-generated Ids.
If you are sending Person as JSON to the client, than you would also have to add one of this two attributes, depending which library does JSON serialization.
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
on DateCreated property.
With guidance from a similar question, this works for what I am after:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated { get; set; }
}
//on migration
migrationBuilder.AlterColumn<DateTime>(name: "DateCreated", table: "Person", defaultValueSql: "GETDATE()");
This is working. DateCreated is not shown as a view component. DateCreated is shown in DB table, and is automatically set upon insertion.
Let's say in our project we use C# and MsSQL and we have one Products table with two columns (ID,Name)
One day we decided to save product information given by Company1, so we created a new table ProductInfoFromCompany1 because it has custom columns (ProductID, Price, CurrentScore)
The next day, we agreed with Company2 and now we need to save their data as well. So, new table -> ProductInfoFromCompany2 with different columns (ProductID, Year, Rating)
Another day, we agreed with Company3 and so on...
So, we have no idea how the data given by new companies will look like. That's why we need to create a new table because if we use one Details table, it will be too wide with numerous null columns
In Entity Framework Core we have these models:
public class ProductInfoFromCompany1
{
public int Id { get; set; }
public int ProductId { get; set; }
public decimal Price { get; set; }
public double CurrentScore { get; set; }
public Product Product { get; set; }
}
public class ProductInfoFromCompany2
{
public int Id { get; set; }
public int ProductId { get; set; }
public int Year { get; set; }
public double Rating { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
//Do we need these navigation properties in this class?
//public ProductInfoFromCompany1 ProductInfoFromCompany1 { get; set; }
//public ProductInfoFromCompany2 ProductInfoFromCompany2 { get; set; }
}
You can see my question is commented in the Product class.
Do we need to add navigation properties in the Product class?
The reason why I'm asking is that in all books or documentation which I've read, people use navigation property, but in this case, it violates open-closed principle because whenever we add new company, we need to modify Product class as well.
P.S. if we want to query ProductInfoFromCompany1 data and we have product Id, we can simply start querying from ProductInfoFromCompany1, like this
var info = _db.ProductInfoesFromCompany1.Where(c=>c.ProductId == productId);
Do we need to add navigation properties in the Product class?
You are the only one who can answer the question if you need something or not.
If the question is does EF Core require navigation properties, the answer is no. Reference: Relationships - Single Navigation Property EF Core documentation topic:
Including just one navigation property (no inverse navigation, and no foreign key property) is enough to have a relationship defined by convention.
In fact EF Core fluent API and shadow properties allow defining relationship without any navigation or FK property. How useful it would be is another story. The main point (which is the question as I read it) is that none of them is mandatory.
Of course the lack of a navigation property imposes some limitations on the type of LINQ queries you can create - like you said, you can't start a query from Product and apply filter on associated ProductInfoFromCompany1, or eager/explicit/lazy load it.
But if you don't need all that, e.g. as you said, you can build your queries starting from ProductInfoFromCompany1, then omitting the navigation property in Product is perfectly fine.
As I mentioned in my comment a design change is required to achieve what you want.
Here is my suggestion:
Since your issue is with the structure of the product table because you don't know what each company wants to store as info for their product you can do it this way : (I ll explain later).
public class Company
{
[Key]
public int Id { get; set; }
[Display(Name = "Name")]
[Required]
public string Name { get; set; }
[Display(Name = "Description")]
public string Description { get; set; }
[Required]
[Display(Name = "Created date")]
[DataType(DataType.DateTime)]
public DateTime CreatedDate { get; set; }
public virtual ICollection<Product> Prodcuts { get; set; }
}
public class Product
{
[Key]
public int Id { get; set; }
[Display(Name="Name")]
[Required]
public string Name { get; set; }
[Required]
[Display(Name = "Created date")]
[DataType(DataType.DateTime)]
public DateTime CreatedDate { get; set; }
[Required]
[ForeignKey("Company")]
[Display(Name = "Company")]
public int CompanyID { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<ProductField> Fields { get; set; }
}
public class ProductField
{
[Key]
public int Id { get; set; }
[Display(Name = "Value")]
[Required]
public string Value { get; set; }
[Required]
[ForeignKey("Product")]
[Display(Name = "Product")]
public int ProductID { get; set; }
public virtual Product Product { get; set; }
[Required]
[ForeignKey("Field")]
[Display(Name = "Field")]
public int FieldID { get; set; }
public virtual Field Field { get; set; }
[Required]
[Display(Name = "Created date")]
[DataType(DataType.DateTime)]
public DateTime CreatedDate { get; set; }
}
public class Field
{
[Key]
public int ID { get; set; }
[MaxLength(100)]
[Index("ActiveAndUnique", 1, IsUnique = true)]
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "Description")]
public string Description { get; set; }
[Required]
[Display(Name = "Created date")]
[DataType(DataType.DateTime)]
public DateTime CreatedDate { get; set; }
}
Explanation of the code:
This approach gives you more control over your data without having to create a table for each product info.
Company:
I started by creating a company table with a navigation property that will lazy load all the products related to it.(if lazy loading is enabled)
Then In the product table I added a FK to reference the company.
Field:
Since you mentioned that you don't know what a company will have as product info , you can create a new field and link it to a product using the ProductField table .
ProductField:
This table will act as a "Many to Many" between your product, and field as a result you can add as many field to a new product without having to modify the structure of your product table or create a new one . You can also reuse the same field if company number 3 needs it.
USAGE:
Given we have a company named MyCompany.
MyCompany has a product named Car and the info required to be added to the car is Make, and Color.
We create two new fields called Make, and Color, then in the ProductField Table we add two new entries:
The first one will have:
The ID of the field "Make", The value "BMW", and a reference to the product with its id which is Car.
We do the same thing for color by referencing the the field "Color" and the product "Car".
Querying:
Now querying is simpler than having a table for each company product info.
Example:
var myProducts = _db.Products.Where(p=>p.CompanyID== "1").Include(p=>p.Fields).Tolist()
Again that's my take on it. Hope it helps.
I've run into a bit of a problem building up my database using Entity Framework Core - Code First, which I know can easily solved but I'm pressed for time.
I have a country entity as seen below:
public class Country
{
public int CountryId {get; set; }
public string Name { get; set; }
public IEnumerable<State> States { get; set; }
}
I also have a State entity like this:
public class State
{
public int StateId {get; set; }
public string Name { get; set; }
public int CountryId {get; set; }
public virtual Country Country {get; set; }
}
My problem is how to map to a Country and State in my Address entity.
public class Address
{
public int AddressId {get; set; }
public string HouseNumber { get; set; }
public string Street { get; set; }
public int StateId { get; set; }
public virtual State State { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
}
For some reason, this setup allows me to select a countryId from a select list in my view, and save an address. However, the state field does not save even though while debugging I see that the stateId is actually passed to the controller action and ModelState is valid.
After saving through my DbContext, I see that the stateId value is never saved.
Additional Detail
I didn't mention that I was using AutoMapper because I didn't think it was relevant to the issue but now I've realized that isn't true. My Address entity is actually a child of a Profile entity and I was trying to save the entire profile thinking AutoMapper could handle the FK in the the child entity but apparently it can't
Solved
After changing my approach several times with no positive results I decided to save the Address entity separately from the Profile entity and now the Address.StateId field updates as it should.
If anyone has answers on how to properly map child entities in AutoMapper I would really appreciate that info.
Thanks.
Based on the solution provided in this question : How to update foreign key in EF 6 - Code First, I'm able to update my foreign key using the id field.
But now, I get an exception when getting entities from the database. Using this code :
// Retrieve data first
using (var db = new TestDbContext())
{
var p2 = db.Persons.First();
}
I get the following SqlException : "Invalid column name 'Country_Id1'."
Does anyone have any clues to be able to retrieve data and to update the foreign key ?
Asked in another way, is it possible to use both the navigation property to ease the use of my entity and the id of the foreign key to be able to update my dependent entity ?
My entities
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
public int Country_Id { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
That might be because entity framework is trying to create new foreign key based on navigation property Country in Person entity.
I think you should annotate Country_Id property with ForeignKey attribute as below.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Country_Id")]
public virtual Country Country { get; set; }
public int Country_Id { get; set; }
}
However if you follow the ef naming convention for naming property as below, you don't need to annotate it.
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: '', '', or ''
You may read more from here
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
public int CountryId { get; set; }
}
Note: you might need to run database migration or need to recreate database.
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