繁体   English   中英

实体框架核心计算属性多表

[英]Entity Framework Core Computed Property Multiple Tables

我正在使用 Entity Framework Core 3.1 开发 ASP.Net Core web 应用程序。 假设我有这四个实体:

public class Project
{
    public string Name { get; set; }

    public DateTimeOffset CreationDate { get; set; }

    public int ProjectTypeId { get; set; }
    public ProjectType ProjectType { get; set; }

    public ICollection<ProjectScope> ProjectScopes { get; private set; } = new HashSet<ProjectScope>();
}

public class ProjectScope
{
    public int ProjectId { get; set; }
    public Project Project { get; set; }

    public int ScopeId { get; set; }
    public Scope Scope { get; set; }

}

public class Scope
{
    public int Id { get; set; }

    public string Name { get; set; }

    public ICollection<ProjectScope> ProjectScopes { get; private set; } = new HashSet<ProjectScope>();
}

public class ProjectType
{
    public int Id { get; set; }

    public string Name { get; set; }
}

我有一个要求,我需要在多个地方显示,并且能够在数据库级别上对其进行排序,项目名称是:按名称排序的所有范围 + CreationDate + 项目类型名称 + 项目名称

这就是我可以使用计算属性实现它的方法:

    public string FullName => 
        "[" + ProjectScopes.Select(ps => ps.Scope.Name).OrderBy(s => s).Aggregate((a, b) => a + " - " + b) + "]" +
        " " + CreationDate.ToString("d") +
        " " + ProjectType.Name +
        " " + Name;

当然,ef core是无法翻译的。 我在我的应用程序的多个 arrays 中显示此属性,并且我需要能够根据此字段对结果进行排序。

对我来说拥有这个计算域的最佳方式是什么?

我需要能够 select 并在连接到该数据库的任何客户端上对该字段进行排序。 (例如可以是 PowerBI)

  • 数据库上的计算列?
  • 触发器?
  • 一个用户自定义的function?

谢谢你的时间。

