Work with SQL Data with Fewer Lines of Code Using Dynamic C#

If you need to query a SQL database and work with the data quickly you can use dynamic C# to write less boilerplate code.

For example, if you wanted to query a Customer table (or a more complex joined query, etc.) you could start by writing a class to represent the fields in the Customer table and then use a library/ORM to connect to the SQL database, and perform the query, and get the results populated into Customer objects.

Sometimes you will want this more detailed approach if you dealing with more complex data, perhaps with joined tables/foreign keys, etc.

Sometimes however you just want to query some data and do something with it (display it, report it, etc.) – in this case you’ve got a lot of wasted effort creating classes to represent rows in tables. You can use dynamic C# in conjunction with a library such as Dapper to remove the need to create these “boilerplate” classes.

The following code shows how to do this in just a few lines of code:

using var cn = new SqlConnection(connectionString);

IEnumerable<dynamic> customers = cn.Query("SELECT TOP 10 * FROM CUSTOMER");

foreach (dynamic customer in customers)
{
    WriteLine($"{customer.FirstName} {customer.SecondName} {customer.Height} {customer.Age}");
}

The cn.Query method comes from Dapper and returns dynamic objects, each object is a row from the table/query.

To access a field from the database you simply reference it on the dynamic object such as customer.FirstName – here FirstName is a field in the CUSTOMER table.

Notice we didn’t need to go and spend the extra time to code a Customer class just to be able to query the database.

There’s a lot more to dynamic C# however and to learn more check out my Dynamic Programming in C# 10 Pluralsight course and even start watching with a free trial.

SHARE:

Support Multiple Versions of .NET From A Single Class Library (.NET multi-targeting)

.NET implementations come in a few different versions or “flavors” including .NE Framework, .NET Core, and the evolution of .NET Core into .NET 5+. There’s also .NET Standard which is not an implementation but a specification of API that may (or may not) be implemented in the different “flavors”.

If you’re developing a library (whether this be a public/open source library on NuGet or a library for internal use in your company) you may need to support multiple versions of .NET to allow your library to be consumable by as many people as possible.

You could do this by creating multiple class library projects with each one targeting a different version of .NET. Each of these projects would compile to a DLL file that will support the version of .NET chosen for the class library project. You could use shared/linked source code files to have the same source code compiled in each of the different class library projects – this can become a bit messy and cumbersome.

An alternative is to have a single class library project and make use of multi-targeting.

What is Multi-Targeting in .NET?

Multi-targeting is the ability to compile the same source code multiple times, each time compiling for a different version of .NET.

Each target will result in a separate DLL being produced for a class library project for example.

You can then take all the different DLLs and package them up in  a single NuGet package that can then be installed into projects with different .NET versions.

How to Create a Multi-Targeted Class Library in .NET

If you create a class library project in either Visual Studio or using the .NET CLI, the project XML file will contain the following element:<TargetFramework>net6.0</TargetFramework>

This element describes what version of .NET the compiled DLL will support (or “target”) in the preceding example this is .NET 6.0.

The “net6.0” is a target framework moniker (TFM). There are many TFMs that describe the different version of .NET.

You can specify multiple TFMs to enable multi-targeting. To do this you also change to a TargetFrameworks element. For example to target both .NET 6 and .NET Standard 2.0 you would have: <TargetFrameworks>net6.0;netstandard2.0;</TargetFrameworks>

Now when you build the project you will have 2 DLLs – one for .NET 6 and one for .NET standard.

Depending on the targets you add you may also need to add some conditional items to the project file if certain newer features are not supported in older versions such as nullable reference types. Once you have multi-targeting set up you can even compile different portions of code for the different platforms using condition compiler directives to take account of differences/features in the .NET APIs. You will also need to decide how to handle features that are not available in older versions of .NET and perhaps provide a way for consumers of your library to query whether a specific feature is available in a targeted version.To learn how to do all of these things and how to create/version/package your class libraries check out my Class Libraries in C# 10 Pluralsight course. You can start watching with a free trial.

