Efcore: Validating entities using data annotations or fluent api (In Memory)

Created on 11 Dec 2016  路  7Comments  路  Source: dotnet/efcore

I can't verify and test my database by in memory providers. for example I set these properties to required :

```c#
public abstract class Log
{
#region Properties
public Guid Id { get; set; }
[Required]
public string ClientIp { get; set; }
[Required]
public string Application { get; set; }
[Required]
public string Host { get; set; }
[Required]
public string Path { get; set; }
[Required]
public string Method { get; set; }
[Required]
public string User { get; set; }
[Required]
public string Date { get; set; }
#endregion
}

and this is my DBContext :
```c#
public class ApplicationDbContext : IdentityDbContext<ApplicationUsers, Role, Guid>, IUnitOfWork
{
    private readonly IConfigurationRoot _configuration;

    public ApplicationDbContext(IConfigurationRoot configuration)
    {
        _configuration = configuration;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var useInMemoryDatabase = _configuration[key: "UseInMemoryDatabase"].Equals(value: "true",
            comparisonType: StringComparison.OrdinalIgnoreCase);
        if (useInMemoryDatabase)
            optionsBuilder.UseInMemoryDatabase();
        else
            optionsBuilder.UseSqlServer(
                connectionString: _configuration[key: "ConnectionStrings:ApplicationDbContextConnection"]
                , sqlServerOptionsAction: serverDbContextOptionsBuilder =>
                {
                    var minutes = (int) TimeSpan.FromMinutes(3).TotalSeconds;
                    serverDbContextOptionsBuilder.CommandTimeout(commandTimeout: minutes);
                });
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Log>()
            .HasKey(c => c.Id);
        modelBuilder.Entity<Log>()
            .HasDiscriminator<int>(name: "Type")
            .HasValue<LogRequest>(value: Convert.ToInt32(value: LogLevel.Information))
            .HasValue<LogError>(value: Convert.ToInt32(value: LogLevel.Error));


    }

And this is my unit test :
```c#
[TestClass]
public class LogRepositoryTest
{

private readonly IServiceProvider _serviceProvider;
public LogRepositoryTest()
{
var services = new ServiceCollection();
services.AddScoped();
services.AddScoped();
services.AddSingleton(provider => new ConfigurationBuilder()
.AddInMemoryCollection(initialData: new[]
{
new KeyValuePair(key: "UseInMemoryDatabase", value: "true"),

        })
        .Build());
     services.AddEntityFrameworkInMemoryDatabase().AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
    _serviceProvider = services.BuildServiceProvider();
}
[TestMethod]
public async Task Verify_SaveRequestLog()
{
    using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
        {
            context.Set<Log>().Add(new LogRequest());
            var result =await context.SaveAllChangesAsync();
            Assert.AreEqual(1, result);
        }

    }
}

```
But the unit test method always return 1 and passes, meanwhile the empty object of LogRequest must not save anything to database!
How can I determine not null properties for unit test ? In fact how can I enforce unit test to reflect to validation policies ?

Further technical details

EF Core version: 1.1.0
Database Provider: Microsoft.EntityFrameworkCore
Operating system: Windows 10
IDE: Visual Studio 2015

closed-by-design

Most helpful comment

Here is one way to check ValidationAttributes and IValidatableObject by overriding SaveChanges.

```C#
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();
}

}
```

All 7 comments

@paradisehuman EF Core doesn't do any validation of entities beyond what is needed for internal consistency. Validation is something that could be done in EF, but experience shows that it is not something that is useful to many developers because it usually cannot replace either client-side validation or database validation and there are also other places where validation can be done more effectively.

Going beyond EF to the database, the in-memory database does not currently validate nullability (i.e. requiredness) when saving property values. I will leave this issue open so that we can discuss as a team whether this is something we should add.

Also, if the intent is test with an in-memory database as an approximation for a relational database, then you might want to consider using SQLite in in-memory mode. See https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/index for more information.

Here is one way to check ValidationAttributes and IValidatableObject by overriding SaveChanges.

```C#
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 Thank you. that was perfect.

@bricelam I was looking at implementing this and it works great for the DataAnnotations validation, but anything I have setup via the Fluent API isn't validating this way. Do you have a tip that could make that work?

@TroySchmidt You would need to implement those checks yourself. Your validate method could use the model metadata available on EntityEntry.Metadata do determine what checks to make.

@bricelam I was looking at implementing this and it works great for the DataAnnotations validation, but anything I have setup via the Fluent API isn't validating this way. Do you have a tip that could make that work?

Have you found a way to implement validation for Fluent API?

The implementation is pretty straightforward; it's just a bit of work. Validate each instance based on the model metadata (e.g. required, max length, etc.)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

abdu292 picture abdu292  路  99Comments

satyajit-behera picture satyajit-behera  路  275Comments

vsfeedback picture vsfeedback  路  98Comments

rowanmiller picture rowanmiller  路  85Comments

rowanmiller picture rowanmiller  路  101Comments