EF many to many, when update it always insert new row in bridge table - asp.net

class Student{
public string Name {get; set;}
public EntityCollection<Info> Infos {get; set;}
}
class Info{
public string Title {get; set;}
public EntityCollection<Student> Students {get; set;}
}
//----------------
modelBuilder.Entity<Student>()
.HasMany( u => u.Infos)
.WithMany( c => c.Students)
.Map( wt =>
{
wt.MapLeftKey( "StudentId" );
wt.MapRightKey( "InfoId" );
wt.ToTable( "StudentInfo" );
} );
In db I have three tables (Student, Info and bridge table StudentInfo). It work as expect when insert. But when I tried to get Student entity and it Infos, I set new Student Name and save change db context. It always insert new record into bridge table (duplicate StudentId and InfoId). How to avoid that?

Related

Entity Frameworok generate guid as id and save it

I'm trying to save to my table Users let's say, string ID, string email, and string password. The problem is that ID must be a guid that I have to create it and save it and not SQL server. Any ideas how?
I searched but I only found how to make SQL server to create the guid.
First of all, tell Entity framework that you will generate the value of the primary key:
Use DatabaseGenerated Attribute
public class School
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name {get; set;}
...
}
The None option prevents values from being generated by the database automatically in cases where they would otherwise be created.
Furthermore, consider to overwrite DbContext.SaveChanges(). In this procedure ask the ChangeTracker for all elements that are Added. Generate an Id for every Added element. It might be dangerous to let others generate an Id, because they might be adding a constant value or just an auto-increment.
Another possibility would be to generate it within the Add function, but if you do that, then users could change your generated Id. So the proper place is within SaveChanges:
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
// This will fail: the added element doesn't have a property Id:
addedElement.Id = GenerateId();
}
return base.SaveChanges();
}
For this you have to be certain that every added element has a property Id. The simplest way is to create an interface and let all your tables implement this interface:
public interface IID
{
string Id {get; set;}
}
public class School : IID {...}
public class Student : IID {...}
public class Teacher : IID {...}
public class DbContext
{
public DbSet<School> Schools {get; set;}
public DbSet<Student> Students{get; set;}
public DbSet<Teacher> Teachers {get; set;}
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries.Cast<IID>
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
addedElement.Id = GenerateId(); // Every Added element implements IId
}
return base.SaveChanges();
}
private string GenerateId()
{
... // TODO: return unique ID, for instance a GUID
}
}

Entity Framework making incorrect PK-FK mapping on Code First migration

I have the following 3 classes set up to be created in a SQL Server database using Entity Framework Code First migrations. The Survey object is the main table.
public class Survey
{
public int SurveyId {get; set;} //Primary Key
public string Description {get; set;}
public bool HasDevice {get; set;}
public bool HasProcess {get; set;}
public virtual Process Process {get; set;}
public virtual ICollection<Device> Devices {get; set;}
}
Each Survey can have multiple Devices (1-to-many)
public class Device
{
public int DeviceId {get; set;} //Primary Key
public string DeviceType {get; set;}
public int SurveyId {get; set;} //Foreign Key
public virtual Survey Survey {get; set;}
}
Each Survey should have only one Process (1-to-0..1)
public class Process
{
public int ProcessId {get; set;} //Primary Key
public string ProcessInfo {get; set;}
public int SurveyId {get; set;} //Foreign Key
public virtual Survey Survey {get; set;}
}
The Fluent API mapping for these classes looks like this.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("Survey");
modelBuilder.Entity<Survey>().HasOptional(x => x.Process).WithRequired(x => x.Survey);
modelBuilder.Entity<Survey>().HasMany(x => x.Devices).WithRequired(x => x.Survey);
}
The problem is that when I apply the code first migration, the ForeignKey property in the Process table (1-to-0..1) keeps getting set to the ProcessId field rather than the SurveyId. This means that every time I try to add a new Process record, I get the following error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Survey.Processes_Survey.Surveys_ProcessId". The conflict occurred in database "Backflow", table "Survey.Surveys", column 'SurveyId'.
The 1-to-many mapping for Device works just fine.
I thought initially that this was due to having all my PK fields just say Id, but even after adding in the additional label part, it still makes the incorrect PK-FK link. I have also tried avoiding the Fluent API by adding the DataAnnotation [Key, ForeignKey("xyz")] instead but it has the same result. Recompiling the project, restarting Visual Studio, and even creating a new project and a new database do not help.
Is there something in the Fluent API or DataAnnotations that I am missing to get this to join correctly? Also, manually fixing the FK in the database does make it work, but that kind of defeats the purpose of doing everything in Code First with migrations.
The fluent mapping of the 1-0..1 relationship is correct:
modelBuilder.Entity<Survey>()
.HasOptional(s => s.Process)
.WithRequired(p => p.Survey);
But Process shouldn't have a SurveyID property (and column). In EF6, the dependent part of a 1-0..1 relationship (here: Process) is supposed to have a primary key that also refers to its principal (here: Survey) as foreign key. So Process.ProcessID is both primary key and foreign key. Thus, one Process is uniquely tied to one Survey.
By the way, in the other mapping, I would also mention the foreign key: if configuration is chosen over convention, it better be complete.
modelBuilder.Entity<Survey>()
.HasMany(s => s.Devices)
.WithRequired(d => d.Survey)
.HasForeignKey(d => d.SurveyId);

