ardalis / Specification

Base class with tests for adding specifications to a DDD model
MIT License
1.84k stars 240 forks source link

Can ignore properties in Select Specifications? #392

Open datnt97 opened 2 months ago

datnt97 commented 2 months ago

I've saw the docs about Select.

public class SitemapPageSpecification : Specification<Page, SitemapPageDto>
{
  public SitemapPageSpecification()
  {
    _ = Query
      .Where(e => !e.IsDeleted
      && !e.IsHiddenPage
      && e.Status == PageStatus.Published
      && e.PageTypeId != nameof(WebpageType.Post)
      && e.PageTypeId != nameof(WebpageType.PostCategory)
      && e.PageTypeId != nameof(WebpageType.Blog));

    _ = Query
      .Select(e => new SitemapPageDto
      {
        Path = e.NiceUrl,
        LastModified = e.DateUpdated ?? e.DateCreated
      });
  }
}

But it seem does not work in my case.

Expected: Some unnecessary properties slow down the query. Can I remove them from the SQL query?

image

fiseni commented 2 months ago

Hi @datnt97,

I think you're missing the methods that return TResult in your repository implementation. Here is the built-in implementation.

datnt97 commented 2 months ago

I tried it. But it seems like it's not working as expected as well. Am I doing something wrong here?

image

fiseni commented 2 months ago

Hey @datnt97

Here I created a full sample app for you. We're confident it works properly, it's battle-tested on production for a long time. Perhaps you should cross-check everything on your side once again. This is the generated SQL statement for the app.

SELECT [c].[Id], [c].[FirstName]
FROM [Customers] AS [c]
using Ardalis.Specification;
using Ardalis.Specification.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

await AppDbContext.SeedAsync();

using var dbContext = new AppDbContext();
var repo = new Repository<Customer>(dbContext);
var spec = new CustomerDtoSpec();

var result = await repo.ListAsync(spec);
Console.WriteLine(result.Count);

public interface IRepository<T> : IRepositoryBase<T> where T : class { }
public class Repository<T> : RepositoryBase<T> where T : class 
{
    public Repository(AppDbContext dbContext)
        : base(dbContext) { }
}

public class CustomerDtoSpec : Specification<Customer, CustomerDto>
{
    public CustomerDtoSpec()
    {
        Query.Select(x => new CustomerDto
        {
            Id = x.Id,
            FirstName = x.FirstName
        });
    }
}
public class CustomerDto
{
    public int Id { get; set; }
    public string? FirstName { get; set; }
}
public class Customer
{
    public int Id { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}
public class AppDbContext : DbContext
{
    public DbSet<Customer> Customers => Set<Customer>();
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connectionString = "Data Source=(localdb)\\MSSQLLocalDb;Initial Catalog=SpecificationSelectDemo;Integrated Security=SSPI;Trusted_Connection=true";
        optionsBuilder.UseSqlServer(connectionString).LogTo(Console.WriteLine);
    }

    public static async Task SeedAsync()
    {
        using var dbContext = new AppDbContext();
        var customers = new List<Customer>()
        {
            new() { FirstName = "Customer1", LastName = "Customer1" },
            new() { FirstName = "Customer2", LastName = "Customer2" },
            new() { FirstName = "Customer3", LastName = "Customer3" },
        };
        await dbContext.Database.EnsureCreatedAsync();
        dbContext.AddRange(customers);
        await dbContext.SaveChangesAsync();
    }
}
datnt97 commented 2 months ago

Thank you for your greatest help!