Mein Problem ist, dass EF Core standardmäßig alle meine definierten Eigenschaften einer Klasse lädt, während ich möchte, dass sie nicht geladen werden, es sei denn, ich frage ausdrücklich nach ihnen.
Nehmen Sie zum Beispiel dieses einfache Beispiel eines Buches mit einem Autor (in diesem Beispiel sind alle Modelle gleich, nur um das verwendete Muster zu zeigen):
Datenbankentität:
using System;
namespace Test.Models.DBModels
{
public partial class Book
{
public int BookId { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
}
}
DTO:
using System;
using System.Collections.Generic;
namespace Test.Models.DTOModels
{
public partial class BookDTO
{
public int BookId { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
public AuthorDTO Author { get; set; }
}
}
ViewModel:
using System;
using System.Collections.Generic;
namespace Test.Models.ViewModels
{
public partial class BookVM
{
public int BookId { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
public AuthorVM Author { get; set; }
}
}
Beachten Sie, dass für keine dieser Klassen "virtuelle" Eigenschaften verwendet werden, da ich gelesen habe, dass dies EF angewiesen hat, sie automatisch zu füllen
DBContext:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Test.Models.DBModels;
namespace Test.DAL
{
public partial class TestContext : DbContext
{
public TestContext()
{
}
public TestContext(DbContextOptions<TestContext> options)
: base(options)
{
}
public virtual DbSet<Author> Author { get; set; }
public virtual DbSet<Book> Book { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
//optionsBuilder.UseSqlServer("connectionstring");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}
Bedienung:
using AutoMapper;
using System.Collections.Generic;
using System.Linq;
using Test.BLL.Interfaces;
using Test.DAL;
using Test.Models.DomainModels;
using Test.Models.DTOModels;
using Microsoft.EntityFrameworkCore;
using AutoMapper.QueryableExtensions;
using System.Linq.Expressions;
using System;
namespace Test.BLL.Implementations
{
public class BookService : IBookService
{
private readonly TestContext dbContext;
private readonly IMapper _mapper;
public BookService(TestContext dbContext, IMapper mapper)
{
this.dbContext = dbContext;
this._mapper = mapper;
}
public IQueryable<BookDTO> Get()
{
var books = dbContext.Book;
var dto = books.ProjectTo<BookDTO>(_mapper.ConfigurationProvider);
return dto;
}
public IQueryable<BookDTO> Get(params Expression<Func<Book, object>>[] includes)
{
var books = dbContext.Book
.Select(x => x);
foreach (var include in includes)
books = books.Include(include);
var dto = books.ProjectTo<BookDTO>(_mapper.ConfigurationProvider);
return dto;
}
}
}
Regler:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Test.BLL.Implementations;
using Test.Models.DTOModels;
using Test.Models.ViewModels;
namespace Test.WebAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BookController : ControllerBase
{
private readonly BookService BookService;
private readonly IMapper _mapper;
public BookController(BookService BookService, IMapper mapper)
{
this.BookService = BookService;
this._mapper = mapper;
}
public IActionResult Index()
{
var books = _mapper.Map<IEnumerable<BookDTO>, IEnumerable<BookVM>>(BookService.Get().ToList());
return Ok(books);
}
}
}
Wenn ich die BookService.Get (). ToList () -Methode im Controller aufrufe , wird der Autor automatisch in die json-Ergebnisse eingetragen, z
{
"bookId":1,
"authorId":1,
"title":"Book A",
"author":{
"authorId":1,
"name":"Some Author"
}
}
Ich möchte nur, dass es so ist:
{
"bookId":1,
"authorId":1,
"title":"Book A",
"author": null
}
Als ob ich wollte, dass das Author-Objekt gefüllt wird, würde ich meine überladene Methode mit BookService.Get (x => x.Author) .ToList () aufrufen.
Ich gehe davon aus, dass dies mit eifrigen oder faulen Ladefunktionen zusammenhängt, bin mir aber nicht sicher, wie. BEARBEITEN: In der Dokumentation werden sie als " Verspätetes Laden bedeutet, dass die zugehörigen Daten beim Zugriff auf die Navigationseigenschaft transparent aus der Datenbank geladen werden " beschrieben. Sie sagen auch, dass " Eifriges Laden bedeutet, dass die zugehörigen Daten als Teil der anfänglichen Abfrage aus der Datenbank geladen werden ". Dies ist das gewünschte Verhalten, jedoch nur für die von mir angegebenen Eigenschaften.
Gibt es in EF Core eine Möglichkeit, die Eigenschaften nur dann zu füllen, wenn ich ausdrücklich die Aufnahme dieser Eigenschaften anfordere?
Die Antwort von Flater und der Kommentar von Lucian Bargaoanu haben mich zur richtigen Implementierung geführt (explizite Erweiterung). Im Automapper-Zuordnungsprofil kann ich angeben, dass ich nicht jede Eigenschaft automatisch erweitern möchte, z
CreateMap<Book, BookDTO>()
.ForMember(x => x.Author, options => options.ExplicitExpansion())
.ReverseMap();
Wenn ich dann meine überladene Get-Methode ändere, um die Includes an die ProjectTo-Methode zu übergeben:
public IQueryable<BookDTO> Get(params Expression<Func<BookDTO, object>>[] includes)
{
var books = dbContext.Book
.Select(x => x);
var dto = books.ProjectTo<BookDTO>(_mapper.ConfigurationProvider, null, includes);
return dto;
}
Dies bedeutet dann, dass das Aufrufen von BookService.Get (). ToList () standardmäßig Folgendes ergibt:
{
"bookId":1,
"authorId":1,
"title":"Book A",
"author": null
}
Wenn Sie jedoch BookService.Get (x => x.Author) .ToList () aufrufen, wird Folgendes zurückgegeben:
{
"bookId":1,
"authorId":1,
"title":"Book A",
"author":{
"authorId":1,
"name":"Some Author"
}
}
Dies bedeutet, dass ich AutoMapper weiterhin verwenden kann, ohne dass alle Eigenschaften automatisch von EF Core ausgefüllt werden.
Dies ist kein EF-Verhalten, sondern ein Automapper-Verhalten.
public IQueryable<BookDTO> Get()
{
var books = dbContext.Book;
var dto = books.ProjectTo<BookDTO>(_mapper.ConfigurationProvider);
return dto;
}
ProjectTo<>
wählt absichtlich alle Eigenschaften aus, denen es zugeordnet werden kann. Wenn Sie es BookDTO
, in ein BookDTO
zu BookDTO
, wird es sein Bestes tun, um alle in BookDTO
definierten Eigenschaften zu füllen, einschließlich des Autors.
Entity Framework weist bestimmte Verhaltensweisen beim Laden von Navigationseigenschaften auf, die allgemein als verzögertes und eifriges Laden bezeichnet werden. Anfangs würde man denken, dass dies die Ursache des Problems war.
Wenn Sie jedoch eine Select
, überschreiben Sie effektiv das Ladeverhalten von EF und teilen ihm explizit mit, was für Sie geladen werden soll. Dies ist beabsichtigt, um in Fällen verwendet zu werden, in denen das einfache Verhalten von EF nicht die genaue Kontrolle bietet, die Sie suchen.
Sie verwenden kein Select
, aber Sie verwenden ProjectTo<>
das intern ein Select
(das basierend auf der Automapper-Konfiguration generiert wird). Dies bedeutet, dass Sie in Bezug auf EF das Ladeverhalten und "Sie" überschreiben "(dh Automapper) weisen EF ausdrücklich an, den Autor zu laden.
Sie können Automapper anweisen, eine Eigenschaft mit dem richtigen Attribut zu ignorieren:
public partial class Book
{
public int BookId { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
[NotMapped]
public Author Author { get; set; }
}
Dies führt dazu, dass Automapper den zugehörigen Autor nicht aus der Datenbank abruft.
Ein Teil der Stärke von ProjectTo<>
besteht jedoch darin, dass Sie nicht mehr verwalten müssen, was Sie tun / nicht laden möchten, sondern Automapper dies anhand des bereitgestellten DTO herausfinden lassen müssen. Es ist nicht schlecht, einem DTO ein Attribut zuzuweisen, aber wenn Sie dies in großem Maßstab anwenden, erhöht sich die Komplexität bei Entwicklung und Wartung.
Stattdessen würde ich vorschlagen, dass Sie zwei separate DTO-Klassen erstellen, eine mit Autoreninformationen und eine ohne. Auf diese Weise müssen Sie das Zuordnungsverhalten nicht manuell steuern (nicht mehr als Sie sollten), und Sie sparen außerdem eine Reihe von Nullprüfungen, die Sie nicht ausführen müssen, wenn Sie dieses DTO ohne seinen Autor behandeln auch geladen werden.