Robert
Robert

Reputation: 3543

Entity Framework 6, validation not working as intended

I have a following piece of code

 var newPost = new Post()
                {
                    Activity = new Activity { Type = 1, ActivityTotalStatistic = new ActivityTotalStatistic() },
                    CreatedDate = oldPost.DateTimeCreated,
                    CategoryId = categoryId,
                    Title = oldPost.Name,
                    OwnerId = oldPost.UserID,
                    Slug = oldPost.Name,
                    LastUpdateDate = oldPost.DateTimeCreated,
                    PublishDate = oldPost.DateTimeCreated,
                    PostStatistic = new PostStatistic(),
                    PostItems = new List<PostItem>
                        {
                                new PostItem
                                {
                                    Activity = new Activity { Type = 2},<-- note this line of code
                                    CreatedDate = oldPost.DateTimeCreated,
                                    Title = oldPost.Name,
                                    Type = 1,
                                    Content = oldPost.Path
                                }
                            }

                    };

newDb.Posts.Add(newPost);
newDb.SaveChanges();

This is SQL schema for activity and statistics table

create table ActivityTotalStatistics
(
    Id int primary key identity(1,1),
    NumberOfLikes int not null,
    NumberOfDislikes int not null,
    SumOfLikes int not null,
    CommentCount int not null
)

create table Activities
(
    Id int identity (1,1) primary key,
    Type int not null,
    ActivityTotalStatisticId int not null  
        foreign key references ActivityTotalStatistics(Id)
)

As you can see, each activity should have activity statistic, because foreign key is not nullable, and my code should break, because Activity within post item does not have activity statistic instantiated.

But EF does not recognize it. What happens is that post item receives same activity statistic from this line of code

Activity = new Activity { Type = 1, ActivityTotalStatistic = new ActivityTotalStatistic() },

Is this a valid behavior of Entity Framework?

UPDATE Activity.cs model

public partial class Activity
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Activity()
        {
            this.ActivityLikes = new HashSet<ActivityLike>();
            this.Comments = new HashSet<Comment>();
            this.PostItems = new HashSet<PostItem>();
            this.Posts = new HashSet<Post>();
        }

        public int Id { get; set; }
        public int Type { get; set; }
        public int ActivityTotalStatisticId { get; set; }

        public virtual ActivityTotalStatistic ActivityTotalStatistic { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<ActivityLike> ActivityLikes { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Comment> Comments { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<PostItem> PostItems { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Post> Posts { get; set; }
    }

ActivityTotalStatistic.cs

public partial class ActivityTotalStatistic
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ActivityTotalStatistic()
        {
            this.Activities = new HashSet<Activity>();
        }

        public int Id { get; set; }
        public int NumberOfLikes { get; set; }
        public int NumberOfDislikes { get; set; }
        public int SumOfLikes { get; set; }
        public int CommentCount { get; set; }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Activity> Activities { get; set; }
    }

Upvotes: 2

Views: 480

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205539

Is this a valid behavior of Entity Framework?

I don't think so - unfortunately there is no official documentation/specification for these tracking behaviors, but looks like implementation specific side effect / defect (bug).

Rationale:

It happens only when the change tracker contains single Added principal entity with autogenerated PK, the dependent entity has both explicit reference navigation property and FK property, all with default values ('0' and null).

It does not happen if:

(1) There is second Added principal entity:

newDb.Set<ActivityTotalStatistic>().Add(new ActivityTotalStatistic());

SaveChanges throws DbUpdateException:

Unable to determine the principal end of the 'Activity_ActivityTotalStatistic' relationship. Multiple added entities may have the same primary key.

Not very user friendly, but still exception.

(2) The dependent entity has no explicit FK property, but correctly configured required shadow FK property. SaveChanges throws:

Entities in 'MyDbContext.Activities' participate in the 'Activity_ActivityTotalStatistic' relationship. 0 related 'Activity_ActivityTotalStatistic_Target' were found. 1 'Activity_ActivityTotalStatistic_Target' is expected.

Again not very user friendly, but still exception.

(3) The reference navigation property has [Required] attribute applied:

[Required]
public virtual ActivityTotalStatistic ActivityTotalStatistic { get; set; }

SaveChanges throws ValidationException containing ValidationError with (finally) user friendly message:

The ActivityTotalStatistic field is required.


Back to the original case. Only and only in that specific case, EF finds the single pending Added principal entity instance and associates it with all pending dependent entities having ActivityTotalStatistic == null and ActivityTotalStatisticId == 0.

All this for me indicates bug / unintended EF6 behavior. The best way of preventing it seems to be decorating the required reference navigation properties with [Required] attribute, but that is problematic in Database First (edmx) generated entity model like yours.

Upvotes: 4

Related Questions