Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/ssis/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
用于映射投影生成的AutoMapper SQL优化(不可为null到可为null)_Sql_Optimization_Automapper_Projection - Fatal编程技术网

用于映射投影生成的AutoMapper SQL优化(不可为null到可为null)

用于映射投影生成的AutoMapper SQL优化(不可为null到可为null),sql,optimization,automapper,projection,Sql,Optimization,Automapper,Projection,下面是反映问题的单元测试代码:: public class NonNullableToNullable : AutoMapperSpecBase { public class Customer { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; } public str

下面是反映问题的单元测试代码::

 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))
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)
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))
如果在映射时强制转换为null

 .ForMember(d => d.GradeId, opt => opt.MapFrom(e => (int?)e.Category.GradeId))
生成的SQL将符合预期:

 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))
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)
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))
选择
[Extent1].[Id]作为[Id],
[Extent1]。[CategoryId]作为[CategoryId],
[Extent2]。[Name]作为[Name],
[Extent1]。[FirstName]作为[FirstName],
[Extent2]。[GradeId]作为[GradeId],
[Extent3]。[Name]作为[Name1],
[Extent1].[LastName]作为[LastName]
来自[dbo].[Customers]作为[Extent1]
将[dbo].[CustomerCategories]作为[Extent1].[CategoryId]=[Extent2].[Id]上的[Extent2]进行内部联接
内部联接[dbo].[CustomerGrades]为[Extent2].[GradeId]=[Extent3].[Id]上的[Extent3]
其中1=[Extent2].[GradeId]
对象查询
.MergeAs(仅限MergeOption.AppendOnly)
.选择(
dtoCustomer=>新的非NullTableToNullable.CustomServiceWModel
{
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
})
.其中(a=>a.GradeId==((int?)1))

所以我的问题是,当目标类型可为null时,为什么automapper强制转换在默认情况下不可为null,或者是否有任何配置可以在不进行手动转换的情况下执行此操作?
ProjectTo
必须是链中的最后一个调用。EF使用实体,而不是DTO。因此,在实体上应用任何筛选和排序,并作为最后一步,投影到DTO。

谢谢您的回答,有些场景是正确的,但有些场景基于DTO筛选更方便。您误用了
ProjectTo
,然后抱怨结果。这不是关于你想要什么,而是关于什么是可能的。您也可以尝试表达式映射,但最终EF也会做同样的事情。