Validation many to many relationship ef core - c#

I want to know if there's a way to add a validator where an author needs to be selected on a combobox otherwise it will display an error.
In my application I have three models, Books, Authors and the join table Rel_Book_Author.
public class Book
{
[Key]
[Display(Name = "Id")]
[Column("book_id")]
public int book_id { get; set; }
[Display(Name = "Title")]
[Column("liv_title")]
[Required(ErrorMessage = "Every book needs a title")]
public string liv_title { get; set; }
}
public class Author
{
[Key]
[Display(Name = "Id")]
[Column("aut_id")]
public int aut_id { get; set; }
[Display(Name = "Author's Name")]
[Column("aut_name")]
public string aut_name { get; set; }
public ICollection<Rel_Book_Author> BookAuthors { get; set; }
}
public class Rel_Book_Author
{
[Column("hla_aut_id")]
public int aut_id { get; set; }
public Author Author { get; set; }
[Column("hla_book_id")]
public int book_id { get; set; }
public Book Book { get; set; }
}

I'm making the assumption here that your view's model will be the Rel_Book_Author class, or a list of those classes, and so you the view shows a book (or books) and allows the user to pick the author from a list for each book?
If so, then validation should work as per any other model with data annotations.
EF Core does not perform any validations itself, it expects you to do have already validated the object using the client-side validation and on the server (typically in the controller), so the fact that the objects are related by a particular type of relationship (e.g. many-to-many) doesn't matter here.
There is one gotcha to watch out for with the Required attribute for an integer; which is that a non-nullable integer (obviously) cannot be null, it will just default to zero, which means that setting Required won't actually ever return a validation error for an integer in a select list (as the property will always have a value of zero or the value of whatever is selected in the list).
To get round that, declare the aut_id property as nullable (int?):
[Required]
public int? aut_id { get; set; }
or add a Range attribute, e.g.
[Range(1, int.MaxValue)]
public int? aut_id { get; set; }

Related

Necessity of navigation properties in EF Core models

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.

How to configure Entity Framework Code First collections with strings as foreign keys to parent IDs to cascade delete?

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);

Entity Framework Code First adding record error

