EF Core 6: Make navigation readonly - entity-framework-core

public class ParentClass
{
[Key]
[StringLength(80)]
public string ID { get; set; } = string.Empty;
[StringLength(80)]
public string ChildID { get; set; } = string.Empty; // login name
[ForeignKey(nameof(ChildID))]
public virtual ChildClass Child { get; set; }
}
public class ChildClass
{
[Key]
[StringLength(80)]
public string ID { get; set; } = string.Empty;
}
When I read a ParentClass entity from the database, I want the Child property to be read, too. But when I write a ParentClass entity to the database, I don't want the Child property to be written as well. In the context of ParentClass, it is a readonly property.
Setting Child to null leads to an error, because EF Core 6 expects valid data to be present. This happens before my controller is reached, so I have no chance to set the property's state to unchanged, like
_context.Entry(parent.Child).State = EntityState.Unchanged;
I have googled and also read some SO articles on this, but not found a solution.
How do I have to specify ParentClass.Child to be readonly and make EF Core ignore it when the property is null?

Related

Use a SQL View as a navigation property

I've been searching to see if what I want is possible in EF6 Code First.
I have added a View in my database.
CREATE VIEW MyView AS
SELECT x.Id, x.Something, y.SomethingElse
FROM ....
I also have an entity with primary key Id:
public class MyEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
So, now, what I would like to do, is add a navigation property to MyEntity, like so:
public class MyEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ICollection<MyViewEntity> MyView { get; set; } = new Collection<MyViewEntity>();
}
Note: I've found this question. What I would like to do is the opposite.
For this purpose, I have created a class and added a foreign key attribute to indicate the relationship.
[Table("MyViewEntity")] // Hardcoded name, no -s suffix
public class MyViewEntity
{
public int MyEntityId { get; set; }
[ForeignKey("BusinessId,StatementId")]
public MyEntity MyEntity { get; set; }
public int Something { get; set; }
public int SomethingElse { get; set; }
}
Either I've forgotten something or what I want is not possible, because I get the error that there's a pending migration. If I run Add-Migration test, EF Code First tries to create a table "MyViewEntity" with the properties MyEntityId, Something, SomethingElse.
How can I tell Entity Framework that the navigation property I want to use comes from a View that's already there?

Remove required attribute - EF Code first

