Need an ASP MVC database guru to properly initialize relationships - c#

I have a one-to-one relationship between a client and an address. By my understanding I have to tinker with the OnModelCreating method which I have. Right now I'm ready to give my application a go but I need to properly initialize my database; but I'm getting an error.
The main plan is to create a client first and then later on create an address to associate with them.
Here's my context class:
public class VolumeV2Context : DbContext
{
public DbSet<GiftCard> GiftCards { get; set; }
public DbSet<Clients> Clients { get; set; }
public DbSet<Address> Address { get; set; }
// use if you need to drop the database
static VolumeV2Context(){
// use if need to reset the models
// Database.SetInitializer(new DropCreateDatabaseIfModelChanges<VolumeV2Context>());
// use to reset whole database tables
Database.SetInitializer(new DropCreateDatabaseAlways<VolumeV2Context>());
}
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Clients>()
.HasOptional(j => j.Address)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
modelBuilder.Entity<Address>()
.HasRequired(j => j.client)
.WithRequiredDependent()
.WillCascadeOnDelete(true) ;
base.OnModelCreating(modelBuilder);
}
}
Models
public class Address
{
[Required]
public int Id { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Street Address")]
public string StreetAddress { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Postal Code")]
public string PostalCode { get; set; }
[DataType(DataType.Text)]
public string City {get; set; }
[DataType(DataType.Text)]
public string Province {get; set;}
public virtual Clients client { get; set; }
}
public class Clients
{
[Required]
public long Id { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[DataType(DataType.PhoneNumber)]
[Display(Name = "Phone ")]
public string PhoneNumber { get; set; }
public virtual Address Address {get; set;}
[Display(Name = "Email List")]
public Boolean EmailList { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "E-mail")]
public string Email { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Hair Type")]
public string HairType { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
}
In my first call to the database, which is in the main index method
return View(db.Clients.Take(25).ToList());
It returns an error saying:
Introducing FOREIGN KEY constraint
'FK_dbo.Addresses_dbo.Clients_client_Id' on table 'Addresses' may
cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or
ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could
not create constraint. See previous errors.

The problem is that you have bi-directional cascading deletes. In other words, deleting a client will delete an address which will delete a client which will delete an address... you get the picture.
Just looking at your data model, wouldn't it make more sense not to delete the client if his/her address were deleted (people move all the time :-)). Or is there a reason why deleting an address should also wipe out the client (after all, your data model does say address is optional....)
If you comment out the lines:
modelBuilder.Entity<Address>()
.HasRequired(j => j.client)
.WithRequiredDependent()
.WillCascadeOnDelete(true) ;
, or make the WillCascadeOnDelete false, does it work?
Another option is to turn the cascade delete convention off completely...
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
but I don't think that's what you're wanting to do here...

Related

.Net Entity Framework Introducing FOREIGN KEY constraint exception

Here is a piece of my DB, creaated according to code first principle:
I have an abstract class Client (a table of this class is created in my data base):
public abstract class Client
{
public int ClientId { get; set; }
[Required(ErrorMessage = "Name is required for Client")]
public string ClientName { get; set; } // name
[Required(ErrorMessage = "Phone number is required for Client")]
public string ClientPhoneNumber { get; set; } // phone number
public string Email { get; set; } // email
public string Comment { get; set; } // note
[Required(ErrorMessage = "Client should be enabled or disabled")]
public bool IsDisabled { get; set; }
}
Three classes are inherited from it:
1)
public class PrivatePerson : Client // дядя Вася
{
public string PrivatePersonSurname { get; set; }
}
2)
public class Firm : Client
{
[Required(ErrorMessage = "Ownership is required for Firm")]
public virtual Ownership Ownership { get; set; }
[Required(ErrorMessage = "Client address is required for Firm")]
public virtual ClientAddress FirmAddress { get; set; }
}
3)
public class AdvertisingAgency : Client
{
[Required(ErrorMessage = "Ownership is required for Advertising agency")]
public virtual Ownership Ownership { get; set; }
[Required(ErrorMessage = "Client address is required for Advertising agency")]
public virtual ClientAddress AdvertisingAgencyAddress { get; set; }
}
!! Firm and AdvertisingAgency have similar fields
And here is ClientAddress class:
public class ClientAddress
{
public int ClientAddressId { get; set; }
[Required(ErrorMessage = "Postal code is required for Client Address")]
public int PostalCode { get; set; }
[Required(ErrorMessage = "City is required for Client Address")]
public virtual City ClientCity { get; set; }
public int POBox { get; set; }
public virtual Street ClientStreet { get; set; }
public string StreetNumber { get; set; }
public int Appartment { get; set; }
public string ClientAddressComment { get; set; }
}
Context:
public virtual DbSet<Client> Clients { get; set; }
public virtual DbSet<ClientAddress> ClientAddresses { get; set; }
Well, when i'm trying to create a DB, I get this exception:
System.Data.SqlClient.SqlException
HResult=0x80131904
Message=Introducing FOREIGN KEY constraint 'FK_dbo.Clients_dbo.ClientAddresses_FirmAddress_ClientAddressId' on table 'Clients' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
Can anyone, please, point out my mistake?
Thanks
This is my context (partly) :
public class FivePlusDBContext : DbContext
{
public FivePlusDBContext() : base("name = FivePlus")
{
}
public virtual DbSet<City> Cities { get; set; }
public virtual DbSet<Street> Streets { get; set; }
public virtual DbSet<Client> Clients { get; set; }
public virtual DbSet<ClientAddress> ClientAddresses { get; set; }
public virtual DbSet<Ownership> Ownerships { get; set; }
}
To create a record, i do the following:
using (var ctx = new FivePlusDBContext())
{
City C_1 = new City() { CityName = "Name" };
ctx.Cities.Add(C_1);
ctx.SaveChanges();
}
First of all be aware that EntityFramework by default creates just one table per hierarchy (see here for details). In that case a special column (Discriminator) gets added to the Clients table to distinguish between persistent classes.
Now to your question.
According your model I assume that you want to use the same ClientAddress for multiple clients of type Firm and AdvertisingAgency. As both classes Firm and AdvertisingAgency have a property of type ClientAddress EntityFramework will generate two foreign keys on Clients table that point to ClientAddress (FK_dbo.Clients_dbo.ClientAddresses_AdvertisingAgencyAddress_ClientAddressId and FK_dbo.Clients_dbo.ClientAddresses_FirmAddress_ClientAddressId). For both foreign keys EntityFramework enables cascading delete by default. This leads to the exception that this may cause multiple cascade paths.
There are multiple possibilities that could solve the problem.
Create two different address types and refer to one of them in Firm and to the other one in AdvertisingAgency class (reusing addresses will not be possible anymore)
Change strategy to Table per Concrete class (TPC) (see here could maybe also solve the problem (not tested)
Disable cascading delete using Fluent API
To disable cascading delete add the following code to your Context class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Firm>()
.HasRequired(c => c.FirmAddress)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<AdvertisingAgency>()
.HasRequired(c => c.AdvertisingAgencyAddress)
.WithMany()
.WillCascadeOnDelete(false);
}
Mention: this could lead to orphaned addresses. When deleting a client its address will not get deleted. However the address could still be related to another client. Furthermore deletion of an address will not be possible as long as the address is related to at least one client. If trying to delete an address that still relates to multiple clients the following exception will be thrown.
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.

Duplicate entries in one to many relation

I have two models as seen below.
public class UserModel
{
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Display(Name = "User Name")]
[StringLength(15, MinimumLength = 8, ErrorMessage = "Length Should not be less than 8 characters")]
[Required]
public string UserName { get; set; }
[Display(Name = "QuestionCategory")]
public virtual ICollection<QuestionCategoryModel> QuestionCategoryModel { get; set; }
}
and
public class QuestionCategoryModel
{
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
[Display(Name = "Title")]
[StringLength(15, MinimumLength = 2, ErrorMessage = "Length Should not be less than 2 characters")]
public string Title { get; set; }
[Required]
public virtual UserModel User { get; set; }
[Required]
public bool IsDeleted { get; set; } = false;
}
The issue am running into is, when i try to insert data to QuestionCategory.
_DbInstance.QuestionCategories.Add(category);
_DbInstance.SaveChanges();
The issue is, each time am adding QuestionCategories, a new row gets added in User despite data already being there. Could someone guide me ?
update
Category variable is coming to a call to Controller, however, am attaching the User component fetching the value from session.
Category.User = (UserModel)Session["User"];
_QCategories.SaveCategory(Mapper.Map<Degree360.ORM.Dictionary.QuestionCategoryModel>(Category));
The problem here is that you don't attach the user to the context your context here is _DbInstance you can follow this link it will provide more information
Entity Framework Add and Attach and Entity States.
I guess you have a DbSet of UserModel that i will call here Users for my example.
so the solution will be something like that.
_DbInstance.Users.Attach(category.User)
PS : you have to do it before the SaveChanges()

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.