SHARE:

Working with Files in C# 10 Course

My latest Pluralsight training course was just published and is an update from the previous version to now use .NET 6 and C# 10.

“Reading and writing data is central to many .NET applications, but it can be difficult to know which approach to take. In this course, Working with Files in C# 10, you’ll gain the ability to read and write data and manage files. First, you’ll explore how to manage files and directories stored on disk. Next, you’ll discover how to monitor and respond to changes in the file system. Finally, you’ll learn how to read, process, and write data in text, binary, and CSV formats. When you’re finished with this course, you’ll have the skills and knowledge of C# files and streams needed to read, write, and process data in your C# applications.”

You can start watching today with a free trial!

SHARE:

What’s New in C# 10: Create Constants Using String Interpolation

This is part of a series on the new features introduced with C# 10.

Prior to C# 10 if you wanted to create a const that was made up from other constants you had to add the string fragments togeter, for example (C# 9):

const string SupportedCurrencyCodes = "GPB, USD, AUD";
const string Copyright = "Jason Roberts";
const string TwitterSupportAccount = "@RobertsJason";

const string AboutMessage = "Currency codes supported '"
                                    + SupportedCurrencyCodes
                                    + "'. Support via Twitter: " + TwitterSupportAccount
                                    + ". Copyright 2022 " + Copyright + ".";

This is a bit messy and hard to read.

From C# 10 you can create a constant using string interpolation as you would do with a normal variable, for example in C# 10:

const string SupportedCurrencyCodes = "GPB, USD, AUD";
const string Copyright = "Jason Roberts";
const string TwitterSupportAccount = "@RobertsJason";

const string AboutMessage = $"Currency codes supported '{SupportedCurrencyCodes}'. Support via Twitter: {TwitterSupportAccount}. Copyright 2022 {Copyright}.";

Even thought the line is a bit longer (horizontally) it is easier to understand the entire string. One caveat with this is that all the values in the braces have to be string contants - you can’t use number constant for example in a const interpolated string.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

New Pluralsight Course: Error Handling in C# 10

My newest Pluralsight course Error Handling in C# 10 was just released.

This is an updated course of my previous error handling courses to bring the demo code up to C# 10, .NET 6, Visual Studio 2022.

Course description: “Missing or incorrectly implemented error handling code can lead to data corruption, unnecessary crashes, annoyed end-users, out-of-hours support calls, and hard to maintain code. In this course, Error Handling in C# 10, you’ll learn to correctly handle runtime errors in your C# applications. First, you’ll explore what exceptions are and why we use them. Next, you’ll discover how to respond to errors that occur in your applications at runtime. Finally, you’ll learn how to throw exceptions in your own code and even how to define your own custom exception classes. When you’re finished with this course, you’ll have the skills and knowledge of C# exception handling needed to handle runtime errors in your C# applications.”

You can start watching today with a free trial.

SHARE:

What’s New in C# 10: Simplify Nested Property Pattern Code

This is part of a series on the new features introduced with C# 10.

Pattern matching in C# was first introduced in C# 7 and has been added to in later versions.

C# 8 added property pattern matching to allow you to match on the values of properties and fields. Prior to C# 10, property pattern matching with simple (non-nested) types was fine but if the thing you were matching was in a nested property the syntax was slightly clumsy:

public record CurrencyExchangeRate(string SourceCurrencyCode,
                                   string DestinationCurrencyCode,
                                   decimal ExchangeRate);

public record Trade(int CustomerId, CurrencyExchangeRate ExchangeRate);

In the preceding code we have a Trade that has a nested CurrencyExchangeRate, in C# 9 if we wanted to match on this nested CurrencyExchangeRate such as the SourceCurrencyCode, we’d have to use the following syntax:

public static bool IsRelatedToAustralia(Trade trade) =>
    trade is { ExchangeRate: { SourceCurrencyCode: "AUD" } } or
             { ExchangeRate: { DestinationCurrencyCode: "AUD" } };

Notice the extra nested {} to access the nested currency codes.