I've got an abstract class where a property "CreatedBy" is required.
This is used for ALMOST every entities but one (UserProfile itself)
How can I remove the Required attribute from the UserProfile without removing it from the other entities inheriting from EntityBase?
public abstract class EntityBase: IEntityBase
{
[Key, Column(Order = 0)]
public Guid? Id { get; set; }
[Key, Column(Order = 1)]
public int Version { get; set; }
[Required]
public DateTime Created { get; set; }
[Required]
public UserProfile CreatedBy { get; set; }
public bool IsDeleted { get; set; }
}
public class UserProfile: EntityBase
{
[Required, Index("Username", 3, IsUnique = true), MaxLength(900)]
public string Username { get; set; }
[Required, Index("Email", 4, IsUnique = true), MaxLength(900)]
public string Email { get; set; }
}
I tried overriding my OnModelCreating, but that doesn't work... Anybody any ideas?
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserProfile>().HasOptional(profile => profile.CreatedBy).WithMany();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
Strange thing is, in my database, the columns CreatedBy_Id and CreatedBy_Version can be null in the UserProfile table.
When I seed my UserProfile table, I get a validation error for each of them saying: "The CreatedBy field is required."
So you're actually designing wrong.
Your requirement clearly means you shouldn't inherit from the EntityBase. You shouldn't be trying to force this design onto your requirement.
Instead relax your EntityBase.
public abstract class EntityBase: IEntityBase
{
[Key, Column(Order = 0)]
public Guid? Id { get; set; }
[Key, Column(Order = 1)]
public int Version { get; set; }
[Required]
public DateTime Created { get; set; }
public UserProfile CreatedBy { get; set; }
public bool IsDeleted { get; set; }
}
Problem solved.
This is the correct way to do this. Entities can come from anywhere, they are not always created by a user at all, so it is quite wrong to put a requirement in your design which says "all entities must be made by a user".
The funny thing here is that the database generation logic is overridden by the fluent mapping, but the validation logic isn't. It's not the only area where there is tension between fluent mapping and data annotations.
The first thing that springs to my mind is: make one exception for the UserProfile class and don't let it inherit from EntityBase, and give it the properties it needs, among which the properties also found in EntityBase.
But I bet you have code elsewhere that relies on your classes inheriting the base class or implementing the interface.
The problem here is that RequiredAttribute has Inherited = true in its specification (from its base class, Attribute), so if you override CreatedBy in UserProfile it's still required.
Solution 1 - not good
To solve this problem you could create your own attribute, inheriting RequiredAttribute, and make it Inherited = false:
[AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
AllowMultiple = false,
Inherited = false)]
public class RequiredInBaseClassAttribute : RequiredAttribute
{
}
Now if you put this attribute in the base class...
[RequiredInBaseClass]
public virtual UserProfile CreatedBy { get; set; }
and override it in UserProfile...
public override UserProfile CreatedBy { get; set; }
it's not required any more in UserProfile. Well, it is. EF seems to trace back the attribute on the base property.
Solution 2
Replace the Required attribute by a CustomValidation attribute that allows CreatedBy to be null when the validated type is a UserProfile:
public abstract class EntityBase: IEntityBase
{
...
[CustomValidation(typeof(EntityBase), "CreatedByIsValid")]
public UserProfile CreatedBy { get; set; }
public static ValidationResult CreatedByIsValid(UserProfile value, ValidationContext context)
{
return value != null || (context.ObjectInstance is UserProfile)
? ValidationResult.Success
: new ValidationResult("CreatedBy is required");
}
}

Lazy loading in Entity Framework

I have an issue with the lazy loading behavior in EntityFramework 5. Here are my two models
public class Person {
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int? OfficeID { get; set; }
[ForeignKey("OfficeID ")]
public virtual Offices OfficeID_Offices { get; set; }
}
public class Offices
{
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
//Navigation Properties
public virtual ICollection<Person> Person_OfficeID { get; set; }
}
Then I have the following function in my Controller
[HttpPost]
public Person Read(int intID)
{
Person objData = (from obj in objDB.Persons
where obj.ID == intID && !obj.Deleted
select obj).FirstOrDefault();
}
This controller method is called through a jquery $.Ajax call, which returns a JSON object. Since my foreign key OfficeID_Offices is virtual, I'd expect to only be loaded when I demand it explicitely. However when I look at my returned JSON Object, I can see that the entire Offices object is returned as well.
Lazy loading seems to be enabled in my DbContext, so I'm wondering how I could avoid having the whole Office object returned.
Thank you!
Serialization of the entity object(s) accesses the property which triggers lazy loading. To disable lazy loading, set the objDB.Configuration.LazyLoadingEnabled property to False

Problems using TPT (Table Per Type) in EF 4.2 and deletion of parent objects

From what I understand on several posts the TPT architecure, with EF, does not create the necessary ON DELETE CASCADE when using a shared primary key.... It was also said that the EF context will handle the proper order of deletion of the sub-classed tables (however I do get an error that it breaks the constraint and that I can fix it with adding the ON DELETE CASCADE on the sub-class table)...
more background info...
I have a Section class, which has a number, title, and a list of pages. The page is designed using a super class which holds basic page properties. I have about 10+ sub-classes of the page class. The Section class holds an ICollection of these pages. The DB is created properly with the exception of no ON DELETE CASCADE on the sub-classed tables.
My code will create the entities and adds to the DB fine. However, if I try to delete a section (or all sections) it fails todelete due to the FK constraint on my sub-class page table...
public abstract BaseContent
{
... common properties which are Ignored in the DB ...
}
public class Course : BaseContent
{
public int Id {get;set;}
public string Name {get;set;}
public string Descripiton {get;set;}
public virtual ICollection<Chapter> Chapters{get;set;}
...
}
public class Chapter : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Course MyCourse{get;set;}
public virtual ICollection<Section> Sections{get;set;}
...
}
public class Section : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Chapter MyChapter {get;set;}
public virtual ICollection<BasePage> Pages {get;set;}
...
}
public abstract class BasePage : BaseContent, IComparable
{
public int Id { get; set; }
public string Title { get; set; }
public string PageImageRef { get; set; }
public ePageImageLocation ImageLocationOnPage { get; set; }
public int PageNumber { get; set; }
public virtual Section MySection { get; set; }
...
}
public class ChapterPage : BasePage
{
public virtual int ChapterNumber { get; set; }
public virtual string ChapterTitle { get; set; }
public virtual string AudioRef { get; set; }
}
public class SectionPage : BasePage
{
public virtual int SectionNumber { get; set; }
public virtual string SectionTitle { get; set; }
public virtual string SectionIntroduction { get; set; }
}
... plus about 8 other BasePage sub-classes...
public class MyContext: DbContext
{
...
public DbSet<Course> Courses { get; set; }
public DbSet<Chapter> Chapters { get; set; }
public DbSet<Section> Sections { get; set; }
public DbSet<BasePage> Pages { get; set; }
...
}
.. Fluent API ... (note Schema is defined to "" for SqlServer, for Oracle its the schema name)
private EntityTypeConfiguration<T> configureTablePerType<T>(string tableName) where T : BaseContent
{
var config = new EntityTypeConfiguration<T>();
config.ToTable(tableName, Schema);
// This adds the appropriate Ignore calls on config for the base class BaseContent
DataAccessUtilityClass.IgnoreAllBaseContentProperties<T>(config);
return config;
}
public virtual EntityTypeConfiguration<BasePage> ConfigurePageContent()
{
var config = configureTablePerType<BasePage>("PageContent");
config.HasKey(pg => pg.Id);
config.HasRequired(pg => pg.Title);
config.HasOptional(pg => pg.PageImageRef);
config.Ignore(pg => pg.ImageLocationOnPage);
return config;
}
public virtual EntityTypeConfiguration<ChapterPage> ConfigureChapterPage()
{
var config = configureTablePerType<ChapterPage>("ChapterPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.ChapterNumber);
config.Ignore(pg => pg.ChapterTitle);
return config;
}
public virtual EntityTypeConfiguration<SectionPage> ConfigureSectionPage()
{
var config = configureTablePerType<SectionPage>("SectionPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.SectionNumber);
config.Ignore(pg => pg.SectionTitle);
return config;
}
... other code to model other tables...
So the app is able to populate content and the relationships are properly set up. However, when I try to delete the course, I get the error that the delete failed due to the constraint on the ChapterPage to PageContent table..
Here is the code which deletes the Course (actually I delete all courses)...
using (MyContext ctx = new MyContext())
{
ctx.Courses.ToList().ForEach(crs => ctx.Courses.Remove(crs));
AttachLookupEntities(ctx);
ctx.SaveChanges();
}
If I add the 'ON DELETE CASCADE' in the ChapterPage and SectionPage table for its shared primary with PageContent, the delete goes through.
In summary,
The only solution that I have seen is to manually alter the constraints to add the ON DELETE CASCADE for all of my sub-class page tables. I can implement the change, as I have code which generates the DB script for the EF tables I need (a small subset of our whole DB) since we will not use EF to create or instantiate the DB (since it does not properly support migrations as yet...).
I sincerely hope that I have miscoded something, or forgot some setting in the model builder logic. Because if not, the EF designers have defined an architecure (TPT design approach) which cannot be used in any real world situation without a hack workaround. It's a half finished solution. Do not get me wrong, I like the work that has been done, and like most MSFT solutions its works for 70% of most basic application usages. It just is not ready for more complex situations.
I was trying to keep the DB design all within the EF fluent API and self-contained. It's about 98% there for me, just would be nice if they finished the job, maybe in the next release. At least it saves me all the CRUD operations.
Ciao!
Jim Shaw
I have reproduced the problem with a little bit simpler example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace EFTPT
{
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<BasePage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
public Parent Parent { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<BasePage> BasePages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Pages)
.WithRequired(p => p.Parent); // creates casc. delete in DB
modelBuilder.Entity<BasePage>()
.ToTable("BasePages");
modelBuilder.Entity<DerivedPage>()
.ToTable("DerivedPages");
}
}
class Program
{
static void Main(string[] args)
{
using (var ctx = new MyContext())
{
var parent = new Parent { Pages = new List<BasePage>() };
var derivedPage = new DerivedPage();
parent.Pages.Add(derivedPage);
ctx.Parents.Add(parent);
ctx.SaveChanges();
}
using (var ctx = new MyContext())
{
var parent = ctx.Parents.FirstOrDefault();
ctx.Parents.Remove(parent);
ctx.SaveChanges(); // exception here
}
}
}
}
This gives the same exception that you had too. Only solutions seem to be:
Either setup cascading delete for the TPT constraint in the DB manually, as you already tested (or put an appropriate SQL command into the Seed method).
Or load the entites which are involved in the TPT inheritance into memory. In my example code:
var parent = ctx.Parents.Include(p => p.Pages).FirstOrDefault();
When the entities are loaded into the context, EF creates actually two DELETE statements - one for the base table and one for the derived table. In your case, this is a terrible solution because you had to load a much more complex object graph before you can get the TPT entities.
Even more problematic is if Parent has an ICollection<DerivedPage> (and the inverse Parent property is in DerivedPage then):
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<DerivedPage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
public Parent Parent { get; set; }
}
The example code wouldn't throw an exception but instead delete the row from the derived table but not from the base table, leaving a phantom row which cannot represent an entity anymore because BasePage is abstract. This problem is not solvable by a cascading delete but you were actually forced to load the collection into the context before you can delete the parent to avoid such a nonsense in the database.
A similar question and analysis was here: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/

