EF code-first optional 1-to-1 relationship - entity-framework-4

I have an EF data model that represents a report with a hierarchical tree of report sections. Each ReportSection entity contains a collection of zero or more child ReportSections. Each Report entity contains a single ReportSection entity that serves as the root of tree of ReportSections.
My data model that has the following navigation properties:
public class Report
{
// Primary key
public int Id { get; set; }
// A Report has one root ReportSection
[ForeignKey("RootReportSection")]
public int ReportSectionId { get; set; }
public virtual ReportSection RootReportSection { get; set; }
}
public class ReportSection
{
// Primary key
public int Id { get; set; }
// Optional self-reference to the parent ReportSection
[ForeignKey("ParentReportSection")]
public int? ParentReportSectionId { get; set; }
public virtual ReportSection ParentReportSection { get; set; }
// Optional foreign key to the parent Report
[ForeignKey("ParentReport")]
public int? ReportId { get; set; }
public virtual Report ParentReport { get; set; }
// Child ReportSections contained in this ReportSection
public virtual ICollection<ReportSection> ReportSections { get; set; }
}
Everything works fine if I omit the ReportSectionId and RootReportSection navigation claptrap from the Report entity. But, as coded above, attempting to add a migration gets an error:
Because the Dependent Role properties are not the key properties,
the upper bound of the multiplicity of the Dependent Role must be '*'.
After a bit of digging, I now understand that EF apparently wants me to use the primary key of my ReportSections entity as the foreign key to my Report entity. But, in my scenario, only the top-level ReportSection in a hierarchical tree of ReportSection entities participates in a relationship with a Report entity. The rest of the ReportSection entities are related to each other, and their primary keys are independent of any Report primary keys.
Is there a way to get this to work? Specifically, is there a way for a Report entity to "contain" a top-level ReportSection entity, which ReportSection entity has its own collection of self-referenced ReportSection entities?

Apparently ReportSection is the principal in the relationship und Report the dependent (because Report must refer to an existing RootReportSection since Report.ReportSectionId is not nullable). In this case it is well possible that a ReportSection exists without a related Report. All your child ReportSection would have no Report.
But this can only work if the key in Report is not autogenerated because the key would have to be the same as the key of the related (and already existing) RootReportSection. So, you could try to model it like this:
public class Report
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[ForeignKey("RootReportSection")]
public int Id { get; set; }
public virtual ReportSection RootReportSection { get; set; }
}
public class ReportSection
{
public int Id { get; set; }
// Optional self-reference to the parent ReportSection
[ForeignKey("ParentReportSection")]
public int? ParentReportSectionId { get; set; }
public virtual ReportSection ParentReportSection { get; set; }
// Optional foreign key to the parent Report
public virtual Report ParentReport { get; set; }
// Child ReportSections contained in this ReportSection
public virtual ICollection<ReportSection> ReportSections { get; set; }
}
(It's possible that [DatabaseGenerated(DatabaseGeneratedOption.None)] is redundant because EF understands that the key cannot be database generated in this one-to-one relationship. I'm not 100% sure though.)
Downside of such a one-to-one relationship with shared primary key is that you could not change the relationship between Report and RootReportSection, i.e. you cannot have the Report refer to any other section than the one that has the same primary key.
If that doesn't work for you, you have to model the relationship as one-to-many because EF does not support one-to-one relationships with separate foreign key. Either remove that part altogether...
[ForeignKey("ParentReport")]
public int? ReportId { get; set; }
public virtual Report ParentReport { get; set; }
...if you don't need to navigate from the section to the report or replace it by a collection:
public virtual ICollection<Report> ParentReports { get; set; }
You would have to ensure in business logic that never more than one report is added to this collection to have kind of a simulation of a one-to-one relationship. In the database you could add a unique constraint on Report.ReportSectionId to have data integrity on database level. But EF won't understand this constraint and still allow to add more than one item to the collection. However, if you try to save it you'd get a unique key violation exception from the database.

Related

FOREIGN KEY constraint may cause cycles or multiple cascade paths [error message]

