loading...

CoreLMS: To DTO or Not to DTO, That is the Question (Among Other Thoughts)

fitzycodesthings profile image John "Fitzy" DeLancey Originally published at fct.dev ・4 min read

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

GitHub logo 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

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

  1. Updated eager-loading .Include()s on the Course entity (I'm used to lazy loading in EF and not having to think about it)
  2. Added SaveChanges() and SaveChangesAsync() overrides to handle soft deletes (with a caveat (see below)) and auto-filling DateCreated, DateUpdated, and DateDeleted fields on IAuditableEntitys
  3. Added the AddCourseAsync method to CourseService 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.

  1. 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);
    }
  1. Override SaveChanges and SaveChangesAsync 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

Posted on by:

fitzycodesthings profile

John "Fitzy" DeLancey

@fitzycodesthings

I'm a (primarily) DotNet developer and software consultant with 15+ years of experience. Lovin' live coding on Twitch.

Discussion

pic
Editor guide