我正在使用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翻译。 我在应用程序的多个数组中显示此属性,并且需要能够根据该字段对结果进行排序。
对我而言,拥有此计算字段的最佳方法是什么?
无论连接到该数据库的哪个客户端,我都必须能够在该字段上进行选择和排序。 (例如可以是PowerBI)
感谢您的时间。
答案 0 :(得分:2)
这取决于数据量,读取/更新的频率以及排序的频率。 您是否考虑过使用SQL View?具有索引,实现等的能力。
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
}
}
}
答案 1 :(得分:1)
您可以将其作为类本身的属性。
只需通过重写DbContext.OnModelCreating并使用EntityTypeBuilder.Ignore方法来通知EF忽略此属性。
答案 2 :(得分:1)
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
答案 3 :(得分:-2)
您可以使用可以封装此视图的ViewModel,并且该模型可以具有可以评估以上内容的属性。
在我的用例中,我使用了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交互的我的数据模型是:
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; }
}
}
就像指出的其他答案一样,您可以简单地使用模型来封装您的全名,或者在扩展的业务用例中,可以转换为复杂的评估。