Microsoft Entity Framework Core 8 samples
Tags: database, csharp, dotnetframework
Published: 2023-12-02 22:15:52

Microsoft release the latest version of Entity Framework Core in November 2023 which has substantially changed from Entity Framework Core 7.

To see what is new check out Microsoft documentation [what's New in EF Core 8](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew) and [breaking changes in EF Core 8](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/breaking-changes) documentation.

Each release of Entity Framework Core the Microsoft engineers provide [code samples](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core). Although these code samples are great, they are not structured for learning and use in a developer’s projects. Also, there are many code samples that may not be useful to the average developer. With that said I took Microsoft code samples and structured the code in a fashion suitable for the average developer to learn from. 

## Important

There are many project were for the most part the project name signifies the purpose of the project. 

{% cta https://github.com/karenpayneoregon/ef-code-8-samples %} GitHub Source code {% endcta %}

## DateOnly TimeOnly

Something been long awaited is support for DateOnly and TimeOnly. Microsoft provides some samples for these yet, again not structured for a developer to learn. So for this a modified version of Microsoft NorthWind database has been provided, the Orders table originally defined dates as DateTime yet none of the dates had time values so they now are defined as dates.

When reverse engineering with [EF Power Tools](https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools) dates are defined as DateOnly and time as TimeOnly.

Partial Orders model show DateOnly.

```csharp
public partial class Orders
{
    public int OrderID { get; set; }
    public DateOnly OrderDate { get; set; }
    public DateOnly RequiredDate { get; set; }
    public DateOnly ShippedDate { get; set; }
    public DateOnly DeliveredDate { get; set; } 
}
```
## TimeOnly
Check out the project TimeBetweenApp


![IsBetween method for TimeOnly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vg93mjsn7ksogzm2c2xj.png)

## Connections and logging (Console project)

To enhance learning, included is a class project for uniform connects read from appsettings.json and logging to a file.

Example implementation in a DbContext where ConnectionString() reads the main application's appsettings.json for the connection string.

```csharp
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWithFileLoggingOnly(ConnectionString());
```

Let's look under the covers for ConfigureWithFileLoggingOnly. 

1. Configure for SQL-Server with the passed connection string
1. Setup for logging to a new file under the debug folder where each day has a new folder and log file.

```csharp
public static void ConfigureWithFileLoggingOnly(this DbContextOptionsBuilder optionsBuilder, string connectionString)
{
    optionsBuilder
        .UseSqlServer(connectionString)
        .LogTo(new DbContextToFileLogger().Log,
            new[]
            {
                DbLoggerCategory.Database.Command.Name
            },
            LogLevel.Information);
}
```

Class for logging.

```csharp
public class DbContextToFileLogger
{
    /// <summary>
    /// Log file name
    /// </summary>
    private readonly string _fileName = 
        Path.Combine(AppDomain.CurrentDomain.BaseDirectory, 
            "LogFiles", $"{Now.Year}-{Now.Month}-{Now.Day}", 
            "EF_Log.txt");

    /// <summary>
    /// Use to override log file name and path, file must exists
    /// </summary>
    /// <param name="fileName"></param>
    public DbContextToFileLogger(string fileName)
    {
        _fileName = fileName;
    }

    /// <summary>
    /// Setup to use default file name for logging
    /// </summary>
    public DbContextToFileLogger()
    {
        var dir = Path.GetDirectoryName(_fileName);
        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }
        if (!File.Exists(_fileName))
        {
            using (StreamWriter w = File.AppendText(_fileName)) ;
        }
    }

    /// <summary>
    /// append message to the existing stream
    /// </summary>
    /// <param name="message"></param>
    [DebuggerStepThrough]
    public void Log(string message)
    {

        if (!File.Exists(_fileName))
        {
            File.CreateText(_fileName).Close();
        }

        StreamWriter streamWriter = new(_fileName, true);

        streamWriter.WriteLine(message);

        streamWriter.WriteLine(new string('-', 40));

        streamWriter.Flush();
        streamWriter.Close();
    }
}
```

To ensure there is a log folder, each project project file has the following to create the log folder if not present.

```xml
<Target Name="MakeMyDir" AfterTargets="Build">
    <MakeDir Directories="$(OutDir)LogFiles" />
</Target>
```

### ASP.NET Core

These projects use dependency injection for connections and have no logging although they are ready to log to the console via [SeriLog](https://www.nuget.org/packages/Serilog/3.1.2-dev-02097) setup in Program.cs. 

```csharp
public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddRazorPages();
        builder.Services.AddDbContextPool<ShadowContext>(options =>
            options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
        SetupLogging.Development();
```

## Benchmarkdotnet

Several of the projects are rigged up to perform benchmarks. Example in BookApp. Note in each method a method is commented out which has no baring on benchmarking but without benchmarking the developer can see the output of each operation performed.

```csharp
[MemoryDiagnoser]
[RankColumn, 
 MinColumn, 
 MaxColumn, 
 Q1Column, 
 Q3Column, 
 AllStatisticsColumn]
[JsonExporterAttribute.Full]
[GcServer(true)]
public partial class BookOperations
{
    [Benchmark]
    public async Task GroupBy()
    {
        await using var context = new BookContext();
        var books = await context.Book.ToListAsync();

        List<IGrouping<int, Book>> results = books
            .GroupBy(book => book.Price < 10 ? 10 : (book.Price < 20 ? 20 : 30))
            .OrderBy(grouping => grouping.Key)
            .ToList();

        //GroupNoSwitchOutput(results);
    }
    

    [Benchmark(Baseline = true)]
    public async Task Switch()
    {
        await using var context = new BookContext();
        var books = await context.Book.ToListAsync();

        List<GroupSwitch> results = books
            .GroupBy(book => book.Price switch
            {
                <= 10 => "Cheap",
                > 10 and <= 20 => "Medium",
                _ => "Expensive"
            })
            .Select(g => new GroupSwitch(g.Key, g.ToList()))
            .OrderBy(ca => ca.Category)
            .ToList();

        //GroupWithSwitchOutput(results);
    }
}
```

## Source code

The following [GitHub repository](https://github.com/karenpayneoregon/ef-code-8-samples) contains source code for this article. There will be more code samples added so do a pull request from time to time.

## Current list of projects

AccessEntityFramework
BooksApp
CachingInterception
ComplexTypesSampleApp
ComputedColumnsApp
CreateAndPopulateSqlServerApp
DateOnlyTimeOnlySampleApp
DefaultConstraintSampleApp
EntityLibrary
ExecuteUpdateDeleteSampleApp
HasQueryFilterRazorApp
HierarchyIdSampleApp
ImmutableComplexTypesSampleApp
Interception
Microsoft Access
NorthWind2023App
NorthWind2023Library
ShadowProperties
SQL Server
SqlLite
Utilities
UtilityLibarary
WineConsoleApp