1

I'm using EF Core.

My Customer entity has properties Address1, Address2, and AddressFull.

Depending on which system sends me the data, I may receive Address1 and Address2, or I may receive AddressFull.

So I need:

  • EITHER Address1 and Address2 required, and AddressFull not-required
  • OR Address1 and Address2 not-required, and AddressFull required

So I have:

  entityTypeBuilder.Property(p => p.Address1).IsRequired(false);
  entityTypeBuilder.Property(p => p.Address2).IsRequired(false);
  entityTypeBuilder.Property(p => p.AddressFull).IsRequired(false);

But this config does not properly map to my domain, and I want to enforce the logic. Is that possible in EF Core?

grokky
  • 8,537
  • 20
  • 62
  • 96

2 Answers2

1

You need to allow for your DbContext different mapping according to your scenario, you can check this answer to enable in your DbContext change the mapping:

Dynamically changing schema in Entity Framework Core

Community
  • 1
  • 1
H. Herzl
  • 3,030
  • 1
  • 14
  • 19
1

In general, there are two ways to implement this type of complex domain logic. You can do it in the database using CHECK constraints or triggers, or you can do it in your domain objects and check them during SaveChanges. Here is an example of the latter.

class MyEntity : IValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (string.IsNullOrEmpty(Address1)
            && string.IsNullOrEmpty(Address2)
            && string.IsNullOrEmpty(AddressFull))
        {
            yield return new ValidationResult("An address is required.");
        }
    }
}

class MyContext : DbContext
{
    public override int SaveChanges()
    {
        var entities = from e in ChangeTracker.Entries()
                       where e.State == EntityState.Added
                           || e.State == EntityState.Modified
                       select e.Entity;
        foreach (var entity in entities)
        {
            var validationContext = new ValidationContext(entity);
            Validator.ValidateObject(
                entity,
                validationContext,
                validateAllProperties: true);
        }

        return base.SaveChanges();
    }
}
bricelam
  • 28,825
  • 9
  • 92
  • 117
  • Correct me if I'm wrong, but this validates all entities instead of only those with complex validation, e.g. `MyEntity`? So should we add `where e is IValidatableObject` to filter for those ones? – grokky Dec 13 '16 at 11:13
  • Correct. It mimics some of the behavior of EF6 where where any `ValidationAttribute`s (e.g. `[Required]` and `[StringLength]`) will also get validated before sending the changes to the database which may save you some network traffic. – bricelam Dec 13 '16 at 16:59
  • Sorry now I'm more confused. What I mean is the above code will try validate all entities, even if they don't implement `IValidatableObject`. What I was trying to ask is whether that's a good idea? Surely it will throw? That's why I though to filter `entities` using the `where e is IValidatableObject` line... In other words, isn't your code missing that line? – grokky Dec 13 '16 at 18:57
  • 1
    `Validator` is a general-purpose validation mechanism. If a class doesn't implement `IValidatableObject` or have any `ValidationAttribute`s on it or its members, it will consider the object valid and do nothing. – bricelam Dec 13 '16 at 21:50
  • See [my blog post](http://www.bricelam.net/2016/12/13/validation-in-efcore.html) for a bit more context. – bricelam Dec 13 '16 at 21:51
  • Ok makes sense now! It wasn't clear because the docs don't mention that it's a no-op in that case... Thanks. – grokky Dec 14 '16 at 19:54
  • PS for others stumbling onto this, keep in mind that if validation fails, it throws a `ValidationException`. – grokky Dec 14 '16 at 19:54
  • I wonder [if it's possible to get the original values](http://stackoverflow.com/questions/41771376/how-do-i-get-a-propertys-original-value-while-validating-in-ef-core) using your method? – grokky Jan 20 '17 at 21:16