I add provincesModel's pr_id as a foreign Key to clinicsModel. So the visual studio display me this error message
Introducing FOREIGN KEY constraint 'FK_dbo.ClinicsModels_dbo.ProvincesModels_pr_id' on table 'ClinicsModels' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I try to solve the issue by browsing on web to find some solution but I can't get how to solve this. So I remove the foreign key relation of pr_id from clinicsModel and run the project but still visual studio shows me the same error message. I also recreate the ClinicsModel and try to update database using Update-Database -Verebose Migrations using package manager console but still it shows the same error message.
Here's my code:
for ProvinceModel
namespace finalFyp.Models
{
public class ProvincesModel
{
[Key]
public int pr_id { get; set; }
public string pr_name { get; set; }
public ICollection<CitiesModel> cities { get; set; }
}
}
ClinicsModel:
public class ClinicsModel
{
[Key]
public int clinic_id { get; set; }
public string clinic_name { get; set; }
public string clinic_address { get; set; }
//Forigen Keys
public int ct_id { get; set; }
public CitiesModel city { get; set; }
}
}
As the error occurred when I redirecting to http://localhost:3110/Doctors/index
Here's the snapshot of of error message.
DoctorsModel:
public class DoctorsModel
{
[Key]
public int d_id { get; set; }
public string d_name { get; set; }
public string contact { get; set; }
public string cnic { get; set; }
public string email { get; set; }
public string gender { get; set; }
//Forigen Key
public ICollection<DocExperiencesModel> experiences { get; set; }
public ICollection<DocSpecialization> specializations { get; set; }
public ICollection<QualificationsModel> qualifications { get; set; }
public ICollection<DoctorProfileModel> profiles { get; set; }
}
For ease of understanding Here's the schema of my database.
Please guide me what I suppose to do? I will be very thankful to him/her.
From first glance, the error occured when pr_id added as foreign key to ClinicsModel because ProvincesModel has one-to-many relationship against ClinicsModel table which involves pr_id primary key field. Since all foreign keys which referencing pr_id are not nullable, all one-to-many relationships where ProvincesModel involved are having cascade delete enabled by default. Hence, it means when a ClinicsModel entity data is deleted, it will have 2 cascade delete paths: through CitiesModel and directly to ProvincesModel as shown in image below.
As a result, it establishing circular relationship with more than one cascade delete path from ClinicsModel to ProvincesModel which causes error 1785 in SQL Server.
To resolve relationships problem, pr_id foreign keys should declared as Nullable<int>:
public int? pr_id { get; set; }
Likewise, if ct_id (and other int foreign key properties which subjected to possible circular relationships) also returning same error, declare them with same way as above:
public int? ct_id { get; set; }
NB: If Fluent API (and Code First) is being used, try adding these lines below:
// taken from /a/20915232
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
// modified from /a/17127512
// used on all entities with circular relationships
var builder = new DbModelBuilder();
builder.Entity<CitiesModel>()
.HasRequired(x => x.ct_id)
.WithMany()
.WillCascadeOnDelete(false);
builder.Entity<ProvincesModel>()
.HasRequired(x => x.pr_id)
.WithMany()
.WillCascadeOnDelete(false);
Similar issues:
Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths - why?
Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths
Error message 1785 occurs when you create a FOREIGN KEY constraint that may cause multiple cascade paths

Entity Framework 6 - How to mark a property as NOT foreign key

I'm using ASP.NET MVC5 together with EF6 and using the code first approach.
I have a property in a model that i need to to tell EF6 is NOT a foreign key:
public class LogEntry
{
public int ID { get; set; }
public int LogDayID { get; set; }
public int LogEntryTypeID { get; set; }
public int DepartmentID { get; set; }
public DateTime Clock { get; set; }
public string Text { get; set; }
public virtual LogDay LogDay { get; set; }
public virtual LogEntryType LogEntryType { get; set; }
public virtual Department Department { get; set; }
}
[NotMapped]
public class Department
{
public int ID { get; set; }
public string Title { get; set; }
}
The model Department has the [NotMapped] as this model should not be stored in the database.
I thought this was enough to make EF6 realise that DepartmentID in LogEntry shouldn't be a foreign key.. But instead it throws me an error that 'Department' is not mapped.
EDIT: Even if i remove the DepartmentID from LogEntry it still complains with the above error.
Here's the complete error message:
"The type 'SupervisorLogWeb.Models.Department' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive or generic, and does not inherit from EntityObject."
Apparently your ComplexType is discovered as a Entity - this happens, if you decided to refactor an former Entity to a ComplexType.
The ModelBuilder will decide if an type is an Entity or not (more or less) by it's presence or absence in the DbContext.
So check if your class is still defined as DbSet inside the Context and adjust accordingly.
Add the NotMapped attribute to the DeparmentID property as well. This attribute can also be applied on properties.
When all your mappings are based on conventions, EF (or any tool) can't really tell whether you broke the convention intentionally or you made a mistake. It can apply some heuristics but it's better to fail and ask the programmer than implement an unwanted mapping.

ASP.NET MVC 4 Multiple Foreign Keys Referencing Single Parent Entity

I am trying to develop an ASP.NET MVC 4 application where players can be rated according to their Offence, Defence and Assist skills. Offence, Defence and Assist are foreign keys on Player table referencing the same lookup table - Rating.
I have the following parent entity:
public class Rating
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
And child entity:
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
public int OffenceRatingId { get; set; }
[ForeignKey("OffenceRatingId")]
public virtual Rating OffenceRating { get; set; }
public int DefenceRatingId { get; set; }
[ForeignKey("DefenceRatingId")]
public virtual Rating DefenceRating { get; set; }
public int AssistRatingId { get; set; }
[ForeignKey("AssistRatingId")]
public virtual Rating AssistRating { get; set; }
}
Building and scaffolding went fine but when I run the app, I get the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.Players_dbo.Ratings_DefenceRatingId' on table 'Players' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I am new to MVC and have no idea what I am missing here. Any help with this will be greatly appreciated. Thanks.
By default, Entity Framework has a Cascade on Delete convention. When two entities have foreign keys to each other, it causes a circular reference and Cascade on Delete can't be applied to both entities.
The simplest solution is to remove the cascade on delete convention, and apply it on a case by case basis.

Establish Foreign Key Connection Using Entity Framework With SQL Queries

I have a couple of classes (for this example anyway) that use code first with the entity framework to connect to the database.
public class Customer
{
[Key]
public long CustomerId { get; set; }
public string CompanyName { get; set; }
...
public virtual List<Contact> Contacts { get; set; }
}
public class Contact
{
[Key]
public long ContactId { get; set; }
public string Forename { get; set; }
...
public long CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
When I hook these up in my context class directly to the db the foreign key relationships hook up fine and I can access the collection of contacts from within the customer class.
class RemoteServerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
...
}
My problem is that these database tables are used by various different systems and are massive. In order to increase efficiency I have overridden the default behaviour to point at a view (and also a stored proc elsewhere) rather than directly at the table.
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
public IEnumerable<Contact> Contacts()
{
return Database.SqlQuery<Contact>("SELECT * FROM vw_ContactsList");
}
I have made sure that in each of the views I have included the foreign key fields: CustomerId and ContactId.
When I do this however the class joins appear to be lost - there's always a null when I drill into either of the objects where it should be pointing to the other one. I have tried to set up what the foreign key field should point to but this doesn't seem to help either.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(p => p.Customer)
.WithMany()
.HasForeignKey(k => k.CustomerId);
}
Is there a way to establish the connection when overriding the default behaviour?
There is no overriding in this case. If you removed
public DbSet<Customer> Customers { get; set; }
and replaced it with
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
you have completely changed the behavior. The first uses entities and full power of EF. The second is only helper to execute custom SQL. Second without first or without defining entity in OnModelCreating doesn't use Customer as mapped entity at all - it uses it as any normal class (only mapped entities can use features like lazy loading).
Because your Customer is now mapped to view you cannot use your former Customer class used with table. You must define mapping of Customer to a view by cheating EF:
modelBuilder.Entity<Customer>().ToTable("vw_ContactsList"); // EF code fist has no view mapping
Once you have this you can try again using:
public DbSet<Customer> Customers { get; set; }
Unless your view is updatable you will get exception each time you try to add, update or delete any customer in this set. After mapping relation between Customer and Contact mapped to views your navigation properties should hopefully work.
The problem with SqlQuery is the way how it works. It returns detached entities. Detached entities are not connected to the context and they will not lazy load its navigation properties. You must manually attach each Customer instance back to context and to do that you again need DbSet.

