Author Edward Wilson-Lee joins Brewster Kahle to uncover the astonishing true story behind The Catalogue of Shipwrecked Books. Wilson-Lee chronicles the adventures of Hernando Colón, who sailed with his father Christopher Columbus before setting out to build a library of everything ever printed—a quest marked by shipwreck, mutiny, and the relentless pursuit of knowledge.
Grab your copy of The Catalogue Of Shipwrecked Books from The Booksmith: https://www.booksmith.com/book/9781982111403
This conversation was recorded on 6/28/2022. Watch the full video recording at: https://archive.org/details/book-talk-the-catalogue-of-shipwrecked-books
Check out all of the Future Knowledge episodes at https://archive.org/details/future-knowledge
Converters have long been a necessary evil in XAML: verbose, hard to maintain, and scattered across projects. But C# Expressions is so much more than replacing converters; you can finally write inline, strongly-typed logic directly in XAML, making your UI code cleaner and faster to write. It will also be a performance boost.
How many times have you tried to save changes to your context only to get an exception about validation problems, like a required property being null? I certainly have, and because of this, I came up with a solution to figure out what is happening. I will also provide some possible solutions for the problem of fully validating an entity using the Data Annotations API (a post on general validation with Data Annotations here).
All the entities that are currently tracked by a DbContext have some state known to the context. This state includes:
So, we can iterate through each tracked entry by means of the Entries, Entries<T>, or Entry methods:
var entity = ...; var entry = ctx.Entry(entity); var entries = ctx.ChangeTracker.Entries;
If the entity is not known to the context, it's state will be Detached, and it won't be possible to get the properties' values. For those that are known, we can validate their properties like this:
public static class DbContextExtensions
{
public static IEnumerable<ValidationResult> ValidateState(this DbContext context) where TEntity : class where TContext : DbContext
{
ArgumentNullException.ThrowIfNull(context);
var entries = context.ChangeTracker
.Entries()
.Where(e => e.State is EntityState.Added or EntityState.Modified);
foreach (var entry in entries)
{
foreach (var prop in entry.Properties)
{
var isRequired = (prop.Metadata.IsNullable == false);
var value = prop.CurrentValue;
if (isRequired && (value == null || (value is string strValue1 && strValue1.Length == 0)))
{
yield return new ValidationResult($"Property '{entry.Metadata.ClrType}.{prop.Metadata.Name}' is null for entity '{entry.Entity}' with state '{entry.State}'.", [prop.Metadata.Name]);
}
else if (prop.Metadata.GetMaxLength() > 0 && value is string strValue2 && strValue2?.Length > prop.Metadata.GetMaxLength())
{
yield return new ValidationResult($"Property '{entry.Metadata.ClrType}.{prop.Metadata.Name}' has length greater than the allowed {prop.Metadata.GetMaxLength()} for entity '{entry.Entity}' with state '{entry.State}'.", [prop.Metadata.Name]);
}
}
}
}
}
We loop through all modified and added entities, look into their properties' values, and from its metadata we find out if they're mandatory (IsNullable) or what's the maximum length, if provided (GetMaxLength). To use:
var entity = new Person { Name = null };
entity.Persons.Add(entity);
var errors = entity.ValidateState(); //validation error: Property 'Person.Name' is null for entity '{ Name = , ... }''
For each validation error we return a ValidationResult instance with a message and the faulting property name.
For the time being, only two validations are implemented, as you can see:
You may remember my post on entity validation using Data Annotations, I also wrote another one on entity validation on EF Core, which I will extend here.
EF Core does not feature entity validation out of the box, but there are ways to add it:
Options 1 is very intrusive, 2 is less so, and 3 probably provides the cleanest approach. Let's go through them one by one. There are other options, but they are far too convoluted, and I won't go into them.
In any case, all validations will be executed by calling Validator.ValidateObject, which throws a ValidationException when validation fails.
That one is easy to implement but does not scale so well - we need to do the same on all DbContext classes:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
CoreSaveChanges();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken)
{
CoreSaveChanges();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void CoreSaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(x => x.State is EntityState.Modified or EntityState.Added))
{
Validator.ValidateObject(entry.Entity, new ValidationContext(entry.Entity), true);
}
}
All good, now, when we save the context, synchronously or asynchronously, validation kicks in and an exception is through in case of a validation error. As you can see, this one requires changes to the DbContext, which we probably don't like to do.
This option involves adding a custom interceptor to the context. This can be done:
For the former option, here's a simple example with AddDbContext:
builder.Services.AddDbContext<MyContext>(options =>
{
options.AddInterceptors(new ValidationInterceptor());
});
For the latter:
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.AddInterceptors(new ValidationInterceptor());
base.OnConfiguring(builder);
}
The actual validator, ValidationInterceptor, inherits from SaveChangesInterceptor, and can be implemented like this:
public class ValidationInterceptor : SaveChangesInterceptor
{
private void CoreValidate(DbContext ctx)
{
foreach (var entry in ctx.ChangeTracker.Entries().Where(x => x.State is EntityState.Modified or EntityState.Added))
{
Validator.ValidateObject(entry.Entity, new ValidationContext(entry.Entity), true);
}
}
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
{
CoreValidate(eventData.Context!);
return base.SavingChanges(eventData, result);
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
{
CoreValidate(eventData.Context!);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
The interceptor must override both SavingChanges and SavingChangesAsync, because one or the other will be called, depending on wether we call SaveChanges or SaveChangesAsync.
This one is the cleanest, in my opinion. We have to hook to the SavingChanges event of the DbContext, which can be done by anyone:
DbContext ctx = ...;
ctx.SavingChanges += (args) => ValidateContext(ctx);
static void ValidateContext(DbContext ctx)
{
foreach (var entry in ctx.ChangeTracker.Entries().Where(x => x.State is EntityState.Modified or EntityState.Added))
{
Validator.ValidateObject(entry.Entity, new ValidationContext(entry.Entity), true);
}
};
Pretty simple, don't you think? The ValidateContext method can be put in some shared class; it can also be used by any of the other two alternatives.
As you can see, EF Core provides almost limitless extension points, and it's always possible to add any missing functionality! Stay tuned for more EF Core posts coming soon!
Read more of this story at Slashdot.
The SQL community is gathering in Atlanta this March for the first‑ever SQLCon, co‑located with FabCon, the Microsoft Fabric Community Conference, March 16-20. One registration unlocks both events, giving you access to deep SQL expertise and the latest in Fabric, Power BI, data engineering, real‑time intelligence, and AI. Whether you’re a DBA, developer, data engineer, architect, or a leader building data‑driven team, this is your chance to learn, connect, and shape what’s next.
Bonus: make the budget work
Depending on timing, look for early‑bird pricing, team discounts, or buy‑one‑get‑one offers on the registration page. These deals move fast, so check what’s live when you register. You can always use SQLCMTY200 for $200 off!
Wrap‑up: build the next chapter of your data strategy at SQLCon
SQLCon + FabCon is the highest‑leverage week of the year to sharpen your technical skills, understand SQL’s next chapter, accelerate modernization and performance, and build meaningful connections across the global community. If SQL plays any role in your data estate, this is the one event you shouldn’t miss.
See you in Atlanta!
The post Five Reasons to attend SQLCon appeared first on Microsoft Azure Blog.