From C# 10 you can access nested properties directly which makes the code a little more readable, for example:

static bool IsRelatedToAustralia(Trade trade) =>
    trade is { ExchangeRate.SourceCurrencyCode: "AUD" } or
             { ExchangeRate.DestinationCurrencyCode: "AUD" };

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

What’s New in C# 10: Value Type Records

This is part of a series on the new features introduced with C# 10.

In a previous post I wrote about records in C# 9.Prior to C# 10 record types were reference types. In C# 10 you can now declare records as value types.

You declare a value record type by adding the struct keyword.

You may also add the readonly modifier if you want to create an immutable value type:

// struct modifier - this will create a value type (mutable)
public record struct CurrencyExchangeRate3(string SourceCurrencyCode,
                                                    string DestinationCurrencyCode,
                                                    decimal ExchangeRate);

// struct modifier (and readonly) - this will create a value type (immutable)
public readonly record struct CurrencyExchangeRate4(string SourceCurrencyCode,
                                                string DestinationCurrencyCode,
                                                decimal ExchangeRate);

If you don’t specify the struct modifier you will get a reference record. If you want to you can add the class modifier if you think it will make the code more readable:

// No modifier - this will be a reference type record
public record CurrencyExchangeRate1(string SourceCurrencyCode,
                                    string DestinationCurrencyCode,
                                    decimal ExchangeRate);


// Explicit class modifier - this will also be a reference type record
public record class CurrencyExchangeRate2(string SourceCurrencyCode,
                                            string DestinationCurrencyCode,
                                            decimal ExchangeRate);

All of the above examples use the positional syntax for defining the record properties.

Record Struct Equality

The default equality for record structs is the same for non-record structs:2 objects will be equal if they are both the same type and have the same values.

There is one key difference and that is how the default equality is implemented. With normal non-record structs, to determine equality reflection is used behind the scenes which can be slow. With record structs however reflection is not used, the equality code is synthesized by the compiler.

If we use a tool like DotPeek to decompile the Equals method we get the following:

public bool Equals(CurrencyExchangeRate3 other)
{
  // ISSUE: reference to a compiler-generated field
  // ISSUE: reference to a compiler-generated field
  // ISSUE: reference to a compiler-generated field
  // ISSUE: reference to a compiler-generated field
  if (EqualityComparer<string>.Default.Equals(this.\u003CSourceCurrencyCode\u003Ek__BackingField, other.\u003CSourceCurrencyCode\u003Ek__BackingField) && EqualityComparer<string>.Default.Equals(this.\u003CDestinationCurrencyCode\u003Ek__BackingField, other.\u003CDestinationCurrencyCode\u003Ek__BackingField))
  {
    // ISSUE: reference to a compiler-generated field
    // ISSUE: reference to a compiler-generated field
    return EqualityComparer<Decimal>.Default.Equals(this.\u003CExchangeRate\u003Ek__BackingField, other.\u003CExchangeRate\u003Ek__BackingField);
  }
  return false;
}

Notice the preceding code is not using reflection to determine if the data items are equal. This means is some situations a record struct may perform better that a standard struct. Check out this related article on struct performance I wrote.

Another difference between record class and record struct is that in class records you can write a custom copy constructor, for example one that always set the exchange rate to 0:

public record class CurrencyExchangeRate5(string SourceCurrencyCode,
                                          string DestinationCurrencyCode,
                                          decimal ExchangeRate)
    {
        // Copy constructor
        protected CurrencyExchangeRate5(CurrencyExchangeRate5 previous)
        {
            SourceCurrencyCode = previous.SourceCurrencyCode;
            DestinationCurrencyCode = previous.DestinationCurrencyCode;
            ExchangeRate = 0;
        }
    }
}

Now if you wrote: CurrencyExchangeRate6 f2 = f1 with { SourceCurrencyCode = "xyz" }; f2 would have it’s currency set to 0.

If you tried this with a record struct, the custom copy constructor won’t be called.

SHARE:

What’s New in C# 10: Take Control of Interpolated String Handling

