Giter Club home page Giter Club logo

ef-core-transforming's Introduction

EF Core Value Conversions

โœ”๏ธ December 2022 moved all projects to .NET Core 5, EF Core 7

Introduction

EF Core provides methods to transform one type into another. Most common use is with enumerations while many other transformations are possible. In this article/code sample there are two samples for enumerations and one for string array.

Bulk-configuring a value converter is possible as shown below.

Pre-defined conversions EF Core contains many pre-defined conversions that avoid the need to write conversion functions manually. Instead, EF Core will pick the conversion to use based on the property type in the model and the requested database provider type.

The code samples below allow a developer to get started by following along with provided code.

Code by itself is not enough, take time to read Microsoft documentation.

Check the following page for builtin converters.

๐Ÿ”ธ Although there are many articles out there on transformations, this has ready to run examples.

๐Ÿ”ธ Although I coded this repository, inspiration can from various incomplete post on the web.

public class CurrencyConverter : ValueConverter<Currency, decimal>
{
    public CurrencyConverter()
        : base(
            v => v.Amount,
            v => new Currency(v))
    {
    }
}

Enumerations

In this example we want to categorize wines. The model has a primary key, string which represents the wine name and a enum for category,

public class Wine
{
    public int WineId { get; set; }
    public string Name { get; set; }
    public WineVariantId WineVariantId { get; set; }
    public WineVariant WineVariant { get; set; }
    public override string ToString() => Name;
}

Enum

public enum WineVariantId : int
{
    Red = 0,
    White = 1,
    Rose = 2
}

The following provides a one to many relationship

public class WineVariant
{
    public WineVariantId WineVariantId { get; set; }
    public string Name { get; set; }
    public List<Wine> Wines { get; set; }
    public override string ToString() => Name;
}

Using HasConversion in the DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Wine>()
        .Property(e => e.WineVariantId)
        .HasConversion<int>();

    modelBuilder
        .Entity<WineVariant>()
        .Property(e => e.WineVariantId)
        .HasConversion<int>();

    modelBuilder
        .Entity<WineVariant>().HasData(
            Enum.GetValues(typeof(WineVariantId))
                .Cast<WineVariantId>()
                .Select(e => new WineVariant()
                {
                    WineVariantId = e,
                    Name = e.ToString()
                })
        );
}

Let's query for all wines

using System.Linq;
using HasConversion.Data;
using HasConversion.Models;
using Microsoft.EntityFrameworkCore;
using Spectre.Console;
using static HasConversion.Models.WineVariantId;

namespace HasConversion.Classes
{
    public class WineOperations
    {
        /// <summary>
        /// If database does not exists than pass true to this method
        /// to create and populate with several records.
        ///
        /// Otherwise passing false to view records in database
        /// </summary>
        /// <param name="reCreate"></param>
        public static void AddViewWines(bool reCreate = false)
        {
            using var context = new WineContext();
            var allWines = context.Wines.Include(item => item.WineVariant).ToList();
        }
    }
}

Results

All Wines

Display

Wines

To be consistent let's do the same pattern with books and category for books.

public partial class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public BookCategory BookCategory { get; set; }
    public override string ToString() => Title;
}

public class BookVariant
{
    [Key]
    public BookCategory BookCategoryId { get; set; }
    public string Name { get; set; }
    public List<Book> Books { get; set; }
    public override string ToString() => Name;
}

Setup the conversion, in this case we are not setting up for one to many but by following the pattern above we can.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");


    modelBuilder
        .Entity<Book>()
        .Property(e => e.BookCategory)
        .HasConversion<int>();

    modelBuilder
        .Entity<BookVariant>().HasData(
            Enum.GetValues(typeof(BookCategory))
                .Cast<BookCategory>()
                .Select(e => new BookVariant()
                {
                    BookCategoryId = e,
                    Name = e.ToString()
                })
        );
}

Get all books

var bookList = context.Book.ToList();

Get by specific category Adventure

var list = bookList.Where(books => books.BookCategory == BookCategory.Adventure).ToList();

Books

Enum tip

When working with enum, consider placing the enum values in a database table and use a T4 template to generate the model. This will be helpful when there are many projects using the same enum and saves time if a member name changes, members are deleted or added.

๐Ÿ”ธ There are several examples included.

String to array

This one although provides a decent sample for using HasConversion the data model can be improved. This comes from the following forum question.

I know I can create classes from the JSON data, and parse out the elements. Butttt how do I then insert into MS SQL Server tables?

Their code

public class Account
 {
     public string Email { get; set; }
     public bool Active { get; set; }
     public DateTime CreatedDate { get; set; }
     public IList<string> Roles { get; set; }
 }
    
    
 string json = @"{
   'Email': '[email protected]',
   'Active': true,
   'CreatedDate': '2013-01-20T00:00:00Z',
   'Roles': [
     'User',
     'Admin'
   ]
 }";
    
 Account account = JsonConvert.DeserializeObject<Account>(json);
    
 Console.WriteLine(account.Email);

One forum member recommended to string string concatenation which is okay but EF Core can handle this.

My recommendation

Model which changes the Roles property to a string array.

public partial class Account
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public bool? Active { get; set; }
    public DateTime? CreatedDate { get; set; }
    public string[] Roles { get; set; }
}

Conversion

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");

    modelBuilder.Entity<Account>()
        .Property(e => e.Roles)
        .HasConversion(
            value => string.Join(',', value),
            value => value.Split(',',
                StringSplitOptions.RemoveEmptyEntries));

    OnModelCreatingPartial(modelBuilder);
}

Has Conversion

View records

public static void ViewAccounts()
{
    using var context = new AccountContext();
    var accountList = context.Account.ToList();

    var table = CreateViewTable();

    foreach (var account in accountList)
    {

        if (account.Id.IsEven())
        {
            table.AddRow($"[bold yellow on green]{account.Id}[/]", $"[bold yellow on green]{account.UserName}[/]");
        }
        else
        {
            table.AddRow($"{account.Id}", account.UserName);
        }

        foreach (var role in account.Roles)
        {
            table.AddRow("", role.PadLeft(10));
        }

        table.AddEmptyRow();

    }

    AnsiConsole.Write(table);

    var admins = accountList.Where(account => account.Roles.Contains("Admin")).ToList();


}

Storing List

In this case a List<int>

Model

public class EntityType
{
    public int Id { get; set; }
    public List<int> ListProperty { get; set; }
}

Conversion

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    
    modelBuilder
        .Entity<EntityType>()
        .Property(e => e.ListProperty)
        .HasConversion(
            v => JsonSerializer.Serialize(v, null),
            v => JsonSerializer.Deserialize<List<int>>(v, null),
            new ValueComparer<List<int>>(
                (list1, list2) => list1.SequenceEqual(list2),
                c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
                c => c.ToList()));
    
}

Add record

var entity = new EntityType { ListProperty = new List<int> { 1, 2, 3 } };
context.Add(entity);
context.SaveChanges();

Result

image

BoolToStringConverter

This converter transforms a bool to string where the value in the database table will be a string then when read back as a bool.

Bool To String

public class Person
{
    [Key]
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public bool IsFriend { get; set; }
    public override string ToString() => $"{FirstName} {LastName}";
}

Json example

Taken from a Stackoverflow post.

Is there a way to indicate that this is not a relationship but should be stored as a big string?

public class Campaign
{
    private string _extendedData;

    [Key]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }

    [NotMapped]
    public JObject ExtendedData
    {
        get
        {
            return JsonConvert.DeserializeObject<JObject>(string.IsNullOrEmpty(_extendedData) ? "{}" : _extendedData);
        }
        set
        {
            _extendedData = value.ToString();
        }
    }
}

In the DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Campaign>()
        .Property<string>("ExtendedDataStr")
        .HasField("_extendedData");
}

Summary

In this article there are enough code to run and study to learn the basics of transforming properties in models using Entity Framework Core. Couple this with Microsoft docs and the links below a developer can easily perform conversions.

๐Ÿ”ธ Some coders may look for places to use what has been presented while the reverse should be the path, tuck these away and when a situation arises you have a solution.

Improvements to HasConversion API

For EF Core 6

Before EF Core 6.0, the generic overloads of the HasConversion methods used the generic parameter to specify the type to convert to.

See also

NuGet packages

Spectre.Console provided classes to provide clear ways to present data. Package.

https://marcominerva.wordpress.com/2022/01/07/dateonly-and-timeonly-support-with-entity-framework-core-6-0/

ef-core-transforming's People

Contributors

karenpayneoregon avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.