REALLY wanted to get the CourseService
wrapped up this week, but ended up getting to one of those major decision points that will drive how things are done in the project from now on, and I didn't want to rush the decision.
The choice now is: to use DTOs (data transfer objects) or not.
I've never used them before, and I just can't get past the feeling that it ultimately amounts to a lot of code duplication (core entity + near-exact-copy DTO).
I just don't know enough about the pattern (or how to do things efficiently/properly without it) yet.
Also: new fun was had! I think I'll start every stream with a Codewars kata or two to warm up and fill in some syntax gaps I still have in ol' C#. Lots of fun.
Get the Git
FitzyCodesThings / core-lms
An open source online learning management system project in ASP.Net Core MVC. Explores many best practices and patterns in modern software development.
Watch the Replay
Skip to the Good Stuff
- Codewars Katas! 00:02:06
-
List<Course>
vsIQueryable<Course>
00:42:00 -
AddCourseAsync
with TDD 01:17:00 - To DTO or not to DTO? 01:41:39
Project Update
I did get a few things done (including figuring out why I like a certain way of using the "repositoryish" pattern over another).
- Updated eager-loading
.Include()
s on the Course entity (I'm used to lazy loading in EF and not having to think about it) - Added
SaveChanges()
andSaveChangesAsync()
overrides to handle soft deletes (with a caveat (see below)) and auto-fillingDateCreated
,DateUpdated
, andDateDeleted
fields onIAuditableEntity
s - Added the
AddCourseAsync
method toCourseService
business logic (starting with the test, of course; really loving TDD as I get used to it)
Soft Deletes in Entity Framework Core
Now: the caveat on soft deletes.
Implementing softdeletes in EF Core is pretty simple on the surface.
- Use the new global query filter mechanism to filter soft-deletable objects with the
IsDeleted
flag set to true (or a DateDeleted, etc.):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasQueryFilter(e => e.IsDeleted == false);
modelBuilder.Entity<Address>().HasQueryFilter(e => e.IsDeleted == false);
}
- Override
SaveChanges
andSaveChangesAsync
to intercept any entities in the Deleted state and change them to modified with the IsDeleted flag set:
foreach (var entry in ChangeTracker.Entries())
{
// Only process soft-deletables (implements interface ISoftDeletable)
if (typeof(ISoftDeletable).IsAssignableFrom(entry.Entity.GetType()))
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["IsDeleted"] = false;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
}
return await SaveChangesAsync();
Seems simple enough, but I encountered an odd scenario if that's all you do to update deleted entities in an already-built project that I'm updating to .Net Core.
The long and short of it is: if an entity you soft delete is contained in an ICollection
navigation property of a parent object (only scenario I've tested so far), the soft-deleted entry will not be removed from the parent collection as long as you're using the same scoped instance of the DbContext (so during the same web request, in the same using
block, etc.).
I've detailed the issue and linked a demonstration project in the GitHub issue here, but let me know if you've run into this and how you solved it.
Using Data Transfer Objects (DTOs)
Then, as mentioned in the intro, I got stuck on whether or not to use DTOs (then, if I do, whether I should add format and validation decorators on the DTOs or the core entities).
I'm gonna put up another post in the next few days as I explore best practices for using DTOs (or not using them) and what makes sense for me.
Question: do you use DTOs? Why or why not? Any good articles/videos/courses I can check out that address it?
Return List
or Return IQueryable
?
Otherwise: I took a look at the GetCoursesAsync
method and considered whether to just return List<Course>
like I had been versus IQueryable<Course>
.
My argument for using IQueryable
was that it just made things simpler for implementing various where clauses, limits, etc.
The problem became very apparent, though, when I realized that there's no ToListAsync
method on standard LINQ (it's an EF Core extension). This would mean I'd have to add an extra dependency on Entity Framework when I don't really want to (or give up Async operation, which I definitely don't want to do).
There may be another solution to the problem that I didn't see, but for now, my plan is to look at using custom predicates to get the dynamic querying that I want without having to just return an IQueryable
.
Wrapping Up
Have I mentioned this is a learning project?
And that it's mostly a learning project for ME? 🤣
Pace WILL pick up soon, but these are big decisions, and I want to get this (as) right (as possible) from the beginning.
Off to the Google I go to learn more about DTOs!
Until next time!
- John
Top comments (0)