This is part of a series on the new features introduced with C# 10.

In C# you can create an interpolated string such as: $"{DateTime.Now}: starting..."

The compiler will transform this to a single string instance using a call to String.Format or String.Concat.

Starting with C# 10 you can override this behaviour if you want more control such as:

  • Not interpolating the sting for performance reasons if it won’t be used
  • Limiting the length of resulting interpolated strings
  • Enforcing custom formatting of interpolated strings
  • Etc.

Take the following simple logging class:

// Simplified implementation
public static class SimpleConsoleLogger
{
    public static bool IsLoggingEnabled { get; set; }

    public static void Log(string message)
    {
        if (IsLoggingEnabled)
        {
            Console.WriteLine(message);
        }            
    }
}

We could call this as follows:

SimpleConsoleLogger.IsLoggingEnabled = true;
SimpleConsoleLogger.Log($"{DateTime.Now}: starting...");
SimpleConsoleLogger.IsLoggingEnabled = false;
SimpleConsoleLogger.Log($"{DateTime.Now}: ending...");

The second call (SimpleConsoleLogger.Log($"{DateTime.Now}: ending...");) won’t output a log message because IsLoggingEnabled is false, however the interpolation of the string $"{DateTime.Now}: ending..." will still take place.

Ideally if logging is not enabled we would not even want to bother interpolating the string. This could improve the performance of the application if logging was identified as a problem.

We can do this by taking control of when (or if) an interpolated string is processed by:

  • Applying the System.Runtime.CompilerServices.InterpolatedStringHandler attribute to a custom handler
  • Creating a constructor with int parameters: (int literalLength, int formattedCount)
  • Adding a public AppendLiteral method
  • Adding a generic public AppendFormatted method

Within this custom handler you can decide how to turn the interpolated string into a single string instance, for example by using a StringBuilder. In the code you could also enforce any custom formatting/length restrictions that are required.

The following code shows a simple example using a StringBuilder:

using System.Runtime.CompilerServices;
using System.Text;

namespace ConsoleApp1
{
    [InterpolatedStringHandler]
    public ref struct LogMessageInterpolatedStringHandler
    {
        readonly StringBuilder logMessageStringbuilder;
     
        public LogMessageInterpolatedStringHandler(int literalLength, int formattedCount)
        {
            logMessageStringbuilder = new StringBuilder(literalLength);
        }

        public void AppendLiteral(string s)
        {
            // For demo purposes
            Console.WriteLine($"AppendLiteral called for '{s}'");

            logMessageStringbuilder.Append(s);
        }

        public void AppendFormatted<T>(T t)
        {
            // For demo purposes
            Console.WriteLine($"AppendFormatted called for '{t}'");

            logMessageStringbuilder.Append(t?.ToString());
        }

        public string BuildMessage() => logMessageStringbuilder.ToString();
    }
}

To make the logging class use this we can add another overload of the log method that instead of a string takes a LogMessageInterpolatedStringHandler:

public static void Log(LogMessageInterpolatedStringHandler logMessageBuilder)
{
    if (IsLoggingEnabled)
    {
        Console.WriteLine("...interpolating message because logging is enabled...");
        Console.WriteLine(logMessageBuilder.BuildMessage());
    }
    else
    {
        Console.WriteLine("...NOT interpolating message because logging is disabled...");
    }
}

Now if Log is called with a non-interpolated string like "Hello - this is not an interpolated string" the original log method will be used.

If the Log method is called with an interpolated string, the custom handler will be invoked (if we choose to invoke it). For example, if logging is disabled we don’t even need to call the handler to build the final log message:

public static void Log(LogMessageInterpolatedStringHandler logMessageBuilder)
{
    if (IsLoggingEnabled)
    {
        Console.WriteLine("...interpolating message because logging is enabled...");
        Console.WriteLine(logMessageBuilder.BuildMessage());
    }
    else
    {
        Console.WriteLine("...NOT interpolating message because logging is disabled...");
    }
}

The final code looks like this:

namespace ConsoleApp1
{
    // Simplified implementation
    public static class SimpleConsoleLogger
    {
        public static bool IsLoggingEnabled { get; set; }

        public static void Log(string message)
        {
            Console.WriteLine("...may have already interpolated the message...");

            if (IsLoggingEnabled)
            {
                Console.WriteLine(message);
            }            
        }

        public static void Log(LogMessageInterpolatedStringHandler logMessageBuilder)
        {
            if (IsLoggingEnabled)
            {
                Console.WriteLine("...interpolating message because logging is enabled...");
                Console.WriteLine(logMessageBuilder.BuildMessage());
            }
            else
            {
                Console.WriteLine("...NOT interpolating message because logging is disabled...");
            }
        }

    }
}


using System.Runtime.CompilerServices;
using System.Text;

namespace ConsoleApp1
{
    [InterpolatedStringHandler]
    public ref struct LogMessageInterpolatedStringHandler
    {
        readonly StringBuilder logMessageStringbuilder;
     
        public LogMessageInterpolatedStringHandler(int literalLength, int formattedCount)
        {
            logMessageStringbuilder = new StringBuilder(literalLength);
        }

        public void AppendLiteral(string s)
        {
            // For demo purposes
            Console.WriteLine($"AppendLiteral called for '{s}'");

            logMessageStringbuilder.Append(s);
        }

        public void AppendFormatted<T>(T t)
        {
            // For demo purposes
            Console.WriteLine($"AppendFormatted called for '{t}'");

            logMessageStringbuilder.Append(t?.ToString());
        }

        public string BuildMessage() => logMessageStringbuilder.ToString();
    }
}


SimpleConsoleLogger.IsLoggingEnabled = true;
SimpleConsoleLogger.Log($"{DateTime.Now}: starting...");
SimpleConsoleLogger.Log("Hello - this is not an interpolated string");
SimpleConsoleLogger.IsLoggingEnabled = false;
SimpleConsoleLogger.Log($"{DateTime.Now}: ending...");

And if we run this:

AppendFormatted called for '30/11/2021 11:52:02 AM'
AppendLiteral called for ': starting...'
...interpolating message because logging is enabled...
30/11/2021 11:52:02 AM: starting...
...may have already interpolated the message...
Hello - this is not an interpolated string
AppendFormatted called for '30/11/2021 11:52:02 AM'
AppendLiteral called for ': ending...'
...NOT interpolating message because logging is disabled...

Now the performance overhead of interpolating the strings will only happen if logging is enabled.

There’s a much more in-depth tutorial in the docs.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

What’s New in C# 10: Easier Lambda Expressions

This is part of a series on the new features introduced with C# 10.

Prior to C# 10, working with lambda expressions required a bit more code to be written, for example to explicitly define the delegate type such as Action<T> or Func<T>:

Action<string, ConsoleColor> writeWithColor = (string s, ConsoleColor color) =>
{
    var originalColor = Console.ForegroundColor;
    Console.ForegroundColor = color;
    Console.WriteLine(s);
    Console.ForegroundColor = originalColor;
};

Func<string, string> upper = (string s) => s.ToUpperInvariant();

writeWithColor("Hello", ConsoleColor.Cyan);
Console.WriteLine(upper("This should be default color"));
writeWithColor("Bye", ConsoleColor.Yellow);

Console.ReadLine();

Notice in the preceding code the lambda statement writeWithColor and the lambda expression upper both need explicit delegate types: Action<string, ConsoleColor> and Func<string, string>

From C# 10 we can make use of the new feature of “natural” lambda expression types.

This “natural type” is inferred by the compiler when it can, this means in C# we could just use var: var writeWithColor = (string s, ConsoleColor color) => etc. and var upper = (string s) => s.ToUpperInvariant();

This natural type inference will not always be possible, for example when you haven’t defined lambda parameter types like: var upper = (s) => s.ToUpperInvariant(); If you tried to compile this line of code you would get: Error    CS8917    The delegate type could not be inferred.

