Missed the Last 10 Years of C#? Read This (Quick) Guide

Here’s an opinionated catch-up guide if you missed the last decade of the C#/.NET/Microsoft evolution.

#1. C# isn’t JAVA anymore. That joke doesn’t work anymore. The two languages have taken completely different paths. They don’t even look the same.

#2. There’s a new runtime: .NET. It’s multiplatform and open-sourced. We have the classic one, we call it: .NET Framework, which you probably already know. And the new one was called “.NET Core.” But now, it’s simply “.NET” Yes, naming is hard.

#3. Speaking of the language, the main language features are still there. C# is an imperative side-effect-heavy language. But with every release, it’s adopting more features from functional languages, like lambda expressions, pattern matching, and records. (See #7)

#4. There’s excellent support for asynchronous methods with two keywords: async and await.

#5. There’s a whole lot of new small language features. Apart from async/await (from C# 5.0) we haven’t had a release with a single major feature. Most of the new features are syntactic sugar for existing features.

#6. There’s a new feature you shouldn’t miss: nullable references. It should have been called: “non-nullable references” instead. Do you remember nullable primitive types? Like int?? We allow a type to contain null by appending a ? when declaring it. We can do the same for classes. So Movie? m can be null, but Movie m can’t. And the compiler warns us when we’re trying to use a reference that might be null. Awesome!

#7. I compiled a list of C# features we should know about. Those are the features I like and have used the most.

#8. A “Hello, world” is now literally one line of code. We don’t need to declare a class and a Main method for console applications. Just write your code directly in a file like in a scripting language.

#9. I stopped expanding that list with the most recent features. C# is getting bloated with more features that are making the language less consistent. I don’t like that.

#10. The same way we have a new runtime, we have a new web framework: ASP.NET Core. It was a full rewrite of the classic ASP.NET. There’s no Global.asax, web.config, or files listed on csproj files. If you knew and used the old ASP.NET, I wrote a guide with the difference between the two. With .NET, we have way better tooling like a command line interface to create projects.

#11. Well, there’s Xamarin, MAUI, Blazor… But unless you’re planning to do front-end work, you don’t need to worry about them. Microsoft is still trying to find, create, and establish a golden hammer for the front-end side.

Sure, I’m missing a lot of other things. But you’re safe catching up on those.

TIL: How To Ignore Unmapped Fields in the Destination Type With AutoMapper

Another day working with AutoMapper, another day with an edge case.

Let’s say we have class CreateMovieRequest with three properties: name, release year, and another property I don’t want to map, for some reason. And a destination class, Movie, with more properties apart from those three and names using a prefix.

Simply trying to use CreateMap<CreateMovieRequest, Movie>() won’t work. AutoMapper will warn you about unmapped properties in the destination type. “Unmapped members were found. Review the types and members below.”

Here’s how to map two classes ignoring the unmapped properties in the destination type and with prefixes:

using AutoMapper;

namespace TestProject1;

[TestClass]
public class IHateYouWilWheaton
{
    public class CreateMovieRequest
    {
        public string Name { get; set; }
        public int ReleaseYear { get; set; }
        public string IDontWantThisOneMapped { get; set; }
    }

    public class Movie
    {
        // These two aren't mapped from the source
        public int Id { get; set; }
        public int DurationInMinutes { get; set; }
        // These two have the 'Movie' prefix
        public string MovieName { get; set; }
        public int MovieReleaseYear { get; set; }
    }

    [TestMethod]
    public void IgnoringNotMappedProps()
    {
        var config = new MapperConfiguration(cfg =>
        {
            // Let's keep it here, shall we?
            cfg.RecognizeDestinationPrefixes("Movie");
            //  ^^^^^
            // To avoid explicitly mapping the fields because of the prefix

            // Before:
            // cfg.CreateMap<CreateMovieRequest, Movie>();
            //     ^^^^^
            // AutoMapper.AutoMapperConfigurationException: 
            // Unmapped members were found. Review the types and members below.
            // ...
            // Unmapped properties:
            // Id
            // MovieName
            // DurationInMinutes
            //
            //
            // After:
            cfg.CreateMap<CreateMovieRequest, Movie>(MemberList.Source)
                //                                   ^^^^^
                // To validate unmapped properties on the source type
                // Possible options: Source, Destination, and None			
                .ForSourceMember(src => src.IDontWantThisOneMapped, opt => opt.DoNotValidate());
                // ^^^^^
                // Since I don't want this one mapped for some reason
        });
        config.AssertConfigurationIsValid();

        var source = new CreateMovieRequest
        {
            Name = "Space Odyssey",
            ReleaseYear = 1968,
            IDontWantThisOneMapped = "Ok, if you say so"
        };

        var mapper = config.CreateMapper();
        var destination = mapper.Map<Movie>(source);

        Assert.AreEqual(destination.MovieName, source.Name);
        Assert.AreEqual(destination.MovieReleaseYear, source.ReleaseYear);
    }
}

The trick for prefixes is RecognizeDestinationPrefixes() and the one for warnings is passing any of Source, Destination, or None as a parameter to Map().

And to ignore an “incoming” property, ForSourceMember(), with DoNotValidate().

Et voilà!

You Don't Need a Published Book to Call Yourself a Writer

Is it a best-selling book? A Pulitzer-winning novel? A large newsletter? More than 1,000 articles published anywhere online?

When can we call ourselves “writers”?

I have to confess I’ve been reluctant to use “digital writer” or “technical writer” or “writer” as a title anywhere online, even though I’ve been writing in my small corner of the Internet since 2018.

But recently, watching this interview with Devon Eriksen on YouTube, I changed my mind about using the title “writer.” He says:

If you’ve written and you’ve gotten paid for it. You’re a writer.

After hearing that, I wasn’t afraid to change my tagline on social media and here on my blog.

Definitely, someone has asked me to write something and paid me for that. That was by pure accident or luck, thanks to sharing the tutorials I write here on my blog. That’s how I made my first internet money.

You don’t need to publish a book. Well, publishing a book isn’t that hard these days. James Altucher has a strategy to publish a book in 30 days.

You don’t need to write and publish a book to call yourself a writer. Find one person that will pay to write and start calling yourself a “writer.” I’m a writer. And you?

TIL: Configure Default Values for Nullable Columns With Default Constraints in EntityFramework Core

TL;DR: For nullable columns with default constraints, you have to tell EntityFramework the default value of the mapping property via C#. Otherwise, when you create a new record, it will have NULL instead of the default value in the database. You’re welcome! Bye!

Let’s create a dummy table with one nullable column but with a defualt constraint

Let’s create a new Movies database with one table called Movies, with an Id, a name, and a director name as optional, but with a default value. Like this,

CREATE DATABASE Movies;
GO
USE Movies;
GO
CREATE TABLE Movies (
    Id INT PRIMARY KEY IDENTITY(1,1),
    Name NVARCHAR(100) NOT NULL,
    DirectorName NVARCHAR(100) DEFAULT 'ThisIsADefaultValue'
);
GO

Let’s create a new record (without the nullable column) and read it back

Now to prove a point, let’s use EntityFramework Core to insert a new movie without passing a director name and read it back.

What will be the value of DirectorName once we read it back? Null? The value inside the default constraint? Make your bets!

Here we go,

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace TestProject1;

[TestClass]
public class EntityFrameworkAndDefaults
{
    [TestMethod]
    public void TestInsertAndReadMovie()
    {
        const string connectionString = $"Server=(localdb)\\MSSQLLocalDB;Database=Movies;Trusted_Connection=True;";

        var dbContextOptions = new DbContextOptionsBuilder<MoviesContext>()
                .UseSqlServer(connectionString)
                .Options;

        using (var context = new MoviesContext(dbContextOptions))
        {
            var inception = new Movie
            {
                Name = "Inception"
                // No director name here...
                // ^^^^^
            };
            context.Movies.Add(inception);
            context.SaveChanges();
        }

        using (var context = new MoviesContext(dbContextOptions))
        {
            var movie = context.Movies.FirstOrDefault(m => m.Name == "Inception");

            Assert.IsNotNull(movie);
            Assert.AreEqual("ThisIsADefaultValue", movie.DirectorName);
            //     ^^^^^^^^
            //     Assert.AreEqual failed. Expected:<ThisIsADefaultValue>. Actual:<(null)>. 
            //
            // Whaaaaat!
        }
    }
}

public class MoviesContext : DbContext
{
    public DbSet<Movie> Movies { get; set; }

    public MoviesContext(DbContextOptions<MoviesContext> options)
        : base(options)
    {
    }
}

public class Movie
{
    public int Id { get; set; }
    public required string Name { get; set; }
    public string? DirectorName { get; set; }
}

Ok, to my surprise that test fails. movie.DirectorName isn’t "ThisIsADefaultValue". It’s null.

I was expecting to see the value from the default constraint in the database. But, no. Wah, wah, wah, Wahhhhhhh…

Here you have it EntityFramework. Can I get my default value now?

We have to tell EntityFramework Core the default value of our column, like this,

public class MoviesContext : DbContext
{
    public DbSet<Movie> Movies { get; set; }

    public MoviesContext(DbContextOptions<MoviesContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // For simplicity, let keep it here...
        modelBuilder.Entity<Movie>(entity =>
        {
            entity
                .Property(e => e.DirectorName)
                .HasDefaultValueSql("ThisIsADefaultValue");
                // ^^^^^
                // Here you have it EntityFramework Core
                // Can I get my default value now?
        });
    }
}

This behavior only adds up to my love and hate relationship with EntityFramework Core.

Et voilà!

Starting out or already on the coding journey? Join my free 7-day email course to refactor your software engineering career now–I distill 10+ years of career lessons into 7 short emails.

AI Is The Future And We, Coders, Have To Adapt

AI is here to stay. We can’t deny that.

One day, will AI become self-conscious and try to exterminate us all? Who knows. That’s another discussion.

But we, as coders, have to adapt, as we always have, and adopt AI.

Today, I had a conversation with a group of ex-coworkers.

One of the members shared he was asked to finish almost an entire working app as part of a take-home coding challenge for an interview process at a startup. He was given only 5 hours. We all agreed that 5 hours wasn’t enough.

Someone else said he should have used AI. But he refused to use AI.

Maybe the intention behind that take-home challenge was to use AI and come up with a decent working prototype quickly. Who knows? Companies aren’t that clear with their interview process. And hiring is broken.

But we can’t refuse to use AI.

AI is here to make us more efficient.

What used to take days, now it only takes hours or minutes. By pressing a button, we compile thousands of source files in just seconds. Refusing to use AI is like refusing to use calculators or spreadsheets for accounting. It’s like refusing to travel by plane or drive a car.

These days when efficiency and productivity are rewarded, AI is a game-changer.

We can’t pretend to keep using punch cards and computers that fill an entire room. AI is the future. And we have to adapt. Coding in 2034 will change. But, one thing is certain: we can’t use AI to replace our thinking. We should be the pilots, and AI as our copilot.

Refactor your coding career with my free 7-day email course. In just 7 emails, you’ll get 10+ years of career lessons to avoid costly career mistakes.