0..1 to N Referencing Relation -> Error Message I don't understand

The issue is a that I am getting an error message reading:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Users_dbo.People_Id". The conflict occurred in database "TrialDb", table "dbo.People", column 'Id'
I try to implment the following:
User may have a Person assigned and Person may have many User assigned
each entity, i.e. Person or any other, will have a creator and a changing user set.
The relationship shold one-way, meaning that User does not need a navigation property for Person.
What I am doing is trying to add a User to my database which results in the aforewritten error.
Here is the User and Person class definition (abreviated)
public class User
{
public int Id {get; set;}
public int? PersonId {get; set; }
public virtual Person Person {get; set;}
// two self-refencing properties
public int CreatorId {get; set; }
public virtual Person Creator{get; set;}
public int ChangingUserId {get; set; }
public virtual Person ChangingUser {get; set;}
public byte[] RowVersion {get; set;}
}
public class Person
{
public int Id {get; set;}
public virtual ICollection<User> Users {get; set;}
public byte[] RowVersion {get; set;}
}
My configuration classes for the two entities are as follows:
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap ()
{
HasRequired (p => p.Creator).WithRequiredPrincipal ().WillCascadeOnDelete (false);
HasRequired (p => p.ChangingUser).WithRequiredPrincipal ().WillCascadeOnDelete (false);
// 0..1 : N relation
HasOptional (p => p.Person).WithMany (p => p.Users).HasForeignKey (p => p.PersonId);
Property (p => p.RowVersion).IsRowVersion ();
}
}
public class PersonMap : EntityTypeConfiguration<Person>
{
public PersonMap ()
{
HasRequired (p => p.Creator).WithRequiredPrincipal ().WillCascadeOnDelete (false);
HasRequired (p => p.ChangingUser).WithRequiredPrincipal ().WillCascadeOnDelete (false);
// 0..1 : N relations - tried this as well - same result
//HasOptional (p => p.Company).WithMany (p => p.Employees).HasForeignKey (p => p.CompanyId);
//HasOptional (p => p.Facility).WithMany (p => p.People).HasForeignKey (p => p.FacilityId);
Property (p => p.RowVersion).IsRowVersion ();
}
}
Can anybody help me with this? It would be utmost apprciated!
The People_Id, which isn't in your model, indicates EF has inserted it because your fluent code is wrong. Your user table is pointing to Person 3 times, but your fluent code only tells EF how to map one FK (PersonId). You need to change the other 2 to indicate the ForeignKey assignment using either HasForeignKey or MapKey (sorry no test project readily available).
Try something like this for UserMap:
HasRequired(p => p.Creator).WithMany().HasForeignKey(p => p.CreatorId).WillCascadeOnDelete (false);
HasRequired(p => p.ChangingUser).WithMany().HasForeignKey(p => p.ChangingUserId).WillCascadeOnDelete (false);
I'm not sure what you are trying to do in your PersonMap since your fluent code references 2 navigation properties not in the model. Assuming you want 2 self references you would need similar code.

How to sort data in an asp.net gridview by properties of sub-objects?

I have a List<Role> (see below) that I am binding to an asp.net gridview. I want to sort this data using SortExpression, such that it is sorted by two properties of sub-objects of the rows. Specifically, I want to sort by the Application's Name, then the ApplicationType's ApplicationTypeName.
How can I do this?
The classes here are:
public class Application
{
public string Name {get; set;}
public int Status {get; set;}
}
public class ApplicationType
{
public string ApplicationTypeName {get; set;}
public int ApplicationTypeStatus {get; set;}
}
public class Role
{
public Application oApplication {get; set;}
public ApplicationType oApplicationType {get; set;}
}
Edit: note that I was responding to the earlier verison of the question, before it related to gridview; still, this might be useful...
Worst case: you can use the approach here to pre-sort the list before binding it to the gridview.
Various options:
implement IComparable[<T>]
implement IComparer[<T>]
use an ad-hoc sort
I'm guessing you just need the last, so perhaps:
list.Sort((x,y) => {
int delta = string.Compare(x.Application.Name, y.Application.Name);
if (delta == 0) delta = string.Compare(
x.ApplicationType.ApplicationTypeName, y.ApplicationType.ApplicationTypeName);
return delta;
});
Alternatively, you can perhaps do it via LINQ in the source data - note however that this is done when creating a new list - it isn't an in-place sort of an existing list:
var list = source.OrderBy(x => x.Application.Name)
.ThenBy(x => x.ApplicationType.ApplicationTypeName)
.ToList();

asp.net MVC3 application flow

I am working on an asp.net application. I need help designing the database related model and linq queries.
I have three tables ( and two lookup tables).
1)Product header (product header id as Primary key)
2) Product Detail(it has product header id as foreign key)
3) product attachment (it has product detail id as foreign key)
Now, I need to insert record in db.
a) for one Product header record there can be multiple Product detail records
b) for multiple product detail records, there can be multiple attachments.
I have created three entities for each tables. Product header also has two keys from user table and history table. but on view I need to show the user name instead of the key. Should I create a view model class which will hold all these entity classes as properties and how can I make sure that there first record is inserted in product header, then product details and then product attachment ?
Please suggest.
Thanks
an easy way to do this is to use the new Code First Feature that comes with EF 4.1
you can create your "entities" like so:
public class ProductHeader
{
public int ID {get; set;}
public virtual ICollection<ProductDetail> ProductDetails {get; set;}
//Other properties
}
public class ProductDetail
{
public int ID {get; set;}
public virtual ICollection<ProductAttachment> ProductAttachments {get; set;}
//Other properties
}
public class ProductAttachment
{
public int ID {get; set;}
// you can have a navigation property here for the user, this allows you to access his name
public virtual User User {get; set;}
//Other properties
}
public class MyContext:DbContext
{
public DbSet<ProductHeader> ProductHeaders {get; set;}
public DbSet<ProductDetail> ProductDetails {get; set;}
public DbSet<ProductAttachment> ProductAttachment {get; set;}
}
for the insertion order, just add your ProductHeader (after adding the ProductDetails and ProductAttachments to it) and EF will take care of it.
EDIT:
here's a sample code for adding a ProductHeader:
var context=new MyContext();
var ph=new ProductHeader();
ph.User=user;
var pd=new ProductDetail();
pd.ProductAttachments.Add(new ProductAttachment());
ph.ProductDetails.Add(pd);
context.ProductHeaders.Add(ph);
context.SaveChanges();
Hope this helps.

Resources