From C# 10, you can specify an explicit return type for a lambda expression where the compiler can’t work it out for you. You add the return type before the lambda parenthesis:

//Error CS8917 The delegate type could not be inferred
var createException = (bool b) => b ? new ArgumentNullException() : new DivideByZeroException();

// No error
var createException = Exception (bool b) => b ? new ArgumentNullException() : new DivideByZeroException();

You can also sometimes benefit from natural types for method groups:

// C#9
Func getUserInput = Console.ReadLine;
Action tellUser = (string s) => Console.WriteLine(s);
Func waitForEnter = Console.ReadLine;

tellUser("Please enter name");
var name = getUserInput();
tellUser($"Your name is {name}");
waitForEnter();

From C# 10 we could just use var:

// C#10
var getUserInput = Console.ReadLine;
var tellUser = (string s) => Console.WriteLine(s);
var waitForEnter = Console.ReadLine;

tellUser("Please enter name");
var name = getUserInput();
tellUser($"Your name is {name}");
waitForEnter();

You can’t however write: var write = Console.Write; because the Write method has multiple overloads so the compiler doesn’t know which one to choose.

SHARE:

What’s New in C# 10: New Possibilities for Validation and Logging Methods

This is part of a series on the new features introduced with C# 10.

From C# 10 we can make use of the [CallerArgumentExpression] attribute.

This attribute can be applied to a parameter to get information about another specified parameter in the method.

Take the following method as an example:

static bool ValidInput(string? inputFromUser,
                string inputDataName,
                bool validationCondition,
                out string? validationErrorMessage,
                [CallerArgumentExpression("validationCondition")] string? validationConditionText = null)
{
    if (validationCondition)
    {
        validationErrorMessage = null;
        return true;
    }

    validationErrorMessage = $"input '{inputFromUser ?? "null"}' from user for {inputDataName} is invalid because '{validationConditionText}'";
    return false;
}

In this method the validationConditionText argument has the [CallerArgumentExpression] applied.

When you use the [CallerArgumentExpression] attribute you need to supply a single constructor parameter. This is a string parameter that specifies which of the other parameters we want to capture information about. In this case it’s the bool validationCondition parameter.

We could make use of this method in a console application:

using System.Runtime.CompilerServices;
using static System.Console;

WriteLine("Please enter your user name");
string? userName = ReadLine();

WriteLine("Please enter your age");
string? age = ReadLine();

string? validationErrorMessage;

if (!ValidInput(userName,
                inputDataName: "user name",
                validationCondition: userName is not null,
                validationErrorMessage: out validationErrorMessage))
{
    WriteLine(validationErrorMessage);
}

if (!ValidInput(inputFromUser: age,
                inputDataName: "age",
                validationCondition: age is not null && int.TryParse(age, out _),
                validationErrorMessage: out validationErrorMessage))
{
    WriteLine(validationErrorMessage);
}

ReadLine();

Every time we call the ValidInput method, we pass a Boolean expression that needs to be satisfied for the input to be recognized as valid, for example: userName is not null.

If we ran the console app and entered a null for user name and some non numeric input for age:

Please enter your user name
^Z
Please enter your age
aaa
input 'null' from user for user name is invalid because 'userName is not null'
input 'aaa' from user for age is invalid because 'age is not null && int.TryParse(age, out _)'

Notice the two validation error messages output contain the boolean expression used in the source code: userName is not null and age is not null && int.TryParse(age, out _).

The [CallerArgumentExpression] attribute pulls out those expressions and lets us access them as strings to be used at runtime.

This kind of user validation is not the primary intended use case for this attribute as telling an end user 'age is not null && int.TryParse(age, out _)' is not very helpful or user friendly, however the example above illustrates the possibilities. This approach could still be used with a more generic error message given to the user and a more detailed one written to logs/traces. The Microsoft documentation states: “Diagnostic libraries may want to provide more details about the expressions passed to arguments. By providing the expression that triggered the diagnostic, in addition to the parameter name, developers have more details about the condition that triggered the diagnostic. That extra information makes it easier to fix.”

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE: