I have a database first model, where is a Person entity, like this:
public partial class Person
{
public System.Guid personID { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public string sex { get; set; } // since in the DB it is char(1), default = 'F'
}
I defined a public enum for sex selection:
public enum Sex
{M, F}
which I want to use it to select the sex of the person and to render it as a radio button group.
I followed this solution: pass enum to html.radiobuttonfor MVC3 but couldn't make it work.
According to that answer, I added the RadioButtonForEnum extension, I extended my partial class with another property, like this:
public partial class Person
{
public System.Guid personID { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public string sex { get; set; } // since in the DB it is char(1), default = 'F'
public Sex personSex { get; set; }
}
and changed my viewmodel and controller to use the enum.
Now I got the error:
The associated metadata type for type 'MyApp.Models.Person' contains the following unknown properties or fields: personSex. Please make sure that the names of these members match the names of the properties on the main type.
How can I fix this, since the model is derived from the database?
Related
I have been using the .Net 5 and EF Core 5 for a small web app. Given EF Core 5 supports many - many out of the box there is no need for a joining table.
I've run into an issue when updating a object that already exists in the DB. For my app I have Athletes and Parents which have the many - many relationship.
public class Athlete
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string Street { get; set; }
public int Postcode { get; set; }
public string City { get; set; }
public StateEnum State { get; set; }
public DateTime DateofBirth { get; set; }
public DateTime DateSignedUp {get; set;}
public virtual ICollection<Parent> Parents { get; set; }
}
public class Parent
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string Street { get; set; }
public int Postcode { get; set; }
public string City { get; set; }
public StateEnum State { get; set; }
public DateTime DateofBirth { get; set; }
public DateTime DateSignedUp {get; set;}
public virtual ICollection<Athlete> Athletes { get; set; }
}
When I try to update the existing athlete that has a relation ship with two other parents I get an error:
Violation of PRIMARY KEY constraint 'PK_AthleteParent'. Cannot insert
duplicate key in object 'dbo.AthleteParent'. The duplicate key value
is (31, 1)
[HttpPost]
public async Task<ActionResult<Athlete>> PostAthlete(Athlete athlete)
{
_context.Athletes.Update(athlete);
await _context.SaveChangesAsync();
return Ok(athlete));
}
From what I can tell when entity tries to update my Athlete it tries to insert new rows into the joining table even though the parents already exist in there. Is there a way to get entity to remove any records when the relationship is updated? Or is there a way to tell entity to take update the joining table to match the Athlete object that is passed in?
Given a simple example like this:
public class Foo {
Guid Id { get; set; }
ICollection<Bar> Bars { get; set; }
}
public class Bar {
Guid Id { get; set; }
ICollection<Foo> Foos { get; set; }
}
You can call clear() on a tracked instance of Foo, and then re-add the Bar instances that you want assigned. I've found this is a nice way to avoid the constraint exception - much easier than manually trying to figure out what Bars have changed.
var foo = context.Foos.Include(x => x.Bars).FirstOrDefault(x => x.Id == someGuid);
foo.Bars.Clear();
foo.Bars.Add(bar1);
foo.Bars.Add(bar2);
...
context.Update(foo);
context.SaveChanges();
With the Model
public class Person {
public string Forename { get; set; }
public string Surname { get; set; }
public string DOB { get; set; }
}
I have a ViewModel which I'll be passing through to the View
public class PersonViewModel {
public IQueryable<Person> PersonVM { get; set; }
public string sometext{ get; set; }
}
If, for example, I wanted to calculate the age in the controller code and store it against each Person row in the IQueryable so it could be seen in the View, what's the best way of adding an Age property to each row?
I'm guessing that I don't have to include a fake property in the Person model like so
public string Age { get; set; }
You can use the NotMapped attribute which will exclude the property from database mapping.
public class Person
{
public string Forename { get; set; }
public string Surname { get; set; }
public string DOB { get; set; }
[NotMapped]
public string Age { get; set; }
}
You can make Age property set private and write your logic in get to calculate it at run time.
public class Person {
public string Forename { get; set; }
public string Surname { get; set; }
public string DOB { get; set; }
public string Age {
get{
//.... you logic
}
private set{}
}
I've just starting learning MVC6 with EF7. I have a stored proc that I'd like to return a portion of the fields that are in my model. If I don't return every field in my model, I'm getting "The required column 'FirstName' was not present in the results of a 'FromSql' operation".
Is there a way to get make some columns not required so I can return just a portion of the fields in my model?
model:
public class LoginViewModel
{
[Key]
public int UserID { get; set; }
[Required]
[Display(Name = "Username")]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Protected ID")]
public string ProtectedID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
My proc for testing:
CREATE PROCEDURE [dbo].[aaa_TopXXUsersTest]
#NumToReturn int = 10
AS
BEGIN
SET NOCOUNT ON;
select top(#NumToReturn) UserID, LastName, Username,Password, ProtectedID from Users where Deleted = 0
END
and last, my controller code:
public IActionResult Index()
{
var user = _context.Set<LoginViewModel>().FromSql("dbo.aaa_TopXXUsersTest #NumToReturn = {0}", 20);
return View(user);
}
If I include all the fields of my model in my stored proc the call work fine, but I can't seem to return just a subset. Is there a way to make some of the fields not required?
Used [NotMapped] attribute with first name.
The NotMappedattribute can be applied to properties of an entity class for which we do not want to create corresponding columns in the database.
public class LoginViewModel
{
[Key]
public int UserID { get; set; }
[Required]
[Display(Name = "Username")]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Protected ID")]
public string ProtectedID { get; set; }
[NotMapped]
public string FirstName { get; set; }
public string LastName { get; set; }
}
this means that the column 'FirstName' is not being returned in the result set.
Do a 'SELECT * FROM TABLE' to solve the issue.
It requires a Id column to be returned from the SP.
select top(#NumToReturn) 0 AS 'Id', UserID, LastName, Username,Password, ProtectedID
from Users
where Deleted = 0
Or Change UserID to Id from select and model
Try removing this public string FirstName { get; set; }. Your stored procedure is not returning the value for Firstname field, but you are trying to accept it in your loginViewModel class in this string Firstname variable.
To addition current answers:
According to last version, FromSql changed. Instead of that we can use eather FromSqlRaw or FromSqlInterpolated
As mentioned in docs: FromSqlInterpolated is similar to FromSqlRaw but allows you to use string interpolation syntax. Just like FromSqlRaw, FromSqlInterpolated can only be used on query roots.
We can achieve excluding property from mapping, using OnModelCreating method:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Ignore<Property>(); //property needed to be excluded
}
or: [NotMapped] attribute
I'm currently learning ASP.NET MVC and Web API.
I'm trying to create a User Model. Users can have any number of UserContacts. UserContacts reference the User it is a contact of and the User who is the contact. I have made a model called UserContact because attached to this Model is additional information.
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
[ForeignKey("UserID"), Column(Order = 0)]
[Required]
public User User { get; set; }
public int ContactID { get; set; }
[ForeignKey("ContactID"), Column(Order = 1)]
[Required]
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
So this gives me an error referring to cascading Delete. How do I set up a relationship like this where two foreign keys point to the same Model type? I have yet to grasp Entity Framework syntax as well. If I don't have an ICollection of UserContacts in the User model, does this hinder my ability to grab the UserContacts associated with that User?
When you have a foreign key and the foreign key columns are not nullable(means,required). EF will automatically tries to enable cascading delete on the relationsip. In your case, it will try to enable Cascading delete for both the foreign key columns and both of them points to the same user table! That is the reason you are getting this error. What if you have a UserContact record with Both UserId and ContactID points to the same User record. Cascading delete is confused now :)
Also, since one user can have more than one Contacts, We need a Contacts property on the User table to represent that. This will be a collection of UserContact's. Also this user can be a a contact of many other people. So Let's create another property for that.
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<UserContact> Contacts { set; get; }
public ICollection<UserContact> ContactOf { set; get; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
public User User { get; set; }
public int ContactID { get; set; }
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
And in your DbContext class, We can configure the foreign key relation ships and tell EF to disable cascade delete using fluent configuration inside the overridden OnModelCreating method. The below code will disable cascading delete on both the the relationships. But for your error to go away. disabling on one foreign key is enough.
public class YourDbContext: DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserContact>()
.HasRequired<User>(g=>g.User)
.WithMany(g=>g.Contacts)
.HasForeignKey(g=>g.UserID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserContact>()
.HasRequired<User>(g => g.Contact)
.WithMany(g => g.ContactOf)
.HasForeignKey(g => g.ContactID)
.WillCascadeOnDelete(false); // this one is not really needed to fix the error
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { set; get; }
public DbSet<UserContact> UserContacts { set; get; }
}
This will create the tables like you wanted with the necessary foreign keys.
There is not enough information for EF to figure out the relationships on the other side, so yes, you need collections. You can use the InverseProperty annotation to clarify (or fluent api statements):
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[InverseProperty("User")]
public Virtual ICollection<UserContact> Users{ get; set; }
[InverseProperty("Contact")]
public Virtual ICollection<UserContact> Contacts { get; set; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
[ForeignKey("UserID"), Column(Order = 0)]
[Required]
public User User { get; set; }
public int ContactID { get; set; }
[ForeignKey("ContactID"), Column(Order = 1)]
[Required]
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
http://www.entityframeworktutorial.net/code-first/inverseproperty-dataannotations-attribute-in-code-first.aspx
in MVC 3 i have a EF code first class like this:
public class Client
{
[Key]
[MaxLength(4)]
[Display(Name="labClientID", ResourceType=typeof(Resources.Translations.Client))]
public string ClientID { get; set; }
[MaxLength(50)]
[Display(Name="labName", ResourceType=typeof(Resources.Translations.Client))]
public string Name { get; set;}
}
Client is used eg in following Person class:
public class Person
{
[Key]
[MaxLength(50)]
public string UserID { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
[UIHint("ForeignKey")]
[MaxLength(4)]
public string ClientID { get; set; }
public virtual Client Client { get; set; }
}
Clients have IDs like 01,02,03 etc
In an edit view I have the following razor statement for the edit person view:
#Html.DropDownListFor(m => m.ClientID , new SelectList(ViewBag.Clients, "ClientID", "Name"))
The ViewBag.Clients is build up before with:
ViewBag.Clients = (from x in Context.Client select new {x.ClientID, x.Name});
Now comes the strange part: Indepented which value I choose for the ClientID in the person class, the DropDownList always displays the second entry from the client table when I open the edit view that contains the DropDownListFor statement.
I debugged the clients viewbag and it contains the correct values, also I used a editorfor instead of an DropDownListFor and the clientid in the person entity has the correct value.
Have you an idea where the problem is?