【发布时间】:2020-10-10 19:33:41
【问题描述】:
这是反映问题的单元测试代码:
public class NonNullableToNullable : AutoMapperSpecBase
{
public class Customer
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? CategoryId { get; set; }
public virtual CustomerCategory Category { get; set; }
}
public class CustomerCategory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; }
public string Name { get; set; }
public int GradeId { get; set; }
public virtual CustomerGrade Grade { get; set; }
}
public class CustomerGrade
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; }
public string Name { get; set; }
}
public class CustomerViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
public int? GradeId { get; set; }
public string GradeName { get; set; }
}
public class Context : DbContext
{
public Context()
{
Database.SetInitializer<Context>(new DatabaseInitializer());
}
public DbSet<Customer> Customers { get; set; }
public DbSet<CustomerCategory> CustomerCategories { get; set; }
public DbSet<CustomerGrade> CustomerGrades { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>()
.HasOptional(a => a.Category).WithMany().HasForeignKey(a => a.CategoryId);
modelBuilder.Entity<CustomerCategory>().HasKey(a => a.Id)
.HasRequired(a => a.Grade).WithMany().HasForeignKey(a => a.GradeId);
modelBuilder.Entity<CustomerGrade>().HasKey(a => a.Id);
base.OnModelCreating(modelBuilder);
}
}
public class DatabaseInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
context.CustomerGrades.Add(new CustomerGrade() { Id = 1, Name = "A" });
context.CustomerCategories.Add(new CustomerCategory() { Id = 10, GradeId = 1, Name = "Category 1" });
context.Customers.Add(new Customer
{
Id = 100,
FirstName = "Bob",
LastName = "Smith",
});
context.Customers.Add(new Customer
{
Id = 101,
FirstName = "Tom",
LastName = "Thomas",
CategoryId = 10
});
base.Seed(context);
}
}
protected override MapperConfiguration Configuration => new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerViewModel>()
.ForMember(d => d.CategoryName, opt => opt.MapFrom(e => e.Category.Name))
// If you forget to cast to nullable, the resulting SQL will look terrible
//.ForMember(d => d.GradeId, opt => opt.MapFrom(e => e.Category.GradeId))
.ForMember(d => d.GradeId, opt => opt.MapFrom(e => (int?)e.Category.GradeId))
.ForMember(d => d.GradeName, opt => opt.MapFrom(e => e.Category.Grade.Name))
;
});
[Fact]
public void Can_map_with_projection()
{
using (var context = new Context())
{
var query = ProjectTo<CustomerViewModel>(context.Customers).Where(a => a.GradeId == 1);
var model = query.First();
model.Id.ShouldBe(101);
model.FirstName.ShouldBe("Tom");
model.LastName.ShouldBe("Thomas");
}
}
}
可读的地图表达式:
ObjectQuery<NonNullableToNullable.Customer>
.MergeAs(MergeOption.AppendOnly)
.Select(
dtoCustomer => new NonNullableToNullable.CustomerViewModel
{
CategoryId = dtoCustomer.CategoryId,
CategoryName = dtoCustomer.Category.Name,
FirstName = dtoCustomer.FirstName,
GradeId = (dtoCustomer.Category == null) ? null : (int?)dtoCustomer.Category.GradeId,
GradeName = dtoCustomer.Category.Grade.Name,
Id = dtoCustomer.Id,
LastName = dtoCustomer.LastName
})
.Where(a => a.GradeId == ((int?)1))
错误的 sql(不能使用索引):
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CategoryId] AS [CategoryId],
[Extent2].[Name] AS [Name],
[Extent1].[FirstName] AS [FirstName],
--The generated SQL is a little redundant
CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE [Extent2].[GradeId] END AS [C1],
[Extent3].[Name] AS [Name1],
CASE WHEN (1 = 0) THEN cast(0 as bigint) ELSE [Extent1].[Id] END AS [C2],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Customers] AS [Extent1]
LEFT OUTER JOIN [dbo].[CustomerCategories] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[Id]
LEFT OUTER JOIN [dbo].[CustomerGrades] AS [Extent3] ON [Extent2].[GradeId] = [Extent3].[Id]
--The WHERE condition cannot use an index
WHERE 1 = (CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE [Extent2].[GradeId] END)
如果在映射时强制转换为可空
.ForMember(d => d.GradeId, opt => opt.MapFrom(e => (int?)e.Category.GradeId))
生成的 SQL 将符合预期:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CategoryId] AS [CategoryId],
[Extent2].[Name] AS [Name],
[Extent1].[FirstName] AS [FirstName],
[Extent2].[GradeId] AS [GradeId],
[Extent3].[Name] AS [Name1],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Customers] AS [Extent1]
INNER JOIN [dbo].[CustomerCategories] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[Id]
INNER JOIN [dbo].[CustomerGrades] AS [Extent3] ON [Extent2].[GradeId] = [Extent3].[Id]
WHERE 1 = [Extent2].[GradeId]
ObjectQuery<NonNullableToNullable.Customer>
.MergeAs(MergeOption.AppendOnly)
.Select(
dtoCustomer => new NonNullableToNullable.CustomerViewModel
{
CategoryId = dtoCustomer.CategoryId,
CategoryName = dtoCustomer.Category.Name,
FirstName = dtoCustomer.FirstName,
GradeId = (int?)dtoCustomer.Category.GradeId,
GradeName = dtoCustomer.Category.Grade.Name,
Id = dtoCustomer.Id,
LastName = dtoCustomer.LastName
})
.Where(a => a.GradeId == ((int?)1))
所以我的问题是,当目标类型可以为空时,为什么 automapper 默认不能为空,或者是否有任何配置可以在不手动转换的情况下做到这一点?
【问题讨论】:
标签: sql optimization automapper projection