Do all associated objects have to be accessed (lazyloaded) before an existing object can be saved?

I'm learning EF Code First and am having trouble when updating existing records. I've boiled it down to this simple example:
This works:
using(var db = new DataContext()){
var p = db.People.Find(1);
p.Name="New Name";
Console.WriteLine(p.Gender.Name); //<--Unnecessary property access
db.SaveChanges(); //Success
}
...but this fails (when the WriteLine is removed):
using(var db = new DataContext()){
var p = db.People.Find(1);
p.Name="New Name";
db.SaveChanges(); //DbValidationError "Gender field is required."
}
Why do I have to access/load the Gender propery if I'm not using it and the data is already correctly stored in the database? I just want to change the Name on an existing record. In this example, Gender is a one-to-many association stored as Gender_Id in the People table. The classes are defined like this:
public class Person
{
[Key]
public int PersonId { get; set; }
[Required, MaxLength(50)]
public string Name { get; set; }
[Required, Column("Gender")]
virtual public GenderCode Gender { get; set; }
}
public class GenderCode
{
[Key]
public int Id { get; set; }
[Required, MaxLength(10)]
public string Name { get; set; }
}
public class DataContext:DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<GenderCode> GenderCodes { get; set; }
}
Of course, the fully defined classes are to have many more fields. I'd rather not have to access every dependant property every time I want to modify an unrelated value.
Is there a way to load an object, change a field, and save it without loading all related objects first?
Yes, this is necessary because of some horrible design mistakes in EF.
Check out my similar question, EF: Validation failing on update when using lazy-loaded, required properties
One trick is declaring FK properties along with the OO relations:
[ForeignKey("GenderId"), Column("Gender")]
virtual public GenderCode Gender { get; set; }
[Required]
public int GenderId { get; set; }
It is because you are using data annotations and Required attribute has also meaning for validation. Once you set navigation property as Required by data annotation it must be filled / loaded when you are going to persist entity to the database.