mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-30 19:54:16 -04:00
Add custom relations on library items (first pass)
This commit is contained in:
parent
eed058c891
commit
ba83edd26c
@ -2,6 +2,7 @@
|
|||||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.MaintainabilityRules">
|
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.MaintainabilityRules">
|
||||||
<Rule Id="SA1413" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
<Rule Id="SA1413" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
||||||
<Rule Id="SA1414" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
<Rule Id="SA1414" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
||||||
|
<Rule Id="SA1114" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
||||||
</Rules>
|
</Rules>
|
||||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.OrderingRules">
|
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.OrderingRules">
|
||||||
<Rule Id="SA1201" Action="None" /> <!-- ElementsMustAppearInTheCorrectOrder -->
|
<Rule Id="SA1201" Action="None" /> <!-- ElementsMustAppearInTheCorrectOrder -->
|
||||||
|
@ -31,6 +31,10 @@ namespace Kyoo.Abstractions.Models.Attributes
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? RelationID { get; }
|
public string? RelationID { get; }
|
||||||
|
|
||||||
|
public string? Sql { get; set; }
|
||||||
|
|
||||||
|
public string? On { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="LoadableRelationAttribute"/>.
|
/// Create a new <see cref="LoadableRelationAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -152,7 +152,23 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// The first episode of this show.
|
/// The first episode of this show.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Projectable(UseMemberBody = nameof(_FirstEpisode), OnlyOnInclude = true)]
|
[Projectable(UseMemberBody = nameof(_FirstEpisode), OnlyOnInclude = true)]
|
||||||
[LoadableRelation] public Episode? FirstEpisode { get; set; }
|
[LoadableRelation(
|
||||||
|
// language=PostgreSQL
|
||||||
|
Sql = """
|
||||||
|
select
|
||||||
|
fe.*
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
e.*,
|
||||||
|
row_number() over (partition by e.show_id order by e.absolute_number, e.season_number, e.episode_number) as number
|
||||||
|
from
|
||||||
|
episodes as e) as fe
|
||||||
|
where
|
||||||
|
fe.number <= 1
|
||||||
|
""",
|
||||||
|
On = "show_id"
|
||||||
|
)]
|
||||||
|
public Episode? FirstEpisode { get; set; }
|
||||||
|
|
||||||
private Episode? _FirstEpisode => Episodes!
|
private Episode? _FirstEpisode => Episodes!
|
||||||
.OrderBy(x => x.AbsoluteNumber)
|
.OrderBy(x => x.AbsoluteNumber)
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -47,32 +46,42 @@ public class Include<T>
|
|||||||
if (string.IsNullOrEmpty(fields))
|
if (string.IsNullOrEmpty(fields))
|
||||||
return new Include<T>();
|
return new Include<T>();
|
||||||
|
|
||||||
|
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
|
||||||
return new Include<T>
|
return new Include<T>
|
||||||
{
|
{
|
||||||
Metadatas = fields.Split(',').Select<string, Metadata>(key =>
|
Metadatas = fields.Split(',').SelectMany(key =>
|
||||||
{
|
{
|
||||||
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
|
var relations = types
|
||||||
PropertyInfo? prop = types
|
.Select(x => x.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)!)
|
||||||
.Select(x => x.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance))
|
.Select(prop => (prop, attr: prop?.GetCustomAttribute<LoadableRelationAttribute>()!))
|
||||||
.FirstOrDefault();
|
.Where(x => x.prop != null && x.attr != null)
|
||||||
LoadableRelationAttribute? attr = prop?.GetCustomAttribute<LoadableRelationAttribute>();
|
.ToList();
|
||||||
if (prop == null || attr == null)
|
if (!relations.Any())
|
||||||
throw new ValidationException($"No loadable relation with the name {key}.");
|
throw new ValidationException($"No loadable relation with the name {key}.");
|
||||||
if (attr.RelationID != null)
|
return relations
|
||||||
return new SingleRelation(prop.Name, prop.PropertyType, attr.RelationID);
|
.Select(x =>
|
||||||
|
{
|
||||||
|
(PropertyInfo prop, LoadableRelationAttribute attr) = x;
|
||||||
|
|
||||||
// Multiples relations are disabled due to:
|
if (attr.RelationID != null)
|
||||||
// - Cartesian Explosions perfs
|
return new SingleRelation(prop.Name, prop.PropertyType, attr.RelationID) as Metadata;
|
||||||
// - Code complexity added.
|
|
||||||
// if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType != typeof(string))
|
// Multiples relations are disabled due to:
|
||||||
// {
|
// - Cartesian Explosions perfs
|
||||||
// // The property is either a list or a an array.
|
// - Code complexity added.
|
||||||
// return new MultipleRelation(
|
// if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType != typeof(string))
|
||||||
// prop.Name,
|
// {
|
||||||
// prop.PropertyType.GetElementType() ?? prop.PropertyType.GenericTypeArguments.First()
|
// // The property is either a list or a an array.
|
||||||
// );
|
// return new MultipleRelation(
|
||||||
// }
|
// prop.Name,
|
||||||
throw new NotImplementedException();
|
// prop.PropertyType.GetElementType() ?? prop.PropertyType.GenericTypeArguments.First()
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
if (attr.Sql != null && attr.On != null)
|
||||||
|
return new CustomRelation(prop.Name, prop.PropertyType, attr.Sql, attr.On, prop.DeclaringType!);
|
||||||
|
throw new NotImplementedException();
|
||||||
|
})
|
||||||
|
.Distinct();
|
||||||
}).ToArray()
|
}).ToArray()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -80,4 +89,6 @@ public class Include<T>
|
|||||||
public abstract record Metadata(string Name);
|
public abstract record Metadata(string Name);
|
||||||
|
|
||||||
public record SingleRelation(string Name, Type type, string RelationIdName) : Metadata(Name);
|
public record SingleRelation(string Name, Type type, string RelationIdName) : Metadata(Name);
|
||||||
|
|
||||||
|
public record CustomRelation(string Name, Type type, string Sql, string On, Type Declaring) : Metadata(Name);
|
||||||
}
|
}
|
||||||
|
@ -131,25 +131,36 @@ namespace Kyoo.Core.Controllers
|
|||||||
) ProcessInclude<T>(Include<T> include, Dictionary<string, Type> config)
|
) ProcessInclude<T>(Include<T> include, Dictionary<string, Type> config)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
|
int relation = 0;
|
||||||
Dictionary<string, Type> retConfig = new();
|
Dictionary<string, Type> retConfig = new();
|
||||||
StringBuilder join = new();
|
StringBuilder join = new();
|
||||||
|
|
||||||
foreach (Include<T>.Metadata metadata in include.Metadatas)
|
foreach (Include<T>.Metadata metadata in include.Metadatas)
|
||||||
{
|
{
|
||||||
|
relation++;
|
||||||
switch (metadata)
|
switch (metadata)
|
||||||
{
|
{
|
||||||
case Include<T>.SingleRelation(var name, var type, var rid):
|
case Include<T>.SingleRelation(var name, var type, var rid):
|
||||||
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
||||||
retConfig.Add(tableName, type);
|
retConfig.Add($"r{relation}", type);
|
||||||
join.AppendLine($"left join {tableName} on {tableName}.id = {_Property(rid, config)}");
|
join.AppendLine($"left join {tableName} as r{relation} on r{relation}.id = {_Property(rid, config)}");
|
||||||
break;
|
break;
|
||||||
|
case Include<T>.CustomRelation(var name, var type, var sql, var on, var declaring):
|
||||||
|
string owner = config.First(x => x.Value == declaring).Key;
|
||||||
|
retConfig.Add($"r{relation}", type);
|
||||||
|
join.AppendLine($"left join ({sql}) as r{relation} on r{relation}.{on} = {owner}.id");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
T Map(T item, IEnumerable<object> relations)
|
T Map(T item, IEnumerable<object> relations)
|
||||||
{
|
{
|
||||||
foreach ((string name, object value) in include.Fields.Zip(relations))
|
foreach ((string name, object? value) in include.Fields.Zip(relations))
|
||||||
{
|
{
|
||||||
|
if (value == null)
|
||||||
|
continue;
|
||||||
PropertyInfo? prop = item.GetType().GetProperty(name);
|
PropertyInfo? prop = item.GetType().GetProperty(name);
|
||||||
if (prop != null)
|
if (prop != null)
|
||||||
prop.SetValue(item, value);
|
prop.SetValue(item, value);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user