[英]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)
谢谢你的时间。
这取决于数据量、读取/更新频率以及排序发生的频率。 您是否考虑过使用 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.