Entity Framework Core 6 and 7 Tips and Tricks


  • Interceptors in EF Core 7
    - Use the TPC (Table per Concrete) Mapping strategy (preferred) if you are looking to implement Hierarchy and inheritance in the Database Design. 
       The default is TPH (Table per Hierarchy)
        https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/sockets/tcp-classes



    - This is when you let EF Core know to call back to another function when an Entity is called. For example the use of ILogger in the Entity Modal as a Property to log messages about operations involving that Entity Model.

    - You need to configure the Database Context to use Interceptor and inject LoggerInjectionInterceptor if you want this to work.

    - You can use ISaveChangesInterceptor to intercept SaveChanges Logs

    //This function shows you then Entities that the EF Core failed to Delete
    public class SuppressDeleteConcurrencyInterceptor: ISaveChangesInterceptor
    {
    public ValueTask<IntercetpionResult> ThrowingConcurrencyExceptionAsync(
    ConccurrencyExceptionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default
    ) => eventData.Entries.All(e => e.State == EntityState.Deleted)? new ValueTask<InterceptionResult>(InterceptionResult.Suppress()):new(result);
    
    }
    
    //Dont forget to add the Interceptors to the db Context OnConfiguring
    protected override void OnConfiguring(DbContextOptionBuilder optionBuilder)
    => optionBuilder.AddInterceptors(
              new LoggerInjectionInterceptor(),
              new SuppressDeleteConcurrencyInterceptor())

  • Aired on 6/29/22: Entity Framework Architecture Part 2.

    [Important]: The dbContext Class is not Thread Safe, do not try to use it from two different Threads at the same time, when you do it will throw.

    1. For those who are wondering what DbSet <MyModel> does, it acts as a Query Root that returns the
        IQuerable. The IQuerable Interface allows you to do composable queries like Linq filters such as WHERE or Take,

    2. The easiest and cleanest way to use InMemory Cache to AddOrGet object is by using the

    _cache.GetOrAdd(key,AFunctionToCheckIfKeyValueIsNotFound); //If value is null the GetOrAdd Function executes the function below and then caches values.
    
    //define the function to execute if Value is not found
    
    private ValueToReturn  AFunctionToCheckIfKeyValueIsNotFound(){
    //Do Something
    //return value;
    }?


    3. DbSet is initialized every time you create DbContext, when you do var db = new DbContext() the DbSet is compiled again. However, if you don't want the DbSet to be Compiled every time you request a new Database Context then you might want to look into Connection Pooling.

    public DbSet<Blog> Blogs => Set<Blogs>();//This is nullable, the Compiler knows it.

    - The other option is declaring the DbSet as below, which I prefer because it will give you some performance:
    - Keep in mind though, that when you use the pattern above, you will be calling the Same Instance over and over.


    If you use the code and the pattern above, it will use the cached Set Objects

  • DbContext Pooling (Is not Connection Pooling, completely different)
    - Connection Pooling is when you reuse the TCP Connection (done in a Database Driver, not EF Core) to reconnect to the database. It is the concern of the SQL Client or NPG Client for PostGreSQL

    - Database Context is just an In-Memory Object that holds the reference to the Database Connection Properties/Configurations.
    DbContext Pooling is reusing the Instatietions of that In-Memory Context to improve performance.

         - Resolving the DbContext via the DI is the same thing as new'ing the instance.

    [NB]: Prepare your code code for dbContextPooling
    1. Remove the OnConfigure from the Context
    2. Enable Pooling with the code below
    var contextPool = new PooledDbContextFactory<myDbContext>(options);//Everytime you use the contextPool it will reuse the same object
    //You could use dbPooling in the DI by using
    
    services.AddDbContextPool<MyDbContext>(options);?


    - Inject DbContext into the Dependency Container in Asp.Net Core Entity Framework Configuration from the Startup.cs
     services.AddDbContext<dbContext>(options =>
    options.UseSqlServer(
       Configuration.GetConnectionString("DefaultConnection")).EnableSensitiveDataLogging().EnableDetailedErrors().LogTo(Console.WriteLine,LogLevel.Information), ServiceLifetime.Transient);
    ?


    Reference:

    1.  Video from the EF Core Team


  • EF Core migration and updating the database actually creates the database if it does not exist. 
    - You should not change the Migration and Designer files because then your entity and the Migration files are out sink.



  • [Important]: Avoid by all means mixing synchronous and asynchronous code in the same application - It might trigger a Thread-Pool starvation issue.

  • Use Indexes to retrieve data e.g.
        var post = context.Posts.Where(a => a.title.startsWith("A")).ToList();

  • If you have any calculation in the Linq code, the best way to do is to create a column with a stored persisted column for your expression. Create a column with a calculated field then just retrieve it.

    - Create an index over that expression if you use that column for filtering purposes in your code.

  • [Important]: Use SELECT in the EF core query to only select data you need and not other columns, this increases performance e.g.

    foreach(var blogName in context.Blogs.Select(b => b.Url)){
    cw(blogName);
    }

    - If you retrieve data this way, then updating the entity becomes tricky and expensive since EF Core Change Tracker only works with full Entity tracked.

  • Limit the number of results retrieved by using Take(NumberHere)

  • Avoid Joins if you can. (Cartesian Explosion) use split queries in EF Core instead, split queries allow you to load data in two separate queries.

  • Know different methods of loading data, like Eager loading, Lazy loading, and or Explicit loading
    - Eager loading is when you use include(blog => blog.Title) //from another table, this will load data eagerly
    - If you need to load more than 1 column from another table then use Projection instead

       var blog = context.Blogs.Select(b => new {b.Url, b.Posts}).ToList();//This will make EF Core fetch blogs and their posts in one trip to the database.

    [NB]: Because lazy loading makes it extremely easy to trigger the N+1 problem, it is advisable to avoid it.

  • Buffering and Streaming: This allows you to load all your data into memory. This requires more memory.
      - ToList() or ToArray() causes the entire resultset to be buffered.

    - foreach(var m in context.Blogs.Where(p=> p.title.StartsWith("A"))) //causes data set to be streamed processing on row at the time.
    - AsEnumerable also streams, it allows you to write Linq Queries on the UI Level.

    [NB] Avoid using ToList() or ToArray if you intend to use another LINQ operator on the result - it will needlessly buffer all results into memory. 
            Use AsEnumerable instead.

    using ToList() or ToArray() might cause another buffering to occur as EF Core uses an Internal buffering for the multi-data set. Use AsEnumerable.

  • Tracking and No-Tracking and Identity Resolution
    EF Core automatically tracks entity so that it does not make another round trip to the DB, when this happens it is called an Identity-Resolution.

    - EF takes a snapshot of tracked entities and when you SaveChanges() it compares (this process takes up memory and time)
    - Use NoTracking() if data won't be changed and saved. [It is possible to retrieve with AsNoTracking() then when something changes attach the entity and save changes]. AsNoTracking() allocates less object on the Heap vs AsTracking().
  • Asynchronous Programming

    - As a general rule, write asynchronous code in order to be scalable e.g. SaveChangesAsync() than just SaveChanges()
      Synchronous code blocks the Main Thread for the duration of the Database IO

    [Important]: Avoid by all means mixing synchronous and asynchronous code in the same application - It might trigger Thread-Pool starvation issues.


    References: 
    https://docs.microsoft.com/en-us/ef/core/performance/efficient-querying

Entity Framework
published
v.0.01



Marry said:

What is EF Core?

Posted On: August 06, 2022 14:09:55 PM
John said:

Thank you, this is great.

Posted On: August 06, 2022 10:09:33 AM

For peering opportunity Autonomouse System Number: AS401345 Custom Software Development at ErnesTech Email AddressContact: [email protected]