Skip to content

Commit

Permalink
[ODS-5419] Add Discriminator to edfi.Descriptor (#1122)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmcelhanon committed Aug 22, 2024
1 parent 0d82779 commit 36421bc
Show file tree
Hide file tree
Showing 23 changed files with 6,523 additions and 3,358 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Dapper;
using EdFi.Ods.Common.Context;
using EdFi.Ods.Common.Database.Querying;
using EdFi.Ods.Common.Database.Querying.Dialects;
using EdFi.Ods.Common.Exceptions;
using EdFi.Ods.Common.Infrastructure.Configuration;
using EdFi.Ods.Common.Models;
Expand All @@ -25,26 +28,29 @@ namespace EdFi.Ods.Common.Descriptors;
public class DescriptorDetailsProvider : IDescriptorDetailsProvider
{
private readonly IContextStorage _contextStorage;
private readonly Dialect _dialect;
private readonly ISessionFactory _sessionFactory;

private readonly Lazy<Dictionary<string, string>> _descriptorTypeNameByEntityName;
private readonly Lazy<Dictionary<string, string>> _discriminatorByDescriptorName;

private readonly ILog _logger = LogManager.GetLogger(typeof(DescriptorDetailsProvider));

public DescriptorDetailsProvider(
ISessionFactory sessionFactory,
IDomainModelProvider domainModelProvider,
IContextStorage contextStorage)
IContextStorage contextStorage,
Dialect dialect)
{
_contextStorage = contextStorage;
_dialect = dialect;
_sessionFactory = sessionFactory;

_descriptorTypeNameByEntityName = new Lazy<Dictionary<string, string>>(
_discriminatorByDescriptorName = new Lazy<Dictionary<string, string>>(
() => domainModelProvider.GetDomainModel()
.Entities.Where(e => e.IsDescriptorEntity)
.ToDictionary(
e => e.Name,
e => e.EntityTypeFullName(e.SchemaProperCaseName())));
e => e.FullName.ToString()));
}

/// <inheritdoc cref="IDescriptorDetailsProvider.GetAllDescriptorDetails" />
Expand All @@ -56,33 +62,23 @@ public IList<DescriptorDetails> GetAllDescriptorDetails()

using (var session = _sessionFactory.OpenSession())
{
var queries = GetQueries(session).ToList();
var qb = GetDescriptorQueryBuilder();
var template = qb.BuildTemplate();

var results = queries
.SelectMany(x => x.detailQuery
.Select(d =>
var records = session.Connection.Query<DescriptorRecord>(template.RawSql);

var results = records.Select(
r => new DescriptorDetails()
{
// Assign the descriptor name to the details
d.DescriptorName = x.descriptorName;
return d;
DescriptorId = r.DescriptorId,
DescriptorName = r.Discriminator?.Substring(r.Discriminator.IndexOf('.') + 1),
CodeValue = r.CodeValue,
Namespace = r.Namespace
})
.ToList())
.ToList();

return results;
}

IEnumerable<(string descriptorName, IEnumerable<DescriptorDetails> detailQuery)> GetQueries(ISession session)
{
foreach (string descriptorName in _descriptorTypeNameByEntityName.Value.Keys)
{
var query =
GetDescriptorCriteria(descriptorName, session)
.Future<DescriptorDetails>();

yield return (descriptorName, query);
}
}
}
catch (Exception ex)
{
Expand All @@ -96,45 +92,52 @@ public IList<DescriptorDetails> GetAllDescriptorDetails()
}
}

private QueryBuilder GetDescriptorQueryBuilder()
{
var qb = new QueryBuilder(_dialect)
.From("edfi.Descriptor")
.Select("DescriptorId", "CodeValue", "Namespace", "Discriminator");

return qb;
}

/// <inheritdoc cref="IDescriptorDetailsProvider.GetDescriptorDetails(string,int)" />
public DescriptorDetails GetDescriptorDetails(string descriptorName, int descriptorId)
{
using var session = _sessionFactory.OpenSession();

return GetDescriptorCriteria(descriptorName, session, descriptorId).UniqueResult<DescriptorDetails>();

var qb = GetFilteredDescriptorQueryBuilder(descriptorName, descriptorId);
var template = qb.BuildTemplate();

return session.Connection.QuerySingleOrDefault<DescriptorDetails>(template.RawSql, template.Parameters);
}

/// <inheritdoc cref="IDescriptorDetailsProvider.GetDescriptorDetails(string,string)" />
public DescriptorDetails GetDescriptorDetails(string descriptorName, string uri)
{
using var session = _sessionFactory.OpenSession();

return GetDescriptorCriteria(descriptorName, session, uri: uri).UniqueResult<DescriptorDetails>();

var qb = GetFilteredDescriptorQueryBuilder(descriptorName, uri: uri);
var template = qb.BuildTemplate();

return session.Connection.QuerySingleOrDefault<DescriptorDetails>(template.RawSql, template.Parameters);
}

private ICriteria GetDescriptorCriteria(
private QueryBuilder GetFilteredDescriptorQueryBuilder(
string descriptorName,
ISession session,
int? descriptorId = null,
string uri = null)
{
if (!_descriptorTypeNameByEntityName.Value.TryGetValue(descriptorName, out string descriptorTypeName))
if (!_discriminatorByDescriptorName.Value.TryGetValue(descriptorName, out string discriminator))
{
throw new ArgumentException($"Descriptor '{descriptorName}' is not a known descriptor entity name.");
}

var descriptorIdColumnName = descriptorName + "Id";

var criteria = session.CreateCriteria(descriptorTypeName)
.SetProjection(
Projections.ProjectionList()
.Add(Projections.Property(descriptorIdColumnName), "DescriptorId")
.Add(Projections.Property("Namespace"), "Namespace")
.Add(Projections.Property("CodeValue"), "CodeValue"));
var qb = GetDescriptorQueryBuilder().Where("Discriminator", discriminator);

if (descriptorId.HasValue)
{
criteria.Add(Restrictions.Eq(descriptorIdColumnName, descriptorId.Value));
qb.Where("DescriptorId", descriptorId.Value);
}

if (uri != null)
Expand All @@ -146,12 +149,18 @@ private ICriteria GetDescriptorCriteria(
throw new ValidationException($"{descriptorName} value '{uri}' is not a valid value for use as a descriptor (a '#' is expected to delineate the namespace and code value portions).");
}

criteria.Add(Restrictions.Eq("Namespace", uri.Substring(0, pos)));
criteria.Add(Restrictions.Eq("CodeValue", uri.Substring(pos + 1)));
qb.Where("Namespace", uri.Substring(0, pos));
qb.Where("CodeValue", uri.Substring(pos + 1));
}

criteria.SetResultTransformer(Transformers.AliasToBean<DescriptorDetails>());
return qb;
}