Ok let me start with my model:
Contact Method Types:
public class ContactMethodType
{
[Key]
[HiddenInput(DisplayValue = false)]
public Guid ContactMethodTypeGUID { get; set; }
[Required(ErrorMessage = "Please enter a Contact Method Type Name.")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a brief description.")]
public string Description { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<ContactMethod> ContactMethods { get; set; }
Contact Methods:
public class ContactMethod
{
[Key]
[HiddenInput(DisplayValue = false)]
public Guid ContactMethodGUID { get; set; }
public virtual ContactMethodType Type { get; set; }
public string CountryCode { get; set; }
[Required]
public string Identifier { get; set; }
public bool IsPreferred { get; set; }
}
Recipient:
public class Recipient
{
[Key]
public Guid RecipientGUID { get; set; }
[Required(ErrorMessage = "Please enter a Recipient's First Name.")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Please enter a Recipient's Last Name.")]
public string LastName { get; set; }
public string Company { get; set; }
public UserGroup Owner { get; set; }
public List<ContactMethod> ContactMethods { get; set; }
public User CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public User LastModifiedBy { get; set; }
public DateTime LastModifiedOn { get; set; }
public bool IsActive { get; set; }
}
I have two Contact Method Types already defined:
Email and SMS
Now I am creating a new Recipient, so I add all of the required data to my Recipient Object, and then I call:
context.Recipients.Add(myRecipient);
context.SaveChanges();
What I get is an error that I am tying to add a new ContactMethodType when one already exists. But this is supposed to be a one to many relationship, and I do not want to add a new ContactMethodType, just categorize a new Contact Method(s) for my recipient.
I am not sure when this is happening. Maybe my model is incorrect? Based on what is chosen as the type, I pull that Type object, and set it to the ContactMethod.Type variable. But like I said, instead of just linking it to an existing ContactMethodType, it is trying to re-create it, and since the GUID already exists, I get the error that the record cannot be created because the key (GUID) already exits.
Any ideas?
After discussing this offline with Marek, it boiled down to DbSet<TEntity>.Add(entity) assuming that all entities in the graph being added are new.
From The API docs for Add...
Begins tracking the given entity, and any other reachable entities that are not already being tracked, in the Added state such that they will be inserted into the database when SaveChanges() is called.
Because this model uses client generated keys, meaning that all entities have a key value assigned before they are given to the context, you can't use any of the "smarter" methods (such as DbSet<TEntity>.Attach(entity)) that would inspect key values to work out if each entity is new or existing.
After adding the new recipient, you can use call DbSet<TEntity>.Attach(entity) on each existing entity (i.e. the contact method type). Alternatively, DbContext.Entry(entity).State = EntityState.Unchanged will also let EF know that an entity is already in the database.
You could also look at DbContext.ChangeTracker.TrackGraph(...), see the API docs for more info.

How to handle CheckBoxFor for string value in Entity Framework?

Hello I am trying to handle checkbox for a form where it can be checked or unchecked for the status of task completion. In my database I have taken data field of type varchar which is to be set Pending/Done whenever user check/uncheck the checkbox. Checkbox examples which I found is not fulfilling my need because they are set to separate class of collection of Elements but I don't want that.
MyTblModel.cs
[Table("tblToDo")]
public class ToDoModel
{
[Key]
public int SrNo { get; set; }
public string Title { get; set; }
public string Status { get; set; }
[Column(TypeName = "DateTime2")]
public virtual DateTime CreatedDate { get; set; }
[Column(TypeName = "DateTime2")]
public virtual DateTime? CompletionDate { get; set; }
}
MyContext.cs
public DbSet<ToDoModel> ToDoList { get; set; }
I am accessing this in controller.
HomeController.cs
Session["ToDoList"] = myContext.ToDoList.OrderByDescending(x => x.CreatedDate).ToList();
Now I just fail to understand how to access above given status in CheckBoxFor because it expects me to pass bool value which I don't get. I also want to know, the time I save my data how to pass checkbox status to table as Done/Pending.
Thank you.

System.Data.SqlClient.SqlException: Invalid column name 'phone_types_phone_type_id'

I'm trying to get information from some of my models that have a foreign key relationships to my main employee model. If I map out each model individually, I can access them like normal with no problems, but I have to visit multiple different web pages to do so.
I'm trying to merge several of my models into essentially a single controller, and work with them this way. Unfortunately, when I try to access these models I get a strange error:
System.Data.SqlClient.SqlException: Invalid column name 'phone_types_phone_type_id'.
After searching through my code, apparently the only location phone_types_phone_type_id appears is in my migration code. I'm incredibly new at C# and Asp.Net in general so any help is appreciated.
Here is the code for my model:
[Table("employee.employees")]
public partial class employees1
{
public employees1()
{
employee_email_manager = new List<email_manager>();
employee_employment_history = new HashSet<employment_history>();
employee_job_manager = new HashSet<job_manager>();
employee_phone_manager = new HashSet<phone_manager>();
this.salaries = new HashSet<salary>();
}
[Key]
public int employee_id { get; set; }
[Display(Name="Employee ID")]
public int? assigned_id { get; set; }
[Display(Name="Web User ID")]
public int? all_id { get; set; }
[Required]
[StringLength(50)]
[Display(Name="First Name")]
public string first_name { get; set; }
[StringLength(50)]
[Display(Name="Last Name")]
public string last_name { get; set; }
[Column(TypeName = "date")]
[Display(Name="Birthday")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime birth_day { get; set; }
[Required]
[StringLength(1)]
[Display(Name="Gender")]
public string gender { get; set; }
[Required]
[StringLength(128)]
[Display(Name="Social")]
public string social { get; set; }
[Required]
[StringLength(128)]
[Display(Name="Address")]
public string address_line_1 { get; set; }
[StringLength(50)]
[Display(Name="Suite/Apt#")]
public string address_line_2 { get; set; }
[Required]
[StringLength(40)]
[Display(Name="City")]
public string city { get; set; }
[Required]
[StringLength(20)]
[Display(Name="State")]
public string state { get; set; }
[Required]
[StringLength(11)]
[Display(Name="Zip")]
public string zip { get; set; }
[Column(TypeName = "date")]
[Display(Name="Hire Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime hire_date { get; set; }
[Column(TypeName = "date")]
[Display(Name="Separation Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime? termination_date { get; set; }
[StringLength(70)]
[Display(Name="Emergency Contact Name")]
public string emergency_contact_name { get; set; }
[StringLength(15)]
[Display(Name = "Emergency Contact Number")]
public string emergency_contact_phone { get; set; }
[Display(Name = "Notes")]
public string notes { get; set; }
public virtual ICollection<phone_manager> employee_phone_manager { get; set; }
[Table("employee.phone_manager")]
public partial class phone_manager
{
[Key]
public int phone_id { get; set; }
public int employee_id { get; set; }
[Required]
[StringLength(15)]
public string phone_number { get; set; }
[StringLength(5)]
public string phone_extension { get; set; }
public int phone_type { get; set; }
[Column(TypeName = "date")]
public DateTime date_added { get; set; }
public bool deleted { get; set; }
public virtual employees1 employees1 { get; set; }
public virtual phone_types phone_types { get; set; }
}
[Table("employee.phone_types")]
public partial class phone_types
{
public phone_types()
{
phone_manager = new HashSet<phone_manager>();
}
[Key]
public int phone_type_id { get; set; }
[Required]
[StringLength(50)]
public string phone_type_name { get; set; }
public virtual ICollection<phone_manager> phone_manager { get; set; }
}
}
And the pertinent code from my view:
#foreach (var item in Model.employee_phone_manager)
{
#Html.DisplayFor(modelItem => item.phone_number);
#: -
#Html.DisplayFor(modelItem => item.phone_type);
<br />
}
EDIT I may have found out the issue, but I'll definitely take more input if there is another option. My solution was to take and add the following: [ForeignKey("phone_type")] directly above this line: public virtual phone_types phone_types { get; set; } in my phone_manager class.
Your issue is that your connection string in data layer and connection string in web layer are pointing to different databases.
e.g.
data layer reading dev database
webapp pointing to test database.
Either update connection strings to point to the same database.
or
Make sure your both database have same tables and columns.
After doing quite a bit more research, it seems like I had a fairly unique issue. I attempted several of the fixes listed both on here and many other sites, but almost nothing seemed to fix the issue.
However, the solution I listed at the bottom of my original post seems to be working, and holding up well, so I believe it to be a fairly adequate solution to my problem.
To somewhat outline what was occurring, MVC EF was attempting to find a fk/pk relationship across two models, but since the column names across the models were different, it wasn't able to map them properly. If I were to trying to get all the emails from email_manager by using the email_types table, it wasn't an issue, but moving backwards, and grabbing the information from email_types from email_manager threw errors.
Since the column names between the two tables are different, EF tried to create a column to house the relationship, but since no such column existed, an error was thrown. To correct this, all that's necessary is to tell EF what the foreign key column actually is, and that is done by using [ForeignKey("email_type")] above the collection that houses the parent model.
So for example, my new email_types and email_manager models were as follows:
[Table("employee.email_manager")]
public partial class email_manager
{
[Key]
public int email_id { get; set; }
public int employee_id { get; set; }
[Required]
[StringLength(255)]
public string email { get; set; }
public int email_type { get; set; }
[Column(TypeName = "date")]
public DateTime date_added { get; set; }
public bool deleted { get; set; }
[ForeignKey("email_type")]
public virtual email_types email_types { get; set; }
public virtual employees1 employees1 { get; set; }
}
[Table("employee.email_types")]
public partial class email_types
{
public email_types()
{
email_manager = new HashSet<email_manager>();
}
[Key]
public int email_type_id { get; set; }
[Required]
[StringLength(50)]
public string email_type_name { get; set; }
public virtual ICollection<email_manager> email_manager { get; set; }
}
I had the similar issue. What happens is that in the database foreign keys are created and it starts mapping both the models and then throws an exception. Best way is to avoid foreign key creation by using [NotMapped] as you could use complex models and also avoid creation of Foreign Key.
You have specify the Database Table using [Table("employee.employees")]. Check your database Table is there have a column that name is phone_types_phone_type_id .It Try to find data of that column but It did not find column then throw this Message. My Problem has solve Check my database database Table.
I'm using nop commerce and to get around my problem I had to use ignore in my database map
Ignore(p => p.CategoryAttachmentType);
In the domain I had
/// <summary>
/// Gets or sets the category attachment type
/// </summary>
public CategoryAttachmentType CategoryAttachmentType
{
get
{
return (CategoryAttachmentType)this.CategoryAttachmentTypeId;
}
set
{
this.CategoryAttachmentTypeId = (int)value;
}
}
I came across the same kind of exception. My solution is to go to the model class and verify the exception given property definition/type where it defines. In here better check the Model class/classes where you define 'phone_types_phone_type_id'.
You are right.
I had similar issue.
Something like this
[ForeignKey("StatesTbl")]
public int? State { get; set; }
public StatesTbl StateTbl { get; set; }
So as you can see, I had kept name 'StateTbl' in the last line instead of 'StatesTbl'
and app kept looking for StateTblID. Then I had to change name to 'StatesTbl' instead. And then it started working well.
So now, my changed lines were:
[ForeignKey("StatesTbl")] <== 'StatesTbl' is my original States table
public int? State { get; set; }
public StatesTbl StatesTbl { get; set; }
These are in the AppDbContext.cs class file
I had an issue where I was getting the same error and I resolved it by deleting the audit trail I had created and creating a new one. I had forgotten to do this when I deleted some columns from the table earlier on.
My problem is that I forgot that I've created several SQL Views in my database.
I've used those views in my ASP.NET C# MVC app.
So when I received error I naturally checked all databases tables but forgot about views in which I didn't add new fields.

Categories