1

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."

RubenHerman
  • 1,674
  • 6
  • 23
  • 42

2 Answers2

1

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".

Worthy7
  • 1,455
  • 15
  • 28
  • what about if I have not access to the `EntityBase` code? I mean: how to solve the issue if `EntityBase` comes from a library? Is there a way to remove the unwanted attribute? – Gianpiero Oct 15 '17 at 14:34
  • If you are using EntityFramework, you might be able to "overwrite" the attribute by using the FluentAPI instead. I had to do exactly this to with some attributes I had from another library, which we're compatible with an oracle database. FluentAPI takes priority. FYI, just inherit the EntityBase and re-write the same properties with new/override, and that should wipe the attributes. – Worthy7 Oct 16 '17 at 01:29
0

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");
    }
}
Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • Very well explained and great solution! I'll test this right away. – RubenHerman Nov 28 '14 at 08:56
  • Tried it, but I still have the same problem. Any other solutions you know of? – RubenHerman Nov 28 '14 at 09:11
  • The only way I know of... is to remove the required attribute from my EntityBase and add a 'HasRequired' in the OnModelCreating for every other class deriving from EntityBase. But if there should be an easier, better solution, I'll change it. So suggestions are still welcome – RubenHerman Nov 28 '14 at 10:23
  • It seems that EF traces back the attribute on the base property. Even putting a `CustomValidationAttribute` (always returning Success) on the override doesn't remove the required validation. – Gert Arnold Nov 28 '14 at 10:38