【问题标题】:Ef Core - Use Automapper to get data from keyless ViewEf Core - 使用 Automapper 从无键视图中获取数据
【发布时间】:2020-12-30 04:39:21
【问题描述】:

我需要知道是否可以使用 Automapper 从数据库视图映射 DTO 属性。或者也许在 DbContext 模型配置中是可能的。让我们想象一下,我有一个业务目的是拥有一个包含其他相关数据的视图,但为了简洁起见,我将类变得简单

相关的Nugets

  • EF Core v3.1.7
  • AutoMapper v10.0.0

我有一个实体

public class Foo {
    public int Id { get; set; }
    public string Name { get; set; }
}

我有一个无钥匙视图

public class BarVW {
    public int FooId { get; set; }
    public string FooName { get; set; }
}

视图构建在 DB Initializer 类中

context.Database.ExecuteSqlRaw(
    @"CREATE OR REPLACE VIEW v_Bar AS 
        select
            f.Id as FooId,
            f.[Name] as FooName
        from
            Foo f
        -- where some logic that makes a Foo appear in view"
);

然后将视图分配给DbContext类中的一个DbSet

modelBuilder.Entity<BarVW>(eb =>
    {
        eb.HasNoKey();
        eb.ToView("v_Bar");
    });

public DbSet<BarVW> BarVW { get; set; }

在我的 FooDto 中,我需要一个附加属性到我的实体类,这将表明这个 Foo 存在于我的数据库视图 v_Bar 中

public class FooDto {
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsBar { get; set; }
}

对于 Automapper 配置,我有以下内容,但我不知道我可以从 BarVW DbSet 映射我的 dto IsBar 属性

CreateMap<Foo, FooDto>()
   .ForMember(dto => dto.IsBar, opt => ??? ); // don't know what to put here

【问题讨论】:

  • IsBar 添加到Foo 并将其设置在那里。这不是映射问题。
  • @LucianBargaoanu IsBar 不是数据库属性(列)。 IsBar 是一个业务逻辑属性,其结果由 View 中存在的 FooId 确定
  • 这是一个数据库问题。它属于你自己的代码,而不是 AM 配置。
  • 您可以使用BeforeMapAfterMap 吗?什么决定了IsBar 是否为真?如果实体不为空,是真的吗?
  • @LucianBargaoanu 不,这不是数据库问题。视图不能具有可导航属性,因此我无法在 Foo 类中引用视图。所以我的问题仍然是,如何将 View 的结果映射到 Dto 上的属性?

标签: c# sql-server entity-framework-core automapper


【解决方案1】:

实现您想要的一个简单方法是引入一个导航属性(例如Bars)并在映射配置中使用它(例如opt.MapFrom(src =&gt; src.Bars.Any()))。

这是一个完整的示例控制台程序,它演示了这种方法:

using System.Diagnostics;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class Foo
    {
        public int Id { get; set; }
        public string Name { get; set; }
        
        public BarVW Bar { get; set; }
    }

    public class BarVW
    {
        public int FooId { get; set; }
        public string FooName { get; set; }
        
        public Foo Foo { get; set; }
    }
    
    public class Context : DbContext
    {
        public virtual DbSet<Foo> Foo { get; set; }
        public virtual DbSet<BarVW> BarVW { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(
                    @"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63850736")
                .UseLoggerFactory(LoggerFactory.Create(b => b
                    .AddConsole()
                    .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Foo>()
                .HasData(
                    new Foo {Id = 1, Name = "Fo"},
                    new Foo {Id = 2, Name = "Foo"},
                    new Foo {Id = 3, Name = "Fooo"});

            modelBuilder.Entity<BarVW>(
                eb =>
                {
                    eb.HasKey(e => e.FooId);
                    eb.ToView("v_Bar");
                    eb.HasOne(e => e.Foo)
                        .WithOne(e => e.Bar)
                        .HasForeignKey<BarVW>(e => e.FooId);
                });
        }
    }
    
    public class FooDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsBar { get; set; }
    }

    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
            
            context.Database.ExecuteSqlRaw(
                @"CREATE VIEW [v_Bar] AS 
    select
        f.[Id] as [FooId],
        f.[Name] as [FooName]
    from
        [Foo] f
    where
        f.[Id] >= 1 and f.[Id] <= 2"
            );
            
            var config = new MapperConfiguration(
                cfg => cfg
                    .CreateMap<Foo, FooDto>()
                        .ForMember(dto => dto.IsBar, opt => opt.MapFrom(src => src.Bar != null)));
            
            var result = context.Foo
                .ProjectTo<FooDto>(config)
                .ToList();
            
            Debug.Assert(result.Count == 3);
            Debug.Assert(result.Count(dto => dto.IsBar) == 2);
        }
    }
}

为查询生成的 SQL 如下所示:

SELECT [f].[Id], CASE
    WHEN [v].[FooId] IS NOT NULL THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END AS [IsBar], [f].[Name]
FROM [Foo] AS [f]
LEFT JOIN [v_Bar] AS [v] ON [f].[Id] = [v].[FooId]

您可以使用.NET Fiddle 运行示例代码。

【讨论】:

  • 我刚刚用一个更简单的版本更新了示例代码,该版本在表和视图之间使用了一对一的关系。
  • 工作就像一个魅力!您可以尝试使用 automapper 解决我的另一个问题,但这次它更接近前端。在此处阅读所有相关信息:stackoverflow.com/questions/63675676/…
  • 我不确定在 Foo 类中添加 BarVW 类,因为我不希望它实际在数据库中创建外键
  • 应该没问题。查看链接的 Fiddle 中的日志记录输出。没有为数据库创建外键。由于ToView() 调用,EF Core 知道实体是一个视图,因此不会尝试添加任何外键约束(如果尝试添加则会失败)。
猜你喜欢
  • 1970-01-01
  • 2020-11-27
  • 1970-01-01
  • 2019-02-23
  • 2021-10-24
  • 1970-01-01
  • 2021-07-29
  • 2019-09-09
  • 1970-01-01
相关资源
最近更新 更多