Partial update of entity objects with EF 4

I'm implementing DAL and BL layers of application.
It is hosted as WCF service, and EF 4 is used as ORM.
We have role based security layer and business rule that only part of object can be updated by some particular role.
Here is simplified example of problem:
We have such DTOs:
MainType
{
public Guid ID { get; set; }
public String DoctorField1 { get; set; }
public String DoctorField2 { get; set; }
public String NurseField1 { get; set; }
public String NurseField2 { get; set; }
public DateTime Created {get; set;}
public DateTime Updated {get; set;}
public Guid LastUpdateBy {get; set;}
public List<DetailsType> Details { get; set; }
}
DetailsType
{
public Guid MainTypeID { get; set; }
public Guid SomeIdentityID { get; set; }
public String DoctorDetail { get; set; }
public String NurseDetail { get; set; }
public DateTime Created {get; set;}
public DateTime Updated {get; set;}
public Guid LastUpdateBy {get; set;}
}
This entities are mapped to corresponding DB tables with the same fields.
ID field of MainType is Primary Key;
MainTypeID of DetailsType is Foreign Key to MainType table.
SomeIdentityID of DetailsType is FK to some other entity that is not important for this sample.
MainTypeID SomeIdentityID is complex primary key for DetailsType table.
I have graph of such objects (1 main and list details), and determined role of user who performs update operation.
My task is:
IF current user has role Doctor - update Doctor fields in Main object and all Details objects, insert new details objects.
IF current user has role Nurse - update Nurse fields in Main object and all Details objects.
save current date to Updated field
save current user id to LastUpdateBy field
do not modify Created field and any other field that are not updated by this role.
So for example if I have user with role Doctor I should do following:
Update DoctorField1, DoctorField2, Updated, LastUpdateBy in MainObject
Update DoctorDetail, Updated, LastUpdateBy in every details object
DO NOT modify any other fields.
Currently we have implementation that reads full graph for MainObject, makes necessary modifications and saves in back to DB.
This solution works too slow and I need to find a way to improve it.
Currently I know clearly how to do that by RAW SQL, but this will be my solution in case nothing else will help.
How can I make Entity Framework to update only needed fields and ignore another.
I have some successful results with ApplyOriginalValues and ApplyCurrentValues methods for String fields.
Idea was to assign some fictive value to property in both objects, for example string "##$%&##$%&##$%&##$%&##$%&", and EF then treats them as not modified properties during saving changes.
However this trick does not work with Boolean, Int32 and Decimal values.
I should use some simple approach to all objects.
I will appreciate any ideas and thoughts about this problem.
If you have such specific requirement you should start by modifying your WCF service to not accept fields which user cannot modify. That leads to two simple DTOs:
public class MainTypeUpdateDto
{
public Guid ID { get; set; }
public String Field1 { get; set; }
public String Field2 { get; set; }
public List<DetailsTypeUpdateDto> Details { get; set; }
}
public class DetailsTypeUpdateDto
{
public Guid MainTypeID { get; set; }
public Guid SomeIdentityID { get; set; }
public String Detail { get; set; }
}
All other fields either cannot be updated or should be handled by your server side logic.
Now when you receive dtos you can map them back to real entity objects. Based on user role you will know which fields and details you must set. You have two options to force EF to save only fields you want:
First create object graph with MainType and related details. Set only Ids in these entities and attach MainType entity to context. After that set all updatable fields to current values. Do not change state of any entity.
Create object graph with MainType and all related details and set all Ids and all updatable fields. After that attachMainType` entity to the context and manually set state for each modified property (on each entity).
You can need some additional logic if user can also remove or add details.

Resources