netcore,

Best Practices And Architecture

Feb 21, 2023 · 5 mins read
Best Practices And Architecture
Share this

Project Structure

1. Importance of Organized Code.

Scalability, Maintainability, Manageability, Testability. c#

2. Project Organization

Application code can be split into sections:

  • Data Layer – database connection (context).
  • Domain Models – entity classes.
  • Client – user-interaction and app logic.
  • Business Logic – data validation, transformations.

Reasons:

  • Easier to locate files when maintaining.
  • Don’t have to rebuild entire codebase after changes (DLLs).

3. Usage Optimization

Only fetch required data by filtering and projecting your queries

context.Employees
  .Where(e => e.Salary >= 15000)
  .Select(e => new {
    e.FirstName,
    e.LastName,
    e.Salary
  }
);
SELECT
    1 AS [C1],
    [Extent1].[FirstName] AS [FirstName],
    [Extent1].[LastName] AS [LastName],
    [Extent1].[Salary] AS [Salary]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE [Extent1].[Salary] >= cast(15000 as decimal(18))

LINQ queries are executed each time the data is accessed

  • If materialized in a collection – ToList()
  • If the elements are aggregated – Count(), Average(), First()
  • When a property is accessed.

  • Try to delay execution (materialization) until you actually need the results.
  • You can monitor query execution using Express Profiler
EF will cache entities and compare the cache for changes
Use Find() with change detection disabled
try
{
    context.ChangeTracker.AutoDetectChangesEnabled = false;
    var product = context.Products.Find(productId);
    ...
}
finally
{
    context.ChangeTracker.AutoDetectChangesEnabled = true;
}

  • When adding or updating a record, Entity framework makes a call to DetectChanges().
  • Use AddRange() and RemoveRange() to reduce calls

List<Product> products = new List<Product>()
  { product1, product2, product3 };

context.Products.AddRange(products);

  • Entity Framework builds associations and tracks changes for every loaded entity.
  • If we only want to display data, this process is redundant.

Disable tracking:

context.Products
  .AsNoTracking()
  .Where(p => p.Price < 150)
  .ToList();

Note this also disables caching

4. Loading Methods

  • Payload size and number of roundtrips to the database are inversely proportional
    • Lazy – less data, more queries
    • Eager – more data, less queries
  • There is no best approach – performance depends on usage scenario

  • Do you need to access many navigation properties from the fetched entities?
    • No – Lazy for large payloads, Eager for small
    • Yes – Eager loading for up three entities, Lazy for more
  • Do you know exactly what data will be needed at run time?.
    • No – Lazy
    • Yes – Eager at first unless, Lazy if loading lots of data.
  • Is your code executing far from your database? (increased network latency)
    • No – Lazy will simplify your code; don’t take database proximity for granted.
    • Yes – Depending on scenario Eager will require fewer round trips.
  • Always test application-wide performance, only optimize if results aren’t satisfactory

5. Design Patterns

  • Singleton – Ensure a class has only one instance and provide a global point of access to it
  • Service Locator – Make a service available globally and decouple the calling class from the dependent object
  • Dependency Injection - no client code has to be changed simply because an object it depends on needs to be changed to a different one
  • Command – Encapsulate a request as an object, allowing delayed execution, undo and replay
  • Repository – Separates the data access logic and maps it to the entities in the business logic
  • Unit of work – Used to group one or more into a single transaction or unit of work, so that all operations either pass or fail as one

Singleton Pattern

public class Authenticator
{
  private static Authenticator instance;
  private Authenticator() {  } //Private Constructor
  public static Authenticator Instance
  {
    get
    {
      if (instance == null)
        instance = new Authenticator(); //Instantiate when first accessed
      return instance;
    }
  }
}

Service Locator

c#

Command Pattern c#

Repository Pattern

    It works with the domain entities and performs data access logic
    The domain entities, the data access logic and the business logic talk to each other using interfaces
    It hides the details of data access from the business logic
    Business logic can access the data object without having knowledge of the underlying data access architecture

c#

Unit of Work

- Serves one purpose: to make sure that when you use multiple repositories, they share a single database context.
- With a Unit of Work, you might also choose to implement Undo / Rollback functionality.
- When using Entity Framework Core, the recommended approach to undo is to discard your context with the changes you are interested in undoing.

c#

c#