Refactors IDomain model, simplifies it so that it doesn't contain references, simplifies the domain repository to no longer require lookups of content and languages, updates all other code referencing IDomain and now if a language lookup is required it is made when appropriate.

This commit is contained in:
Shannon
2015-07-27 12:53:09 +02:00
parent 00c13b9939
commit 16c9ca9e4b
23 changed files with 323 additions and 439 deletions

View File

@@ -4,9 +4,9 @@ namespace Umbraco.Core.Models
{
public interface IDomain : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
ILanguage Language { get; set; }
int? LanguageId { get; set; }
string DomainName { get; set; }
IContent RootContent { get; set; }
int? RootContentId { get; set; }
bool IsWildcard { get; }
}
}

View File

@@ -5,8 +5,6 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
//TODO: Need to custom serialize this
[Serializable]
[DataContract(IsReference = true)]
public class UmbracoDomain : Entity, IDomain
@@ -16,25 +14,26 @@ namespace Umbraco.Core.Models
_domainName = domainName;
}
private IContent _content;
private ILanguage _language;
private int? _contentId;
private int? _languageId;
private string _domainName;
private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo<UmbracoDomain, ILanguage>(x => x.Language);
private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo<UmbracoDomain, int?>(x => x.RootContentId);
private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo<UmbracoDomain, int?>(x => x.LanguageId);
private static readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo<UmbracoDomain, string>(x => x.DomainName);
private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo<UmbracoDomain, IContent>(x => x.RootContent);
[DataMember]
public ILanguage Language
public int? LanguageId
{
get { return _language; }
get { return _languageId; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_language = value;
return _language;
}, _language, DefaultLanguageSelector);
_languageId = value;
return _languageId;
}, _languageId, DefaultLanguageSelector);
}
}
@@ -53,16 +52,16 @@ namespace Umbraco.Core.Models
}
[DataMember]
public IContent RootContent
public int? RootContentId
{
get { return _content; }
get { return _contentId; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_content = value;
return _content;
}, _content, ContentSelector);
_contentId = value;
return _contentId;
}, _contentId, ContentSelector);
}
}
@@ -70,5 +69,10 @@ namespace Umbraco.Core.Models
{
get { return string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); }
}
public string IsoCode
{
get { throw new NotImplementedException(); }
}
}
}

View File

@@ -5,7 +5,8 @@ using Umbraco.Core.Persistence.Repositories;
namespace Umbraco.Core.Persistence.Mappers
{
[MapperFor(typeof(DomainRepository.CacheableDomain))]
[MapperFor(typeof(IDomain))]
[MapperFor(typeof(UmbracoDomain))]
public sealed class DomainMapper : BaseMapper
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
@@ -22,10 +23,10 @@ namespace Umbraco.Core.Persistence.Mappers
internal override void BuildMap()
{
CacheMap<DomainRepository.CacheableDomain, DomainDto>(src => src.Id, dto => dto.Id);
CacheMap<DomainRepository.CacheableDomain, DomainDto>(src => src.RootContentId, dto => dto.RootStructureId);
CacheMap<DomainRepository.CacheableDomain, DomainDto>(src => src.DefaultLanguageId, dto => dto.DefaultLanguage);
CacheMap<DomainRepository.CacheableDomain, DomainDto>(src => src.DomainName, dto => dto.DomainName);
CacheMap<UmbracoDomain, DomainDto>(src => src.Id, dto => dto.Id);
CacheMap<UmbracoDomain, DomainDto>(src => src.RootContentId, dto => dto.RootStructureId);
CacheMap<UmbracoDomain, DomainDto>(src => src.LanguageId, dto => dto.DefaultLanguage);
CacheMap<UmbracoDomain, DomainDto>(src => src.DomainName, dto => dto.DomainName);
}
}
}

View File