这取决于数据量、读取/更新频率以及排序发生的频率。 您是否考虑过使用 SQL 视图? 具有被索引、物化等的能力。

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace sqlview
{
    class Program
    {
        static void Main(string[] args)
        {
            using var dbContext = new EmpDbContext();

            // create db table
            dbContext.Database.EnsureCreated();

            // create view with "FullName" column as an aggregate like "First Name - Middle Name - Last Name"
            dbContext.Database.ExecuteSqlRaw("DROP VIEW IF EXISTS View_EmployeeView;");
            dbContext.Database.ExecuteSqlRaw(
                @"CREATE VIEW View_EmployeeView AS 
                        SELECT
                            e.Id,
                            e.FirstName + ' - ' + e.MiddleName + ' - '  + e.LastName as FullName
                        FROM Employees e;
            ");

            // insert record
            dbContext.Add(new Employee() { FirstName = "John", MiddleName = "Jack", LastName = "Sugarcoater" });
            dbContext.SaveChanges();

            // fetch using view
            var employeesFromView = dbContext.EmployeesFromView.ToListAsync().Result;
            foreach (var emp in employeesFromView)
            {
                Console.WriteLine($"Full name: {emp.FullName}");
            }
        }

        class EmpDbContext : DbContext
        {
            public DbSet<Employee> Employees { get; set; }
            public DbSet<EmployeeView> EmployeesFromView { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(
                    @"YOUR CONNECTION STRING");
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                // configure view
                modelBuilder
                    .Entity<EmployeeView>(eb =>
                    {
                        eb.HasNoKey();
                        eb.ToView("View_EmployeeView");
                    });
            }
        }

        public class Employee
        {
            public int Id { get; set; }
            public string FirstName { get; set; }
            public string MiddleName { get; set; }
            public string LastName { get; set; }
        }

        public class EmployeeView
        {
            public int Id { get; set; }
            public string FullName { get; set; } // will be computed on the SQL side
        }
    }
}

您可以将其作为 class 本身的属性。

只需通过覆盖 DbContext.OnModelCreating 并使用 EntityTypeBuilder.Ignore 方法来通知 EF 忽略此属性。

IF OBJECTPROPERTY(OBJECT_ID('[dbo].[project_scope]'), 'IsTable') = 1
   DROP TABLE [dbo].[project_scope]
GO
IF OBJECTPROPERTY(OBJECT_ID('[dbo].[project]'), 'IsTable') = 1
   DROP TABLE [dbo].[project]
GO
IF OBJECTPROPERTY(OBJECT_ID('[dbo].[scope]'), 'IsTable') = 1
   DROP TABLE [dbo].[scope]
GO
IF OBJECTPROPERTY(OBJECT_ID('[dbo].[project_type]'), 'IsTable') = 1
   DROP TABLE [dbo].[project_type]
GO
CREATE TABLE [dbo].[project_type]
(
   id INT NOT NULL PRIMARY KEY,
   name NVARCHAR(100) NOT NULL
)
GO
CREATE TABLE [dbo].[scope]
(
   id INT NOT NULL PRIMARY KEY,
   name NVARCHAR(100) NOT NULL
)
GO
CREATE TABLE [dbo].[project]
(
   id INT NOT NULL PRIMARY KEY,
   name NVARCHAR(100) NOT NULL,
   creation_date DATETIMEOFFSET,
   project_type_id INT
)
GO
CREATE TABLE [dbo].[project_scope]
(
   project_id INT NOT NULL,
   scope_id INT NOT NULL
)
GO
INSERT INTO [dbo].[project_type]
   (id, name)
VALUES
   (1, 'Type 1')
GO
INSERT INTO [dbo].[scope]
   (id, name)
VALUES
   (1, 'Scope A')
GO
INSERT INTO [dbo].[scope]
   (id, name)
VALUES
   (2, 'Scope B')
GO
INSERT INTO [dbo].[scope]
   (id, name)
VALUES
   (3, 'Scope C')
GO
INSERT INTO [dbo].[project]
   (id, name, creation_date, project_type_id)
VALUES
   (1, 'Project Avengers', SYSDATETIMEOFFSET(), 1)
GO
INSERT INTO [dbo].[project_scope]
   (project_id, scope_id)
VALUES
   (1, 1),
   (1, 2),
   (1, 3)
GO
IF OBJECT_ID('[dbo].[udfGetProjectFullName]', 'FN') IS NOT NULL
   DROP FUNCTION udfGetProjectFullName
GO
CREATE FUNCTION [dbo].[udfGetProjectFullName](@project_id INT)
RETURNS NVARCHAR(255)
AS
BEGIN
   DECLARE @fullname NVARCHAR(255)

   SELECT @fullname = CONCAT('[', STRING_AGG(s.name, ' - ') WITHIN GROUP (ORDER BY s.name), '] ', p.creation_date, ' ', pt.name, ' ', p.name)
   FROM project AS p
      INNER JOIN project_type AS pt on pt.id = p.project_type_id
      LEFT JOIN project_scope AS ps on ps.project_id = p.id
      LEFT JOIN scope AS s on s.id = ps.scope_id
   WHERE p.id = 1
   GROUP BY p.id, p.name, p.creation_date, pt.name

   RETURN @fullname
END
GO
SELECT id, dbo.udfGetProjectFullName(id) as [full_name]
FROM project
GO

您可以使用可以封装此内容的 ViewModel,并且 model 可以具有可以评估上述内容的属性。

在我的用例中,我使用了 AutoMapper,并创建了一个映射配置,我也在其中进行了转换。 比如,我正在根据 Db 的 EndDate 属性确定我在 UI 上需要的用户 Active 属性。

使用自动映射器,我实现了这一点:

public static IEnumerable<Models.ViewModels.User> AsViewModel(this IEnumerable<Models.DataModels.User> userData)
{
        var config = new MapperConfiguration(cfg => {                
            cfg.CreateMap<Models.DataModels.User, Models.ViewModels.User>()
            .ForMember(vm => vm.Active, d => d.MapFrom(m => !m.EndDate.HasValue))
            .ReverseMap();
        });
        var mapper = config.CreateMapper();
        return mapper.Map<IEnumerable<Models.DataModels.User>, IEnumerable<Models.ViewModels.User>>(userData);
}

我通过 EF 与 Db 交互的数据 Model 是:

namespace PM.Models.DataModels
{
    [Table("Users")]
    public class User
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid Id { get; set; }
        [Required]
        public string FirstName { get; set; }
        [Required]
        public string LastName { get; set; }
        [Required]
        public string UserId { get; set; }
        [DefaultValue("getdate()")]
        public DateTime Created
        {
            get { return _createdValue == DateTime.MinValue ? DateTime.Now : _createdValue; }
            set { _createdValue = value; }
        }
        private DateTime _createdValue;

        public DateTime? EndDate { get; set; }
    }
}

以及我用来与表示层交互的 Viewmodel:

namespace PM.Models.ViewModels
{
    public class User
    {
        public Guid Id { get; set; }
        [Required]
        public string FirstName { get; set; }
        //[Required]
        public string LastName { get; set; }
        public string FullName { get { return string.Format($"{LastName}, {FirstName}"); } }
        [Required]
        public string UserId { get; set; }
        public bool Active { get; private set; }
    }
}

就像另一个答案指出的那样,您可以简单地使用 Model 来封装您的 FullName,或者在扩展的业务用例中,您可以转换为复杂的评估。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM