Metadata coming together for XBMC
This commit is contained in:
parent
8263ab1d0f
commit
b50e16a456
|
@ -200,6 +200,10 @@ namespace NzbDrone.Common
|
|||
return File.ReadAllText(filePath);
|
||||
}
|
||||
|
||||
public virtual void WriteAllText(string filename, string contents)
|
||||
{
|
||||
File.WriteAllText(filename, contents);
|
||||
}
|
||||
|
||||
public static bool PathEquals(string firstPath, string secondPath)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
using System.Data;
|
||||
using Migrator.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migrations
|
||||
{
|
||||
|
||||
[Migration(20120707)]
|
||||
public class Migration20120707 : NzbDroneMigration
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Database.AddTable("MetadataDefinitions", new[]
|
||||
{
|
||||
new Column("Id", DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity),
|
||||
new Column("Enable", DbType.Boolean, ColumnProperty.NotNull),
|
||||
new Column("MetadataProviderType", DbType.String, ColumnProperty.NotNull),
|
||||
new Column("Name", DbType.String, ColumnProperty.NotNull)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Model
|
||||
namespace NzbDrone.Core.Model.Metadata
|
||||
{
|
||||
public class MisnamedEpisodeModel
|
||||
{
|
||||
|
|
|
@ -81,5 +81,22 @@ namespace NzbDrone.Core.Providers
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Download(string remotePath, string filename)
|
||||
{
|
||||
var url = BANNER_URL_PREFIX + remotePath;
|
||||
|
||||
try
|
||||
{
|
||||
_httpProvider.DownloadFile(url, filename);
|
||||
logger.Trace("Successfully download banner from '{0}' to '{1}'", url, filename);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = String.Format("Failed to download Banner from '{0}' to '{1}'", url, filename);
|
||||
logger.DebugException(message, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -501,6 +501,27 @@ namespace NzbDrone.Core.Providers.Core
|
|||
set { SetValue("PlexPassword", value); }
|
||||
}
|
||||
|
||||
public virtual Boolean MetadataEnabled
|
||||
{
|
||||
get { return GetValueBoolean("MetadataEnabled"); }
|
||||
|
||||
set { SetValue("MetadataEnabled", value); }
|
||||
}
|
||||
|
||||
public virtual Boolean MetadataXbmcEnabled
|
||||
{
|
||||
get { return GetValueBoolean("MetadataXbmcEnabled"); }
|
||||
|
||||
set { SetValue("MetadataXbmcEnabled", value); }
|
||||
}
|
||||
|
||||
public virtual Boolean MetadataUseBanners
|
||||
{
|
||||
get { return GetValueBoolean("MetadataUseBanners"); }
|
||||
|
||||
set { SetValue("MetadataUseBanners", value); }
|
||||
}
|
||||
|
||||
private string GetValue(string key)
|
||||
{
|
||||
return GetValue(key, String.Empty);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Providers.Core;
|
||||
using NzbDrone.Core.Repository;
|
||||
using TvdbLib.Data;
|
||||
|
||||
namespace NzbDrone.Core.Providers.Metadata
|
||||
{
|
||||
|
@ -10,43 +12,42 @@ namespace NzbDrone.Core.Providers.Metadata
|
|||
{
|
||||
protected readonly Logger _logger;
|
||||
protected readonly ConfigProvider _configProvider;
|
||||
protected readonly DiskProvider _diskProvider;
|
||||
protected readonly BannerProvider _bannerProvider;
|
||||
protected readonly EpisodeProvider _episodeProvider;
|
||||
|
||||
protected MetadataBase(ConfigProvider configProvider)
|
||||
protected MetadataBase(ConfigProvider configProvider, DiskProvider diskProvider,
|
||||
BannerProvider bannerProvider, EpisodeProvider episodeProvider)
|
||||
{
|
||||
_configProvider = configProvider;
|
||||
_diskProvider = diskProvider;
|
||||
_bannerProvider = bannerProvider;
|
||||
_episodeProvider = episodeProvider;
|
||||
_logger = LogManager.GetLogger(GetType().ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name for the notification provider
|
||||
/// Gets the name for the metabase provider
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Performs the on grab action
|
||||
/// Creates metadata for a series
|
||||
/// </summary>
|
||||
/// <param name = "message">The message to send to the receiver</param>
|
||||
public abstract void OnGrab(string message);
|
||||
/// <param name = "series">The series to create the metadata for</param>
|
||||
/// <param name = "tvDbSeries">Series information from TheTvDb</param>
|
||||
public abstract void ForSeries(Series series, TvdbSeries tvDbSeries);
|
||||
|
||||
/// <summary>
|
||||
/// Performs the on download action
|
||||
/// Creates metadata for the episode file
|
||||
/// </summary>
|
||||
/// <param name = "message">The message to send to the receiver</param>
|
||||
/// <param name = "series">The Series for the new download</param>
|
||||
public abstract void OnDownload(string message, Series series);
|
||||
/// <param name = "episodeFile">The episode file to create the metadata</param>
|
||||
/// <param name = "tvDbSeries">Series information from TheTvDb</param>
|
||||
public abstract void ForEpisodeFile(EpisodeFile episodeFile, TvdbSeries tvDbSeries);
|
||||
|
||||
/// <summary>
|
||||
/// Performs the on rename action
|
||||
/// </summary>
|
||||
/// <param name = "message">The message to send to the receiver</param>
|
||||
/// <param name = "series">The Series for the new download</param>
|
||||
public abstract void OnRename(string message, Series series);
|
||||
|
||||
/// <summary>
|
||||
/// Performs the after rename action, this will be handled after all renaming for episode/season/series
|
||||
/// </summary>
|
||||
/// <param name = "message">The message to send to the receiver</param>
|
||||
/// <param name = "series">The Series for the new download</param>
|
||||
public abstract void AfterRename(string message, Series series);
|
||||
public virtual string GetEpisodeGuideUrl(int seriesId)
|
||||
{
|
||||
return String.Format("http://www.thetvdb.com/api/{0}/series/{1}/all/en.zip", TvDbProvider.TVDB_APIKEY, seriesId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Providers.Core;
|
||||
using NzbDrone.Core.Repository;
|
||||
using TvdbLib.Data;
|
||||
using TvdbLib.Data.Banner;
|
||||
|
||||
namespace NzbDrone.Core.Providers.Metadata
|
||||
{
|
||||
public abstract class Xbmc : MetadataBase
|
||||
{
|
||||
protected readonly Logger _logger;
|
||||
|
||||
public Xbmc(ConfigProvider configProvider, DiskProvider diskProvider, BannerProvider bannerProvider, EpisodeProvider episodeProvider)
|
||||
: base(configProvider, diskProvider, bannerProvider, episodeProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "XBMC"; }
|
||||
}
|
||||
|
||||
public override void ForSeries(Series series, TvdbSeries tvDbSeries)
|
||||
{
|
||||
//Create tvshow.nfo, fanart.jpg, folder.jpg and searon##.tbn
|
||||
var episodeGuideUrl = GetEpisodeGuideUrl(series.SeriesId);
|
||||
|
||||
_logger.Debug("Generating tvshow.nfo for: {0}", series.Title);
|
||||
var sb = new StringBuilder();
|
||||
var xws = new XmlWriterSettings();
|
||||
xws.OmitXmlDeclaration = false;
|
||||
xws.Indent = false;
|
||||
|
||||
using (var xw = XmlWriter.Create(sb, xws))
|
||||
{
|
||||
var tvShow = new XElement("tvshow");
|
||||
tvShow.Add(new XElement("title", tvDbSeries.SeriesName));
|
||||
tvShow.Add(new XElement("rating", tvDbSeries.Rating));
|
||||
tvShow.Add(new XElement("plot", tvDbSeries.Overview));
|
||||
tvShow.Add(new XElement("episodeguide", new XElement("url"), episodeGuideUrl));
|
||||
tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
|
||||
tvShow.Add(new XElement("mpaa", tvDbSeries.ContentRating));
|
||||
tvShow.Add(new XElement("genre", tvDbSeries.GenreString));
|
||||
tvShow.Add(new XElement("premiered", tvDbSeries.FirstAired.ToString("yyyy-MM-dd")));
|
||||
tvShow.Add(new XElement("studio", tvDbSeries.Network));
|
||||
|
||||
foreach(var actor in tvDbSeries.TvdbActors)
|
||||
{
|
||||
tvShow.Add(new XElement("actor",
|
||||
new XElement("name", actor.Name),
|
||||
new XElement("role", actor.Role),
|
||||
new XElement("thumb", actor.ActorImage)
|
||||
));
|
||||
}
|
||||
|
||||
var doc = new XDocument(tvShow);
|
||||
doc.Save(xw);
|
||||
}
|
||||
|
||||
_logger.Debug("Saving tvshow.nfo for {0}", series.Title);
|
||||
_diskProvider.WriteAllText(Path.Combine(series.Path, "tvshow.nfo"), sb.ToString());
|
||||
|
||||
_logger.Debug("Downloading fanart for: {0}", series.Title);
|
||||
_bannerProvider.Download(tvDbSeries.FanartPath, Path.Combine(series.Path, "fanart.jpg"));
|
||||
|
||||
if (!_configProvider.MetadataUseBanners)
|
||||
{
|
||||
_logger.Debug("Downloading series thumbnail for: {0}", series.Title);
|
||||
_bannerProvider.Download(tvDbSeries.PosterPath, "folder.jpg");
|
||||
|
||||
_logger.Debug("Downloading Season posters for {0}", series.Title);
|
||||
DownloadSeasonThumbnails(series, tvDbSeries, TvdbSeasonBanner.Type.season);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Debug("Downloading series banner for: {0}", series.Title);
|
||||
_bannerProvider.Download(tvDbSeries.BannerPath, "folder.jpg");
|
||||
|
||||
_logger.Debug("Downloading Season banners for {0}", series.Title);
|
||||
DownloadSeasonThumbnails(series, tvDbSeries, TvdbSeasonBanner.Type.seasonwide);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ForEpisodeFile(EpisodeFile episodeFile, TvdbSeries tvDbSeries)
|
||||
{
|
||||
//Download filename.tbn and filename.nfo
|
||||
//Use BannerPath for Thumbnail
|
||||
var episodes = _episodeProvider.GetEpisodesByFileId(episodeFile.EpisodeFileId);
|
||||
|
||||
if (!episodes.Any())
|
||||
{
|
||||
_logger.Debug("No episodes where found for this episode file: {0}", episodeFile.EpisodeFileId);
|
||||
return;
|
||||
}
|
||||
|
||||
var episodeFileThumbnail = tvDbSeries.Episodes.FirstOrDefault(
|
||||
e =>
|
||||
e.SeasonNumber == episodeFile.SeasonNumber &&
|
||||
e.EpisodeNumber == episodes.First().EpisodeNumber);
|
||||
|
||||
if (episodeFileThumbnail == null || String.IsNullOrWhiteSpace(episodeFileThumbnail.BannerPath))
|
||||
{
|
||||
_logger.Debug("No thumbnail is available for this episode");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug("Downloading episode thumbnail for: {0}", episodeFile.EpisodeFileId);
|
||||
_bannerProvider.Download(episodeFileThumbnail.BannerPath, "folder.jpg");
|
||||
|
||||
_logger.Debug("Generating filename.nfo for: {0}", episodeFile.EpisodeFileId);
|
||||
var sb = new StringBuilder();
|
||||
var xws = new XmlWriterSettings();
|
||||
xws.OmitXmlDeclaration = false;
|
||||
xws.Indent = false;
|
||||
|
||||
using (var xw = XmlWriter.Create(sb, xws))
|
||||
{
|
||||
var doc = new XDocument();
|
||||
|
||||
foreach (var episode in episodes)
|
||||
{
|
||||
var tvdbEpisode =
|
||||
tvDbSeries.Episodes.FirstOrDefault(
|
||||
e =>
|
||||
e.SeasonNumber == episode.SeasonNumber &&
|
||||
e.EpisodeNumber == episode.EpisodeNumber);
|
||||
|
||||
if (tvdbEpisode == null)
|
||||
{
|
||||
_logger.Debug("Unable to find episode from TvDb - skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
var details = new XElement("episodedetails");
|
||||
details.Add(new XElement("title", tvdbEpisode.EpisodeName));
|
||||
details.Add(new XElement("season", tvdbEpisode.SeasonNumber));
|
||||
details.Add(new XElement("episode", tvdbEpisode.EpisodeNumber));
|
||||
details.Add(new XElement("aired", tvdbEpisode.FirstAired));
|
||||
details.Add(new XElement("plot", tvDbSeries.Overview));
|
||||
details.Add(new XElement("displayseason"));
|
||||
details.Add(new XElement("displayepisode"));
|
||||
details.Add(new XElement("thumb", "http://www.thetvdb.com/banners/" + tvdbEpisode.BannerPath));
|
||||
details.Add(new XElement("watched", "false"));
|
||||
details.Add(new XElement("credits", tvdbEpisode.Writer.First()));
|
||||
details.Add(new XElement("director", tvdbEpisode.Directors.First()));
|
||||
details.Add(new XElement("rating", tvDbSeries.Rating));
|
||||
|
||||
foreach(var actor in tvdbEpisode.GuestStars)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(actor))
|
||||
continue;
|
||||
|
||||
details.Add(new XElement("actor",
|
||||
new XElement("name", actor)
|
||||
));
|
||||
}
|
||||
|
||||
foreach(var actor in tvDbSeries.TvdbActors)
|
||||
{
|
||||
details.Add(new XElement("actor",
|
||||
new XElement("name", actor.Name),
|
||||
new XElement("role", actor.Role),
|
||||
new XElement("thumb", actor.ActorImage)
|
||||
));
|
||||
}
|
||||
|
||||
doc.Add(details);
|
||||
doc.Save(xw);
|
||||
}
|
||||
}
|
||||
|
||||
var filename = Path.GetFileNameWithoutExtension(episodeFile.Path) + ".nfo";
|
||||
_logger.Debug("Saving episodedetails to: {0}", filename);
|
||||
_diskProvider.WriteAllText(filename, sb.ToString());
|
||||
}
|
||||
|
||||
private void DownloadSeasonThumbnails(Series series, TvdbSeries tvDbSeries, TvdbSeasonBanner.Type bannerType)
|
||||
{
|
||||
var seasons = tvDbSeries.SeasonBanners.Where(s => s.BannerType == bannerType).Select(s => s.Season);
|
||||
|
||||
foreach (var season in seasons)
|
||||
{
|
||||
var banner = tvDbSeries.SeasonBanners.FirstOrDefault(b => b.BannerType == bannerType && b.Season == season);
|
||||
_logger.Debug("Downloading banner for Season: {0} Series: {1}", season, series.Title);
|
||||
_bannerProvider.Download(banner.BannerPath,
|
||||
Path.Combine(series.Path, String.Format("season{0:00}.tbn", season)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,13 +16,15 @@ namespace NzbDrone.Core.Providers
|
|||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly IDatabase _database;
|
||||
|
||||
private IEnumerable<MetadataBase> _metadataBases;
|
||||
private IEnumerable<MetadataBase> _metadataProviders;
|
||||
private readonly TvDbProvider _tvDbProvider;
|
||||
|
||||
[Inject]
|
||||
public MetadataProvider(IDatabase database, IEnumerable<MetadataBase> metadataBases)
|
||||
public MetadataProvider(IDatabase database, IEnumerable<MetadataBase> metadataProviders, TvDbProvider tvDbProvider)
|
||||
{
|
||||
_database = database;
|
||||
_metadataBases = metadataBases;
|
||||
_metadataProviders = metadataProviders;
|
||||
_tvDbProvider = tvDbProvider;
|
||||
}
|
||||
|
||||
public MetadataProvider()
|
||||
|
@ -30,12 +32,12 @@ namespace NzbDrone.Core.Providers
|
|||
|
||||
}
|
||||
|
||||
public virtual List<MetabaseDefinition> All()
|
||||
public virtual List<MetadataDefinition> All()
|
||||
{
|
||||
return _database.Fetch<MetabaseDefinition>();
|
||||
return _database.Fetch<MetadataDefinition>();
|
||||
}
|
||||
|
||||
public virtual void SaveSettings(MetabaseDefinition settings)
|
||||
public virtual void SaveSettings(MetadataDefinition settings)
|
||||
{
|
||||
if (settings.Id == 0)
|
||||
{
|
||||
|
@ -50,31 +52,31 @@ namespace NzbDrone.Core.Providers
|
|||
}
|
||||
}
|
||||
|
||||
public virtual MetabaseDefinition GetSettings(Type type)
|
||||
public virtual MetadataDefinition GetSettings(Type type)
|
||||
{
|
||||
return _database.SingleOrDefault<MetabaseDefinition>("WHERE MetadataProviderType = @0", type.ToString());
|
||||
return _database.SingleOrDefault<MetadataDefinition>("WHERE MetadataProviderType = @0", type.ToString());
|
||||
}
|
||||
|
||||
public virtual IList<MetadataBase> GetEnabledExternalNotifiers()
|
||||
public virtual IList<MetadataBase> GetEnabledMetabaseProviders()
|
||||
{
|
||||
var all = All();
|
||||
return _metadataBases.Where(i => all.Exists(c => c.MetadataProviderType == i.GetType().ToString() && c.Enable)).ToList();
|
||||
return _metadataProviders.Where(i => all.Exists(c => c.MetadataProviderType == i.GetType().ToString() && c.Enable)).ToList();
|
||||
}
|
||||
|
||||
public virtual void InitializeNotifiers(IList<MetadataBase> notifiers)
|
||||
public virtual void Initialize(IList<MetadataBase> metabaseProviders)
|
||||
{
|
||||
Logger.Debug("Initializing notifiers. Count {0}", notifiers.Count);
|
||||
Logger.Debug("Initializing metabases. Count {0}", metabaseProviders.Count);
|
||||
|
||||
_metadataBases = notifiers;
|
||||
_metadataProviders = metabaseProviders;
|
||||
|
||||
var currentNotifiers = All();
|
||||
|
||||
foreach (var notificationProvider in notifiers)
|
||||
foreach (var notificationProvider in metabaseProviders)
|
||||
{
|
||||
MetadataBase metadataProviderLocal = notificationProvider;
|
||||
if (!currentNotifiers.Exists(c => c.MetadataProviderType == metadataProviderLocal.GetType().ToString()))
|
||||
{
|
||||
var settings = new MetabaseDefinition
|
||||
var settings = new MetadataDefinition
|
||||
{
|
||||
Enable = false,
|
||||
MetadataProviderType = metadataProviderLocal.GetType().ToString(),
|
||||
|
@ -86,35 +88,23 @@ namespace NzbDrone.Core.Providers
|
|||
}
|
||||
}
|
||||
|
||||
public virtual void OnGrab(string message)
|
||||
public virtual void CreateForSeries(Series series)
|
||||
{
|
||||
foreach (var notifier in _metadataBases.Where(i => GetSettings(i.GetType()).Enable))
|
||||
var tvDbSeries = _tvDbProvider.GetSeries(series.SeriesId, false, true);
|
||||
|
||||
foreach (var provider in _metadataProviders.Where(i => GetSettings(i.GetType()).Enable))
|
||||
{
|
||||
notifier.OnGrab(message);
|
||||
provider.ForSeries(series, tvDbSeries);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnDownload(string message, Series series)
|
||||
public virtual void CreateForEpisodeFile(EpisodeFile episodeFile)
|
||||
{
|
||||
foreach (var notifier in _metadataBases.Where(i => GetSettings(i.GetType()).Enable))
|
||||
{
|
||||
notifier.OnDownload(message, series);
|
||||
}
|
||||
}
|
||||
var tvDbSeries = _tvDbProvider.GetSeries(episodeFile.SeriesId, true, true);
|
||||
|
||||
public virtual void OnRename(string message, Series series)
|
||||
{
|
||||
foreach (var notifier in _metadataBases.Where(i => GetSettings(i.GetType()).Enable))
|
||||
foreach (var provider in _metadataProviders.Where(i => GetSettings(i.GetType()).Enable))
|
||||
{
|
||||
notifier.OnRename(message, series);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AfterRename(string message, Series series)
|
||||
{
|
||||
foreach (var notifier in _metadataBases.Where(i => GetSettings(i.GetType()).Enable))
|
||||
{
|
||||
notifier.AfterRename(message, series);
|
||||
provider.ForEpisodeFile(episodeFile, tvDbSeries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace NzbDrone.Core.Providers
|
|||
public class TvDbProvider
|
||||
{
|
||||
private readonly EnvironmentProvider _environmentProvider;
|
||||
private const string TVDB_APIKEY = "5D2D188E86E07F4F";
|
||||
public const string TVDB_APIKEY = "5D2D188E86E07F4F";
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly TvdbHandler _handler;
|
||||
|
@ -44,13 +44,12 @@ namespace NzbDrone.Core.Providers
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual TvdbSeries GetSeries(int id, bool loadEpisodes)
|
||||
public virtual TvdbSeries GetSeries(int id, bool loadEpisodes, bool loadActors = false)
|
||||
{
|
||||
lock (_handler)
|
||||
{
|
||||
Logger.Debug("Fetching SeriesId'{0}' from tvdb", id);
|
||||
var result = _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, false, true, true);
|
||||
var result = _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, loadActors, true, true);
|
||||
|
||||
//Fix American Dad's scene gongshow
|
||||
if (result != null && result.Id == 73141)
|
||||
|
@ -86,6 +85,5 @@ namespace NzbDrone.Core.Providers
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace NzbDrone.Core.Repository
|
||||
{
|
||||
[TableName("MetabaseDefinitions")]
|
||||
[TableName("MetadataDefinitions")]
|
||||
[PrimaryKey("Id", autoIncrement = true)]
|
||||
public class MetabaseDefinition
|
||||
public class MetadataDefinition
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
Loading…
Reference in New Issue