@@ -16,48 +16,42 @@ namespace Umbraco.Core.Persistence.Repositories
{
internal class DomainRepository : PetaPocoRepositoryBase<int, IDomain>, IDomainRepository
{
private readonly IContentRepository _contentRepository;
private readonly ILanguageRepository _languageRepository;
private readonly RepositoryCacheOptions _cacheOptions;
public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentRepository contentRepository, ILanguageRepository languageRepository)
public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
_contentRepository = contentRepository;
_languageRepository = languageRepository;
//Custom cache options for better performance
_cacheOptions = new RepositoryCacheOptions
{
GetAllCacheAllowZeroCount = true,
GetAllCacheValidateCount = false
};
}
/// <summary>
/// Override the cache, this repo will not perform any cache, the caching is taken care of in the inner repository
/// Returns the repository cache options
/// </summary>
/// <remarks>
/// This is required because IDomain is a deep object and we dont' want to cache it since it contains an ILanguage and an IContent, when these
/// are deep cloned the object graph that will be cached will be huge. Instead we'll have an internal repository that caches the simple
/// Domain structure and we'll use the other repositories to resolve the entities to attach
/// </remarks>
protected override IRuntimeCacheProvider RuntimeCache
protected override RepositoryCacheOptions RepositoryCacheOptions
{
get { return new NullCacheProvider(); }
get { return _cacheOptions; }
}
protected override IDomain PerformGet(int id)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
var factory = new DomainModelFactory();
return factory.BuildDomainEntity(repo.Get(id), _contentRepository, _languageRepository);
}
//use the underlying GetAll which will force cache all domains
return GetAll().FirstOrDefault(x => x.Id == id);
}
protected override IEnumerable<IDomain> PerformGetAll(params int[] ids)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0");
if (ids.Any())
{
var factory = new DomainModelFactory();
return factory.BuildDomainEntities(
ids.Length == 0 ? Enumerable.Empty<CacheableDomain>().ToArray() : repo.GetAll(ids).ToArray(),
_contentRepository,
_languageRepository);
sql.Where("umbracoDomains.id in (@ids)", new { ids = ids });
}
return Database.Fetch<DomainDto>(sql).Select(ConvertFromDto);
}
protected override IEnumerable<IDomain> PerformGetByQuery(IQuery<IDomain> query)
@@ -77,18 +71,13 @@ namespace Umbraco.Core.Persistence.Repositories
return "umbracoDomains.id = @Id";
}
protected override void PersistDeletedItem(IDomain entity)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
var factory = new DomainModelFactory();
repo.PersistDeletedItem(factory.BuildEntity(entity));
}
}
protected override IEnumerable<string> GetDeleteClauses()
{
throw new NotImplementedException();
var list = new List<string>
{
"DELETE FROM umbracoDomains WHERE id = @Id"
};
return list;
}
protected override Guid NodeObjectTypeId
@@ -98,269 +87,105 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PersistNewItem(IDomain entity)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
var exists = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName });
if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName));
if (entity.RootContentId.HasValue)
{
var factory = new DomainModelFactory();
var cacheableEntity = factory.BuildEntity(entity);
repo.PersistNewItem(cacheableEntity);
//re-map the id
entity.Id = cacheableEntity.Id;
entity.ResetDirtyProperties();
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value });
if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value);
}
if (entity.LanguageId.HasValue)
{
var languageExists = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value });
if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value);
}
((UmbracoDomain)entity).AddingEntity();
var factory = new DomainModelFactory();
var dto = factory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
entity.Id = id;
entity.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IDomain entity)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
((UmbracoDomain)entity).UpdatingEntity();
var exists = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName AND umbracoDomains.id <> @id",
new { domainName = entity.DomainName, id = entity.Id });
//ensure there is no other domain with the same name on another entity
if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName));
if (entity.RootContentId.HasValue)
{
var factory = new DomainModelFactory();
repo.PersistUpdatedItem(factory.BuildEntity(entity));
entity.ResetDirtyProperties();
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value });
if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value);
}
if (entity.LanguageId.HasValue)
{
var languageExists = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value });
if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value);
}
var factory = new DomainModelFactory();
var dto = factory.BuildDto(entity);
Database.Update(dto);
entity.ResetDirtyProperties();
}
public IDomain GetByName(string domainName)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
var factory = new DomainModelFactory();
return factory.BuildDomainEntity(
repo.GetAll().FirstOrDefault(x => x.DomainName.InvariantEquals(domainName)),
_contentRepository, _languageRepository);
}
return GetAll().FirstOrDefault(x => x.DomainName.InvariantEquals(domainName));
}
public bool Exists(string domainName)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
return repo.GetAll().Any(x => x.DomainName.InvariantEquals(domainName));
}
return GetAll().Any(x => x.DomainName.InvariantEquals(domainName));
}
public IEnumerable<IDomain> GetAll(bool includeWildcards)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
var factory = new DomainModelFactory();
return factory.BuildDomainEntities(repo.GetAll().ToArray(), _contentRepository, _languageRepository)
.Where(x => includeWildcards || x.IsWildcard == false);
}
return GetAll().Where(x => includeWildcards || x.IsWildcard == false);
}
public IEnumerable<IDomain> GetAssignedDomains(int contentId, bool includeWildcards)
{
using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
var factory = new DomainModelFactory();
return factory.BuildDomainEntities(
repo.GetAll().Where(x => x.RootContentId == contentId).ToArray(),
_contentRepository, _languageRepository)
.Where(x => includeWildcards || x.IsWildcard == false);
}
return GetAll()
.Where(x => x.RootContentId == contentId)
.Where(x => includeWildcards || x.IsWildcard == false);
}
/// <summary>
/// Dispose disposable properties
/// </summary>
/// <remarks>
/// Ensure the unit of work is disposed
/// </remarks>
protected override void DisposeResources()
private IDomain ConvertFromDto(DomainDto dto)
{
_contentRepository.Dispose();
_languageRepository.Dispose();
}
/// <summary>
/// A simple domain model that is cacheable without a large object graph
/// </summary>
internal class CacheableDomain : Entity, IAggregateRoot
{
public int? DefaultLanguageId { get; set; }
public string DomainName { get; set; }
public int? RootContentId { get; set; }
}
/// <summary>
/// Inner repository responsible for CRUD for domains that allows caching simple data
/// </summary>
/// <remarks>
/// Since there's never very many domains, any query against this repo, should query against the result of GetAll
/// </remarks>
private class CachedDomainRepository : PetaPocoRepositoryBase<int, CacheableDomain>
{
private readonly DomainRepository _domainRepo;
private readonly RepositoryCacheOptions _cacheOptions;
public CachedDomainRepository(DomainRepository domainRepo, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
_domainRepo = domainRepo;
//Custom cache options for better performance
_cacheOptions = new RepositoryCacheOptions
{
GetAllCacheAllowZeroCount = true,
GetAllCacheValidateCount = false
};
}
/// <summary>
/// Returns the repository cache options
/// </summary>
protected override RepositoryCacheOptions RepositoryCacheOptions
{
get { return _cacheOptions; }
}
protected override CacheableDomain PerformGet(int id)
{
//use the underlying GetAll which will force cache all domains
return GetAll().FirstOrDefault(x => x.Id == id);
}
protected override IEnumerable<CacheableDomain> PerformGetAll(params int[] ids)
{
var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0");
if (ids.Any())
{
sql.Where("umbracoDomains.id in (@ids)", new { ids = ids });
}
return Database.Fetch<DomainDto>(sql).Select(ConvertFromDto);
}
protected override IEnumerable<CacheableDomain> PerformGetByQuery(IQuery<CacheableDomain> query)
{
throw new NotSupportedException("This repository does not support this method");
}
protected override Sql GetBaseQuery(bool isCount)
{
return _domainRepo.GetBaseQuery(isCount);
}
protected override string GetBaseWhereClause()
{
return _domainRepo.GetBaseWhereClause();
}
protected override IEnumerable<string> GetDeleteClauses()
{
var list = new List<string>
{
"DELETE FROM umbracoDomains WHERE id = @Id"
};
return list;
}
protected override Guid NodeObjectTypeId
{
get { throw new NotImplementedException(); }
}
protected override void PersistNewItem(CacheableDomain entity)
{
var exists = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName });
if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName));
entity.AddingEntity();
var factory = new DomainModelFactory();
var dto = factory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
entity.Id = id;
entity.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(CacheableDomain entity)
{
entity.UpdatingEntity();
var factory = new DomainModelFactory();
var dto = factory.BuildDto(entity);
Database.Update(dto);
entity.ResetDirtyProperties();
}
private CacheableDomain ConvertFromDto(DomainDto dto)
{
var factory = new DomainModelFactory();
var entity = factory.BuildEntity(dto);
return entity;
}
}
var factory = new DomainModelFactory();
var entity = factory.BuildEntity(dto);
return entity;
}
internal class DomainModelFactory
{
public IEnumerable<IDomain> BuildDomainEntities(CacheableDomain[] cacheableDomains, IContentRepository contentRepository, ILanguageRepository languageRepository)
public IDomain BuildEntity(DomainDto dto)
{
var contentIds = cacheableDomains.Select(x => x.RootContentId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray();
var langIds = cacheableDomains.Select(x => x.DefaultLanguageId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray();
//Ensure to not look anything up if there are no items, otherwise this will look up ALL items!!! :( which would be terrible
var contentItems = contentIds.Length == 0 ? Enumerable.Empty<IContent>() : contentRepository.GetAll(contentIds);
var langItems = langIds.Length == 0 ? Enumerable.Empty<ILanguage>(): languageRepository.GetAll(langIds);
return cacheableDomains
.WhereNotNull()
.Select(cacheableDomain => new UmbracoDomain(cacheableDomain.DomainName)
{
Id = cacheableDomain.Id,
//lookup from repo - this will be cached
Language = cacheableDomain.DefaultLanguageId.HasValue ? langItems.FirstOrDefault(l => l.Id == cacheableDomain.DefaultLanguageId.Value) : null,
//lookup from repo - this will be cached
RootContent = cacheableDomain.RootContentId.HasValue ? contentItems.FirstOrDefault(l => l.Id == cacheableDomain.RootContentId.Value) : null,
});
}
public IDomain BuildDomainEntity(CacheableDomain cacheableDomain, IContentRepository contentRepository, ILanguageRepository languageRepository)
{
if (cacheableDomain == null) return null;
return new UmbracoDomain(cacheableDomain.DomainName)
{
Id = cacheableDomain.Id,
//lookup from repo - this will be cached
Language = cacheableDomain.DefaultLanguageId.HasValue ? languageRepository.Get(cacheableDomain.DefaultLanguageId.Value) : null,
//lookup from repo - this will be cached
RootContent = cacheableDomain.RootContentId.HasValue ? contentRepository.Get(cacheableDomain.RootContentId.Value) : null
};
}
public CacheableDomain BuildEntity(IDomain entity)
{
var domain = new CacheableDomain
{
Id = entity.Id,
DefaultLanguageId = entity.Language == null ? null : (int?)entity.Language.Id,
DomainName = entity.DomainName,
RootContentId = entity.RootContent == null ? null : (int?)entity.RootContent.Id
};
var domain = new UmbracoDomain(dto.DomainName) { Id = dto.Id, LanguageId = dto.DefaultLanguage, RootContentId = dto.RootStructureId };
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
domain.ResetDirtyProperties(false);
return domain;
}
public CacheableDomain BuildEntity(DomainDto dto)
public DomainDto BuildDto(IDomain entity)
{
var domain = new CacheableDomain { Id = dto.Id, DefaultLanguageId = dto.DefaultLanguage, DomainName = dto.DomainName, RootContentId = dto.RootStructureId };
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
domain.ResetDirtyProperties(false);
return domain;
}
public DomainDto BuildDto(CacheableDomain entity)
{
var dto = new DomainDto { DefaultLanguage = entity.DefaultLanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId };
var dto = new DomainDto { DefaultLanguage = entity.LanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId };
return dto;
}
}

View File

@@ -294,7 +294,7 @@ namespace Umbraco.Core.Persistence
public IDomainRepository CreateDomainRepository(IDatabaseUnitOfWork uow)
{
return new DomainRepository(uow, _cacheHelper, _logger, _sqlSyntax, CreateContentRepository(uow), CreateLanguageRepository(uow));
return new DomainRepository(uow, _cacheHelper, _logger, _sqlSyntax);
}
public ITaskTypeRepository CreateTaskTypeRepository(IDatabaseUnitOfWork uow)