I have an MVC application that uses Entity Framework v6. We have a class
public class ChildObject
{
public string Name { get; set; }
....
}
that maps to a table in the database. This table has 6 rows that are never changed. Neither will there ever be any additions. We have a second class defined along the lines of the following:
public class ParentClass
{
public int ChildObjectId { get; set; }
public ChildObject ChildObject { get; set; }
....
}
Whenever a ParentClass object is created or updated the logic only references the ChildObjectId property. The ChildObject property is only referenced when data is pulled back for viewing. However about once per month an extra row appears in the ChildObject table that is a duplicate of an existing row. This obviously causes issues. However I can't see how this could happen seeing as we only ever save using the Id value. Any thoughts on how this could be occurring would be very much appreciated.
The typical culprit for behavior like you describe is when a new child entity is composed based on existing data and attached to the parent rather than the reference associated to the context. An example might be that you load child objects as a set to select from, and send the data to your view. The user wants to change an existing child reference to one of the 6 selections. The call back to the server passes a child object model where there is code something like:
parent.ChildObject = new ChildObject{ Name = model.Name, ... }
rather than:
var child = context.Children.Single(x => x.Id = model.ChildObjectId);
parent.ChildObject = child;
Depending on how your domain is set up you may run into scenarios where the EF context creates a new child entity when a navigation property is set. Check with a FindUsages on the ChildObject property and look for any use of the setter.
In general you should avoid combining the use of FK properties (ChildObjectId) with navigation properties (ChildObject) because you can get confusing behavior between what is set in the navigation reference vs. the FK. Entities should be defined with one or the other. (Though at this time EF Core requires both if Navigation properties are used.)
A couple notables from your example:
Mark the navigation property as virtual - This ensures that EF assigns a proxy and recognizes it.
Option A - Remove the FK child ID property. For the parent either use an EntityTypeConfiguration or initialize the DbContext to map the FK column:
EntityTypeConfiguration:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.Map(x => x.MapKey("ChildObjectId"));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().Map(x => x.MapKey("ChildObjectId")).WillCascadeOnDelete(false);
}
or Option B - Ensure the FK is linked to the reference, and take measures to ensure that the two are always kept in sync:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.HasForeignKey(x => x.ChildObjectId));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().HasForeignKey(x => x.ChildObjectId)).WillCascadeOnDelete(false);
}
Option B is the only one currently available with EF Core to my knowledge, and it may help mitigate your issue but you still have to take care to avoid discrepancies between the navigation property and the FK. I definitely recommend option A, though it will likely require a bit of change if your code is commonly accessing the FK column.
Related
I have two entities, Tag and Member. A member can be marked with multiple tags. A tag can be used to mark multiple members. It's a clear case of many-to-many relation and since I'm using EF Core, I have to declare an explicit connector, which I call Tag_Member. I configure it in the following way.
private void OnModelCreating(EntityTypeBuilder<Tag_Member> entity)
{
entity.HasKey(e => new { e.TagId, e.MemberId });
entity.HasOne(e => e.Tag);
entity.HasOne(e => e.Member)
.WithMany(e => e.Tag_Member)
.HasForeignKey(e => e.MemberId);
}
The behavior I wish to enforce when deleting is as follows.
When removing an instance of Tag_Member, nothing is changed.
When removing an instance of Tag, any connected instances of Tag_Member are deleted.
When removing an instance of Member, any connected instances of Tag_Member are deleted.
I'm confused on two points. When I add the condition for deletion as shown below, I have a lot of options to pick from and, despite reading the intellisense, I don't feel certain which to use to enforce the above behavior.
entity.HasOne(e => e.Member)
.WithMany(e => e.Tag_Member)
.HasForeignKey(e => e.MemberId)
.OnDelete(DeleteBehavior.NoAction);
Should I use NoAction, ClientNoAction, Restrict or someting else? I'm not even clear on which of hte entities that the deletetion behavior affects. Which is it?
The second point of confusion is that I don't get OnDelete() to appear for the tag configuration. I haven't used WithMany() because that entity lacks references back to the interlinking entity. Can I still manage its deletion behavior? Do I need to explicitly declare it to achieve the requested behavior?
entity.HasOne(e => e.Tag)
.OnDelete(DeleteBehavior.NoAction);
The classes look roughly like this.
public class Tag { public Guid Id { get; set; } }
public class Member { public Guid Id { get; set; } }
public class Tag_Member
{
public Guid TagId { get; set; }
public Guid MemberId { get; set; }
public Tag Tag { get; set; }
public Member Member { get; set; }
}
My references are mainly this and this.
edit: Based on the suggestions in the answer, this is the final version of the relation between members and tag.
private static void OnModelCreating(EntityTypeBuilder<Member> entity)
{
entity.HasKey(e => e.Id); ...
}
private static void OnModelCreating(EntityTypeBuilder<Tag> entity)
{
entity.HasKey(e => e.Id); ...
}
private static void OnModelCreating(EntityTypeBuilder<Member_Tag> entity)
{
entity.HasKey(e => new { e.MemberId, e.TagId });
entity.HasOne(e => e.Member).WithMany().OnDelete(DeleteBehavior.NoAction);
entity.HasOne(e => e.Tag).WithMany().OnDelete(DeleteBehavior.NoAction);
}
I'm not even clear on which of hte entities that the deletetion behavior affects. Which is it?
That's easy. The cascade delete always affects the dependent entity (i.e. the entity containing the FK).
I don't feel certain which to use to enforce the above behavior. Should I use NoAction, ClientNoAction, Restrict or someting else?
You seem to be using EF Core 3.0 preview which adds more options not documented yet. But the option for classic cascade delete implemented at the database level has always been Cascade.
I haven't used WithMany() because that entity lacks references back to the interlinking entity.
In order to be able to fluently configure the relationship aspects, you have to fully specify the relationship parties by using the Has + With pair. Since navigation properties are not mandatory for either side of the relationship, all you need it to pass the correct argument to Has / With method - if you do have navigation property, pass the name or lambda expression accessor, otherwise don't pass anything (but still include the call). e.g.
entity.HasOne(e => e.Tag)
.WithMany() // <--
.OnDelete(DeleteBehavior.Cascade); // now you can do this
But note that DeleteBehavior.Cascade is the default for required relationships (in other words, when the FK is non nullable type), so you normally don't need fluent configuration for that. And if the property names follow the EF Core naming conventions, you don't need fluent configuration at all.
Simple example can be seen here.
Imagine that I want to add an IsDeleted colum or some auditing columns to all of my entities. I could create a base class from which all of my entities will inherit and this will solve my problem, however I cannot specify the order in which the column will be created so I will end up with all the auditing fields before the fields of my entity, which I do not want. I want them to be at the end of the table.
In the standard version of entity framework we can do this by using annotations that specify the order of the columns. However, such a thing does not exist for EF core at the moment.
I could do it with the fluent api on the OnModelCreating() method, the problem is that I only know how to do it individually for each of my entities, which means I would have to write the same code for every entity I have.
Is there any way I can do it generically for all of my entities? Some sort of for loop that iterates through all the entities registered in the DbSets on my dbcontext?
Your question title is about adding the same properties to multiple entities. However, you actually know how to achieve this (use a base type) and your actual question is how to ensure that these properties come last in the generated tables' columns.
Although column order shouldn't really matter nowadays, I'll show an alternative that you may like better than a base type and also positions the common properties at the end of the table. It makes use of shadow properties:
Shadow properties are properties that are not defined in your .NET entity class but are defined for that entity type in the EF Core model.
Most of the times, auditing properties don't need much visibility in the application, so I think shadow properties is exactly what you need. Here's an example:
I have two classes:
public class Planet
{
public Planet()
{
Moons = new HashSet<Moon>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Moon> Moons { get; set; }
}
public class Moon
{
public int ID { get; set; }
public int PlanetID { get; set; }
public string Name { get; set; }
public Planet Planet { get; set; }
}
As you see: they don't have auditing properties, they're nicely mean and lean POCOs. (By the way, for convenience I lump IsDeleted together with "audit properties", although it isn't one and it may require another approach).
And maybe that's the main message here: the class model isn't bothered with auditing concerns (single responsibility), it's all EF's business.
The audit properties are added as shadow properties. Since we want to do that for each entity we define a base IEntityTypeConfiguration:
public abstract class BaseEntityTypeConfiguration<T> : IEntityTypeConfiguration<T>
where T : class
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.Property<bool>("IsDeleted")
.IsRequired()
.HasDefaultValue(false);
builder.Property<DateTime>("InsertDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
builder.Property<DateTime>("UpdateDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
}
}
The concrete configurations are derived from this base class:
public class PlanetConfig : BaseEntityTypeConfiguration<Planet>
{
public override void Configure(EntityTypeBuilder<Planet> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
// Follows the default convention but added to make a difference :)
builder.HasMany(p => p.Moons)
.WithOne(m => m.Planet)
.IsRequired()
.HasForeignKey(m => m.PlanetID);
base.Configure(builder);
}
}
public class MoonConfig : BaseEntityTypeConfiguration<Moon>
{
public override void Configure(EntityTypeBuilder<Moon> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
base.Configure(builder);
}
}
These should be added to the context's model in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PlanetConfig());
modelBuilder.ApplyConfiguration(new MoonConfig());
}
This will generate database tables having columns InsertDateTime, IsDeleted and UpdateDateTime at the end (independent of when base.Configure(builder) is called, BTW), albeit in that order (alphabetical). I guess that's close enough.
To make the picture complete, here's how to set the values fully automatically in a SaveChanges override:
public override int SaveChanges()
{
foreach(var entry in this.ChangeTracker.Entries()
.Where(e => e.Properties.Any(p => p.Metadata.Name == "UpdateDateTime")
&& e.State != Microsoft.EntityFrameworkCore.EntityState.Added))
{
entry.Property("UpdateDateTime").CurrentValue = DateTime.Now;
}
return base.SaveChanges();
}
Small detail: I make sure that when an entity is inserted the database defaults set both fields (see above: ValueGeneratedOnAdd(), and hence the exclusion of added entities) so there won't be confusing differences caused by client clocks being slightly off. I assume that updating will always be well later.
And to set IsDeleted you could add this method to the context:
public void MarkForDelete<T>(T entity)
where T : class
{
var entry = this.Entry(entity);
// TODO: check entry.State
if(entry.Properties.Any(p => p.Metadata.Name == "IsDeleted"))
{
entry.Property("IsDeleted").CurrentValue = true;
}
else
{
entry.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
}
}
...or turn to one of the proposed mechanisms out there to convert EntityState.Deleted to IsDeleted = true.
You can always generate an initial migration for the model and manually rearrange the column order in the Migration.
Here is the open issue tracking support for explicit column ordering in EF Core: https://github.com/aspnet/EntityFrameworkCore/issues/10059
Also see this question and answer on using Shadow Properties and Query Filters for soft deletes. EF Core: Soft delete with shadow properties and query filters
I'm hoping you can help me with a small issue I'm having.
I have an entity framework Code First setup where I have the following setup:
public class UserDetails{
public int Id {get;set;}
//... some other properties .. //
//This represents the approval group the user is a member of.
public virtual ApprovalGroup {get;set;}
//This represents the approval groups that the user is resposible for approving
public virual ICollection<ApprovalGroup> ApprovalGroups {get;set;}
}
public class ApprovalGroup
{
public int Id {get;set;}
public string Name {get;set;}
public virtual UserDetails Approvee {get;set;}
public virtual ICollection<UserDetails> Members {get;set;}
}
In my db context I have the following:
modelBuilder.Entity<ApprovalGroup>().ToTable("ApprovalGroup")
.HasKey(t=>t.Id)
.HasRequired(t=>t.Approver);
modelBuilder.Entity<ApprovalGroup>().HasMany(t=>t.Members)
.WithOptional().WillCascadeOnDelete(false);
So an approval group MUST have an approvee set-up, however it could potentially have no members (particularly when first configured).
This code is running, however when I examine the database that it creates the approval group table has an extra column in it that is called "UserDetails_Id". It is set up as a foreign key, but it is always null.
The schema of the table it creates has the following columns:
Id, Name, UserDetails_Id, Approver_Id
I have no idea why it is creating the unnecessary table "UserDetails_Id" and I'd like it not to as there is no reason for it. I suspect I have something wrong with my configuration/mapping behaviour but I can't figure out what it is.
As of yet, google has failed to shed light on what I'm doing wrong so if anyone here can help it would be greatly appreciated.
Nik
The issue you are experiencing is because of the improper/incomplete relationships mappings, combined with EF default conventions.
I would suggest you always configuring the relationships separately, only once per relationship and use the overloads that match exactly the presence/absence of the navigation and explicit FK properties.
In this particular case you have two one-to-many bidirectional (i.e. with navigation properties on both ends) relationships with no explicit FK properties. So the correct configuration should be like this:
// Entities
modelBuilder.Entity<ApprovalGroup>()
.ToTable("ApprovalGroup")
.HasKey(t => t.Id);
modelBuilder.Entity<UserDetails>()
.ToTable("UserDetails")
.HasKey(t => t.Id);
// Relationships
modelBuilder.Entity<ApprovalGroup>()
.HasRequired(t => t.Approver)
.WithMany(t => t.ApprovalGroups);
modelBuilder.Entity<ApprovalGroup>()
.HasMany(t => t.Members)
.WithOptional(t => t.ApprovalGroup) // or whatever the name of the navigation property is (it's missing in the posted code)
.WillCascadeOnDelete(false);
In previous versions of EF I can use following code to implement an identifying relationship:
public class Child
{
[Key, Column(Order = 1)]
public virtual int Id { get; set; }
[Key, Column(Order = 2)]
public virtual int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
It's needed to easily remove a child from collection like this:
var parent = _context.Parents.First();
var child = parent.Children.First();
parent.Children.Remove(child);
_context.SaveChanges();
This approach is described in http://www.kianryan.co.uk/2013/03/orphaned-child/ (the method #2).
But in EF7 this code throws exception when migration is creating:
An exception was thrown while executing a resolve operation. See the
InnerException for details. ---> Entity type 'Child' has composite
primary key defined with data annotations. To set composite primary
key, use fluent API.
I also tried to use FluentAPI as described in How to define nested Identifying Relationships Entity Framework code first in following code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
modelBuilder.Entity<Child>()
.HasKey(c => new {c.Id, c.ParentId});
base.OnModelCreating(modelBuilder);
}
This approach allow generate a migration successfully, but when I tried to remove a child from Children collection I got following exception:
System.InvalidOperationException: The association between entity types
'Parent' and 'Child' has been severed but the foreign key for this
relationship cannot be set to null. If the dependent entity should be
deleted, then setup the relationship to use cascade deletes.
But I wouldn't like to use cascade deletes, I would like to use identifying relationship!
Please, help me understand what I do incorrect. Thank you!
Use cascade on delete instead as this is what it's used for:
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
.WillCascadeOnDelete(true);
https://msdn.microsoft.com/en-gb/data/jj591620.aspx
Just in case someone sees this error, let me tell you how I resolved mine:
When you do an update, on EF you need to first query the database and get the data model, then map the Domain layer model with your changes onto it (basically copying fields onto the data), and finally call the DBContext update method, then save changes.
My problem was that my model (not the data model, the domain model) also had the sub objects on it.
So here's the data layer model (for example):
public class Parent
{
public int ChildId {get; set; }
[ForeignKey("ChildId")]
public virtual Child Child { get; set; }
}
And here's how the domain layer model should be:
public class Parent
{
public int ChildId { get; set; }
//public Child Child { get; set; } // this caused the error, keep reading if you want to know more.
}
When I was seeing the error, I had been using Autofac's runtime mapper to map the domain layer model's properties onto the data layer model. However, the child in the domain layer model was null, so it would nullify the data layer, causing the error:
"The association between entity types 'Parent' and 'Child' has been severed but the foreign key for this relationship cannot be set to null. If the dependent entity should be deleted, then setup the relationship to use cascade deletes."
By the way, in the db context class, I have the following relationship defined:
modelBuilder.Entity<Parent>()
.HasOne(a => a.Child)
.WithMany()
.HasForeignKey(p => p.ChildId)
.OnDelete(DeleteBehavior.Restrict);
It's working.
I'm running into the following problem:
In this project it is mandatory that the registries should not be physically (from the database) deleted, so I create a bool property named deleted on each class that represents a table on the database.
In a delete case, the property will become true and need to be only accessible, in any case, by SQL Queries inside the Database.
I made the repositories and etc to always return the IQueryables with a query.where(x => !x.Deleted), but how can I do this on the Collections that are 'lazy loaded' by EF?
For example, when I get a Person and it has a ICollection, it always comes populated with all objects linked to that Person, I always have to filter the deleted ones manually.
Is there a way to tell EF that in every Lazy Loading it should use a custom where clause?
You can filter out the deleted entities while mapping the DbSet itself by overriding OnModelCreating.
Example:
public class MyContext : DbContext
{
public virtual IDbSet<Company> Companies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.Map(m => m.Requires("IsDeleted").HasValue(false))
.Ignore(m => m.IsDeleted);
}
}
This will set a default value of false for the IsDeleted property and filters out records where IsDeleted is true.
More info: https://putshello.wordpress.com/2014/08/20/entity-framework-soft-deletes-are-easy/