entity framework 6.1 - assign a forigen key cause to an insertion of new record

I seen similar posts but didn't find an answer. I have WPF 4.5 application with EF 6.1.
Here is my part of my data model:
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required]
[StringLength(32)]
public string FileDisplayName { get; set; }
[Required]
[StringLength(32)]
public string FileName { get; set; }
[Required]
[StringLength(8)]
public string FileExtention { get; set; }
[StringLength(1024)]
public string Description { get; set; }
[Required]
[StringLength(1024)]
public string FilePath { get; set; }
[Required]
public DateTime UploadDate { get; set; }
[Required]
public virtual FilesTypeLookup FileType { get; set; }
[Required]
[DefaultValue(0)]
public double Amount { get; set; }
[Required]
public virtual Expenses ExpenseId { get; set; }
public virtual ExpensesCategories Category { get; set; }
public virtual ExpensesPayees Payee { get; set; }
public class ExpensesCategories
{
[Key]
[Required]
public int Id { get; set; }
[Required]
[StringLength(64)]
[Index(IsUnique = true)]
public string Name { get; set; }
[StringLength(256)]
public string Description { get; set; }
}
As you can see, File entity has ExpensesCategory navigation property.
The problem happens when I'm assigning new value for this property.
I'm using the next code to editing existing File record:
var fileEntity = entityToEdit.Files.Single(p => p.Id == file.Id);
fileEntity.Amount = file.Amount;
fileEntity.Category = DB.ExpensesCategories.Single(p => p.Id == file.Category.Id);
//more work here
context.SaveChanges();
The SaveChanges() method is firing an exception:
System.Data.Entity.Infrastructure.DbUpdateException An error occurred while updating the entries. See the inner exception for details. Int32 SaveChanges()
UpdateException An error occurred while updating the entries. See the inner exception for details. Int32 Update()
SqlException Cannot insert duplicate key row in object 'dbo.ExpensesCategories' with unique index 'IX_Name'. The duplicate key value is (חומרי יצירה).
The statement has been terminated. Void OnError(System.Data.SqlClient.SqlException, Boolean, System.Action`1[System.Action])
It seems that instead of create a relationship between existing File record to existing ExpenseCategory, EF is trying to create a new ExpenseCategory record and link it with my existing File record. The unique constraint does not allows it and fires the exception.
I don't want that EF will create new ExpenseCategory records, just want to set relationships with existing records (ExpenseCategory is look-up table).
How can I do this?
Thanks
Ofir
Can you try this, add this to the ExpensesCategories class:
public virtual ICollection<Files> Files { get; set; }
Not sure if that will fix it but it's something that's missing anyway.
If there is one to one relationship between two objects
you should add the following properties to the ExpensesCategories
[Key, ForeignKey("File")]
public int FileId {get;set;}
public virtual File File{ get; set;}
In my opinion the relation ship between ExpensesCatrgories and Files is one to many (one category has may files but each file belongs to a specific category). In this case please add
public virtual ICollection<File> Files { get; set; }
to the ExpensesCategories

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