return criteria;
private class DescriptorRecord
{
public int DescriptorId { get; set; }
public string Discriminator { get; set; }
public string Namespace { get; set; }
public string CodeValue { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public class NHibernateConfigurator : INHibernateConfigurator
private readonly IDictionary<string, HbmBag[]> _entityExtensionHbmBagsByEntityName;
private readonly IExtensionNHibernateConfigurationProvider[] _extensionConfigurationProviders;
private readonly IDictionary<string, HbmSubclass[]> _extensionDerivedEntityByEntityName;
private readonly IDictionary<string, HbmJoinedSubclass[]> _extensionDescriptorByEntityName;
private readonly IOrmMappingFileDataProvider _ormMappingFileDataProvider;
private readonly Func<IEntityAuthorizer> _entityAuthorizerResolver;
private readonly IAuthorizationContextProvider _authorizationContextProvider;
Expand Down Expand Up @@ -77,14 +76,6 @@ public NHibernateConfigurator(IEnumerable<IExtensionNHibernateConfigurationProvi
x => x.SelectMany(y => y.Value)
.ToArray());

_extensionDescriptorByEntityName = _extensionConfigurationProviders
.SelectMany(x => x.NonDiscriminatorBasedHbmJoinedSubclassesByEntityName)
.GroupBy(x => x.Key)
.ToDictionary(
x => x.Key,
x => x.SelectMany(y => y.Value)
.ToArray());

_extensionDerivedEntityByEntityName = _extensionConfigurationProviders
.SelectMany(x => x.DiscriminatorBasedHbmSubclassesByEntityName)
.GroupBy(x => x.Key)
Expand Down Expand Up @@ -172,8 +163,6 @@ private void Configuration_BeforeBindMapping(object sender, BindMappingEventArgs
MapJoinedSubclassesToCoreEntity(
classMappingByEntityName, joinedSubclassMappingByEntityName, subclassJoinMappingByEntityName);

MapDescriptorToCoreDescriptorEntity(classMappingByEntityName);

MapDerivedEntityToCoreEntity(classMappingByEntityName);
}

Expand All @@ -198,27 +187,6 @@ void MapDerivedEntityToCoreEntity(Dictionary<string, HbmClass> classMappingByEnt
}
}

void MapDescriptorToCoreDescriptorEntity(Dictionary<string, HbmClass> classMappingByEntityName)
{
// foreach entity name, look in core mapping file (e.mapping) for core entity mapping and if found
// concat new extension HbmJoinedSubclass to current set of Ed-Fi entity HbmJoinedSubclasses.
foreach (string entityName in _extensionDescriptorByEntityName.Keys)
{
if (!classMappingByEntityName.TryGetValue(entityName, out HbmClass classMapping))
{
throw new MappingException(
$"The subclass extension to entity '{entityName}' could not be applied because the class mapping could not be found.");
}

var hbmJoinedSubclasses = _extensionDescriptorByEntityName[entityName]
.Select(x => (object) x)
.ToArray();

classMapping.Items1 = (classMapping.Items1 ?? Array.Empty<object>()).Concat(hbmJoinedSubclasses)
.ToArray();
}
}

void MapJoinedSubclassesToCoreEntity(Dictionary<string, HbmClass> classMappingByEntityName,
Dictionary<string, HbmJoinedSubclass> joinedSubclassMappingByEntityName,
Dictionary<string, HbmJoin> subclassJoinMappingByEntityName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.Linq;
using EdFi.Common;
using EdFi.Common.Configuration;
using EdFi.Ods.Common.Configuration;
using EdFi.Ods.Common.Constants;
using EdFi.Ods.Common.Conventions;
using EdFi.Ods.Common.Dtos;
Expand Down Expand Up @@ -76,14 +75,6 @@ public IDictionary<string, HbmBag[]> AggregateExtensionHbmBagsByEntityName
get => CreateAggregateExtensionHbmBagsByEntityName();
}

/// <summary>
/// Returns a dictionary keyed by Ed-Fi standard base entity name, containing the derived entity discriminators as HbmJoinedSubclasses.
/// </summary>
public IDictionary<string, HbmJoinedSubclass[]> NonDiscriminatorBasedHbmJoinedSubclassesByEntityName
{
get => CreateNonDiscriminatorBasedHbmJoinedSubclassesByEntityName();
}

/// <summary>
/// Returns a dictionary keyed by Ed-Fi standard base entity name, containing the derived entity as HbmSubclass. (Discriminator is required)
/// </summary>
Expand Down Expand Up @@ -148,63 +139,14 @@ private Dictionary<string, HbmBag[]> CreateAggregateExtensionHbmBagsByEntityName
.ToDictionary(x => x.Key, x => x.ToArray());
}

