EF Core实体。导航(一对多)加入dbquery(一对一)

时间:2019-12-21 13:03:17

标签: c# sql linq ef-core-2.2

实体的结构(不包含所有不需要的属性):

public class EventType
{
    // omitted properties not relevant to question
    public int Id {get; private set; }

    public IReadOnlyCollection<EventTypePermission> Permissions => _permissions;
}

public class EventTypePermission
{
    public long Id {get; private set; }
    public int EventTypeId { get; private set; }
    public Guid RoleId { get; private set; } // id of a role -> role is defined in a different schema and context. Created a view

    public Permission Permission { get; private set; } // is an enum
}

public class RoleOnMetadata
{
    public Guid RoleId { get; private set; }
    public string Name { get; private set; }
}

查看

private static void CreateViewRoles(this MigrationBuilder builder)
{
    var sql = $@"
        CREATE VIEW {RolesOnMetadataConfiguration.GetViewName()}
        /*WITH SCHEMABINDING*/
        AS
        SELECT      Id as RoleId, Name
        FROM        [identity].AspNetRoles
    ;";

    builder.Sql(sql);
}

关系:

  • 类型可以具有0、1或多个TypePermissions
  • TypePermission只能具有一个角色

EF核心

我没有将RoleOnMetadata作为导航属性添加到EventTypePermission,因为实体不能将DbQueries作为导航属性。

我想要的

通过更少的数据库调用获得结果,并且没有太多重复数据。

结果对象:

public class EventTypeWithPermissions
{
    // omitted properties not relevant to question
    public int Id { get; set; }

    public List<EventTypeRole> ReadOnlyRoles { get; set; }
    public List<EventTypeRole> WriteRoles { get; set; }
}

public class EventTypeRole
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}
到目前为止,我发现的

SQL解决方案:我不喜欢这种解决方案,因为它返回了过多的重复数据,这些数据必须通过网络传输。每行都将返回所有EventData。但这只是一个电话。

select 
    e.Id EventTypeId, e.Name, e.Description, e.Color, e.Reminder, 
    p.Id PermissionId, p.Permission, r.RoleId, r.Name as RoleName
from 
    [planningapp_dev].[metadata].[EventTypes] as e
join 
    [planningapp_dev].[metadata].[Permissions] as p on p.EventTypeId = e.Id
join
    [planningapp_dev].[metadata].RoleOnMetadata as r on r.RoleId = p.RoleId

我可以将其转换为linq语句,并在本地进行一些分组

EF核心解决方案

虽然在这里我没有太多的重复数据,但有太多的数据库调用。

var eventTypes = _context.EventTypes
            .AsNoTracking()
            .Include(x => x.Permissions)
            .Select(x => 
                new EventType {
                    Description = x.Description, Id = x.Id, Name = x.Name, Color = x.Color, Reminder = x.Reminder,
                    ReadOnlyRoles = x.Permissions
                        .Where(permission => permission.Permission == Permission.Read)
                        .Select(permission => new EventTypeRole{ Id = permission.RoleId, Name = _context.Roles.AsNoTracking().Single(role => role.RoleId == permission.RoleId).Name })
                        .ToList(),

                    WriteRoles = x.Permissions
                    .Where(permission => permission.Permission == Permission.Write)
                    .Select(permission => new EventTypeRole { Id = permission.RoleId, Name = _context.Roles.AsNoTracking().Single(role => role.RoleId == permission.RoleId).Name })
                    .ToList()
                }
            );

这里发生的是我使用从类型到权限的常规导航属性。然后通过从数据库上下文中获取每个角色的名称来构建事件类型角色。

SQL查询正在执行:

查询事件类型数据:

    Microsoft.EntityFrameworkCore.Database.Command[20101]
    Executed DbCommand (4ms) [Parameters=[@__TypedProperty_0='50'], CommandType='Text', CommandTimeout='30']
    SELECT TOP(@__TypedProperty_0) [x].[Description], [x].[Id], [x].[Name], [x].[Color], [x].[Reminder]
    FROM [metadata].[EventTypes] AS [x]
    ORDER BY [x].[Name], [x].[Id]

2个查询,一个用于读取权限,一个用于写入权限:

 Executed DbCommand (4ms) [Parameters=[@__TypedProperty_0='50'], CommandType='Text', CommandTimeout='30']
  SELECT [t].[Name], [t].[Id], [x.Permissions].[RoleId] AS [Id0], [x.Permissions].[EventTypeId]
  FROM [metadata].[Permissions] AS [x.Permissions]
  INNER JOIN (
      SELECT TOP(@__TypedProperty_0) [x0].[Name], [x0].[Id]
      FROM [metadata].[EventTypes] AS [x0]
      ORDER BY [x0].[Name], [x0].[Id]
  ) AS [t] ON [x.Permissions].[EventTypeId] = [t].[Id]
  WHERE [x.Permissions].[Permission] = N'Read'
  ORDER BY [t].[Name], [t].[Id]


 Executed DbCommand (4ms) [Parameters=[@__TypedProperty_0='50'], CommandType='Text', CommandTimeout='30']
  SELECT [t].[Name], [t].[Id], [x.Permissions].[RoleId] AS [Id0], [x.Permissions].[EventTypeId]
  FROM [metadata].[Permissions] AS [x.Permissions]
  INNER JOIN (
      SELECT TOP(@__TypedProperty_0) [x0].[Name], [x0].[Id]
      FROM [metadata].[EventTypes] AS [x0]
      ORDER BY [x0].[Name], [x0].[Id]
  ) AS [t] ON [x.Permissions].[EventTypeId] = [t].[Id]
  WHERE [x.Permissions].[Permission] = N'Write'
  ORDER BY [t].[Name], [t].[Id]

对于每个分配的角色,将执行以下选择:

Executed DbCommand (2ms) [Parameters=[@_outer_RoleId1='3bf70454-6026-4a76-b488-28db9fce1b36'], CommandType='Text', CommandTimeout='30']
  SELECT TOP(2) [role2].[Name]
  FROM [metadata].[RoleOnMetadata] AS [role2]
  WHERE [role2].[RoleId] = @_outer_RoleId1

一种选择是首先获取所有角色,然后查询内存中的角色,而不是每次都在db上进行新选择。

这已经可以解决我想要获取的每个角色名称的SQL调用。再说一次,我只想查询我实际需要的角色。但是,目前不应该有那么多角色。

我需要的下一个优化是摆脱两个调用,一个用于读取,一个用于写入权限。

我可以就如何最好地查询最佳优化结果使用一些建议,并且如上所述,如果可能的话,只打一个电话而不是两个电话

备注:对于OData,它必须作为IQueryable返回

0 个答案:

没有答案