private Dictionary<string, HbmJoinedSubclass[]> CreateNonDiscriminatorBasedHbmJoinedSubclassesByEntityName()
{
return Enumerable.Select(
_domainModel.Entities
.Where(e => e.IsDerived && e.IsExtensionEntity && e.Schema.Equals(PhysicalName) && e.IsDescriptorEntity), e => new
{
EntityName = !e.BaseEntity.IsAbstract
? $"{e.BaseEntity.Name}Base"
: e.BaseEntity.Name,
HbmJoinedSubclass = CreateHbmJoinSubclass(e)
})
.GroupBy(x => x.EntityName, x => x.HbmJoinedSubclass)
.ToDictionary(x => x.Key, x => x.ToArray());

HbmJoinedSubclass CreateHbmJoinSubclass(Entity entity)
{
var properties = Enumerable.ToArray<object>(
entity.Properties
.OrderBy(p => p.PropertyName)
.Select(p => CreateHbmProperty(p, entity)));

var references = Enumerable.ToArray<object>(entity.NavigableOneToOnes.Select(CreateHbmBag));

return new HbmJoinedSubclass
{
table = entity.TableName(_databaseEngine),
schema = PhysicalName,
name = GetExtensionEntityAssemblyQualifiedName(entity),
key = CreateHbmKey(entity.Properties.Where(p => p.IsIdentifying).OrderBy(p => p.PropertyName)),
Items = properties.Concat(references).ToArray(),
lazy = false
};

HbmBag CreateHbmBag(AssociationView association)
{
return new
HbmBag
{
name = $"{association.Name}",
cascade = "all-delete-orphan",
inverse = true,
lazy = HbmCollectionLazy.False,
key = CreateHbmKey(
association.OtherEntity.Properties.Where(p => p.IsIdentifying).OrderBy(p => p.PropertyName)),
Item = new HbmOneToMany {@class = GetExtensionEntityAssemblyQualifiedName(association.OtherEntity)}
};
}
}
}

private Dictionary<string, HbmSubclass[]> CreateDiscriminatorBasedHbmSubclassesByEntityName()
{
return Enumerable.Select(
_domainModel.Entities
.Where(
e => e.IsDerived && e.IsExtensionEntity && e.Schema.Equals(PhysicalName) &&
!e.BaseEntity.Schema.Equals(PhysicalName) && !e.IsDescriptorEntity), e => new
!e.BaseEntity.Schema.Equals(PhysicalName)),
e => new
{
EntityName = !e.BaseEntity.IsAbstract
? $"{e.BaseEntity.Name}Base"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ public interface IExtensionNHibernateConfigurationProvider
/// </summary>
IDictionary<string, HbmBag[]> AggregateExtensionHbmBagsByEntityName { get; }

/// <summary>
/// Extensions that are derived from a class without a discriminator (e.g. a descriptor).
/// </summary>
IDictionary<string, HbmJoinedSubclass[]> NonDiscriminatorBasedHbmJoinedSubclassesByEntityName { get; }

/// <summary>
/// Extensions that are derived from either a class using a discriminator.
/// </summary>
Expand Down
4 changes: 0 additions & 4 deletions Application/EdFi.Ods.Common/Models/Domain/EntityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,6 @@ public static bool HasDiscriminator(this Entity entity)
if (entity.IsDerived)
return false;

// Ed-Fi descriptors cannot be derived
if (entity.IsDescriptorBaseEntity())
return false;

// SchoolYearType is also not derivable
if (entity.FullName.Equals(new FullName(EdFiConventions.PhysicalSchemaName, "SchoolYearType")))
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ IEnumerable<SelectColumn> GetSelectColumns(ResourceProperty resourceProperty)
yield break;
}

if (IsDerivedFromEntityWithDiscriminator(entityProperty.Entity))
if (entityProperty.Entity.IsDerived && !entityProperty.Entity.IsDescriptorEntity)
{
yield return new SelectColumn
{
Expand Down Expand Up @@ -171,8 +171,6 @@ IEnumerable<SelectColumn> GetSelectColumns(ResourceProperty resourceProperty)
JsonPropertyName = resourceProperty.JsonPropertyName,
};
}

bool IsDerivedFromEntityWithDiscriminator(Entity entity) => entity.BaseEntity?.HasDiscriminator() == true;
}
}
}
Loading

0 comments on commit 36421bc

Please sign in to comment.