diff --git a/src/Orchard.Tests/DataUtility.cs b/src/Orchard.Tests/DataUtility.cs index ba73b2b1e..5c3ddb4c9 100644 --- a/src/Orchard.Tests/DataUtility.cs +++ b/src/Orchard.Tests/DataUtility.cs @@ -13,10 +13,11 @@ using Orchard.Data; namespace Orchard.Tests { public static class DataUtility { public static ISessionFactory CreateSessionFactory(string fileName, params Type[] types) { - - var persistenceModel = AutoMap.Source(new Types(types)) - .Alterations(alt => AddAlterations(alt, types)) - .Conventions.AddFromAssemblyOf(); + + //var persistenceModel = AutoMap.Source(new Types(types)) + // .Alterations(alt => AddAlterations(alt, types)) + // .Conventions.AddFromAssemblyOf(); + var persistenceModel = HackSessionLocator.CreatePersistenceModel(types); return Fluently.Configure() .Database(SQLiteConfiguration.Standard.UsingFile(fileName).ShowSql()) @@ -25,9 +26,10 @@ namespace Orchard.Tests { .BuildSessionFactory(); } - private static void AddAlterations(AutoMappingAlterationCollection alterations, Type[] types) { + private static void AddAlterations(AutoMappingAlterationCollection alterations, IEnumerable types) { foreach (var assembly in types.Select(t => t.Assembly).Distinct()) { alterations.Add(new AutoMappingOverrideAlteration(assembly)); + alterations.AddFromAssembly(assembly); } alterations.AddFromAssemblyOf(); } diff --git a/src/Orchard.Tests/Environment/DefaultCompositionStrategyTests.cs b/src/Orchard.Tests/Environment/DefaultCompositionStrategyTests.cs new file mode 100644 index 000000000..16b482da8 --- /dev/null +++ b/src/Orchard.Tests/Environment/DefaultCompositionStrategyTests.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Orchard.Environment; +using Orchard.Packages; +using Orchard.Tests.Models.Records; +using Orchard.Tests.Models.Stubs; + +namespace Orchard.Tests.Environment { + [TestFixture] + public class DefaultCompositionStrategyTests { + [Test] + public void ExpectedRecordsShouldComeBack() { + var packageManager = new Moq.Mock(); + packageManager.Setup(x => x.ActivePackages()).Returns(new[] { + new PackageEntry + {ExportedTypes = new[] {typeof (GammaRecord), typeof (DeltaRecord), typeof (Delta)}} + }); + var strategy = new DefaultCompositionStrategy(packageManager.Object); + var recordTypes = strategy.GetRecordTypes(); + + Assert.That(recordTypes.Count(), Is.Not.EqualTo(0)); + Assert.That(recordTypes, Has.Some.EqualTo(typeof(DeltaRecord))); + Assert.That(recordTypes, Has.Some.EqualTo(typeof(GammaRecord))); + Assert.That(recordTypes, Has.None.EqualTo(typeof(Delta))); + } + } +} diff --git a/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs b/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs index 13081be31..75cb345bd 100644 --- a/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs +++ b/src/Orchard.Tests/Environment/DefaultOrchardHostTests.cs @@ -80,6 +80,10 @@ namespace Orchard.Tests.Environment { public IEnumerable GetDependencyTypes() { return Enumerable.Empty(); } + + public IEnumerable GetRecordTypes() { + return Enumerable.Empty(); + } } [Test] diff --git a/src/Orchard.Tests/Models/ContentQueryTests.cs b/src/Orchard.Tests/Models/ContentQueryTests.cs new file mode 100644 index 000000000..8820b0b80 --- /dev/null +++ b/src/Orchard.Tests/Models/ContentQueryTests.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Autofac; +using Autofac.Builder; +using Autofac.Modules; +using NHibernate; +using NUnit.Framework; +using Orchard.Data; +using Orchard.Models; +using Orchard.Models.Driver; +using Orchard.Models.Records; +using Orchard.Tests.Models.Records; +using Orchard.Tests.Models.Stubs; + +namespace Orchard.Tests.Models { + [TestFixture] + public class ContentQueryTests { + private IContainer _container; + private IContentManager _manager; + private ISessionFactory _sessionFactory; + private ISession _session; + + [TestFixtureSetUp] + public void InitFixture() { + var databaseFileName = System.IO.Path.GetTempFileName(); + _sessionFactory = DataUtility.CreateSessionFactory( + databaseFileName, + typeof(GammaRecord), + typeof(DeltaRecord), + typeof(ContentItemRecord), + typeof(ContentTypeRecord)); + } + + [TestFixtureTearDown] + public void TermFixture() { + + } + + + + [SetUp] + public void Init() { + var builder = new ContainerBuilder(); + builder.RegisterModule(new ImplicitCollectionSupportModule()); + builder.RegisterModule(new ContentModule()); + builder.Register().As(); + builder.Register().As(); + builder.Register().As(); + builder.Register().As(); + builder.Register().As(); + builder.Register().As(); + builder.Register().As(); + + builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); + + _session = _sessionFactory.OpenSession(); + builder.Register(new DefaultModelManagerTests.TestSessionLocator(_session)).As(); + + _session.Delete("from GammaRecord"); + _session.Delete("from DeltaRecord"); + _session.Delete("from ContentItemRecord"); + _session.Delete("from ContentTypeRecord"); + _session.Flush(); + _session.Clear(); + + _container = builder.Build(); + _manager = _container.Resolve(); + + } + + private void AddSampleData() { + _manager.Create("alpha", init => { }); + _manager.Create("beta", init => { }); + _manager.Create("gamma", init => { init.Record.Frap = "the frap value"; }); + _manager.Create("delta", init => { init.Record.Quux = "the quux value"; }); + _session.Flush(); + } + + [Test] + public void QueryInstanceIsDifferentEachTimeYouCreateOne() { + var contentManager1 = _container.Resolve(); + var query1a = contentManager1.Query(); + var query1b = contentManager1.Query(); + + var contentManager2 = _container.Resolve(); + var query2a = contentManager2.Query(); + var query2b = contentManager2.Query(); + + Assert.That(contentManager1, Is.SameAs(contentManager2)); + Assert.That(query1a, Is.SameAs(query1a)); + + Assert.That(query1a, Is.Not.SameAs(query1b)); + Assert.That(query1a, Is.Not.SameAs(query2a)); + Assert.That(query1a, Is.Not.SameAs(query2b)); + + Assert.That(query1b, Is.Not.SameAs(query2a)); + Assert.That(query1b, Is.Not.SameAs(query2b)); + + Assert.That(query2a, Is.Not.SameAs(query2b)); + } + + [Test] + public void ContentManagerPropertyIsSet() { + var contentManager = _container.Resolve(); + var query = contentManager.Query(); + Assert.That(query.ContentManager, Is.SameAs(contentManager)); + + var mockManager = new Moq.Mock().Object; + var anotherQuery = _container.Resolve(TypedParameter.From(mockManager)); + Assert.That(anotherQuery, Is.Not.SameAs(query)); + Assert.That(anotherQuery.ContentManager, Is.SameAs(mockManager)); + } + + [Test] + public void AllItemsAreReturnedByDefault() { + AddSampleData(); + + var allItems = _manager.Query().Select(); + + Assert.That(allItems.Count(), Is.EqualTo(4)); + Assert.That(allItems.Count(x => x.Has()), Is.EqualTo(1)); + Assert.That(allItems.Count(x => x.Has()), Is.EqualTo(1)); + Assert.That(allItems.Count(x => x.Has()), Is.EqualTo(1)); + Assert.That(allItems.Count(x => x.Has()), Is.EqualTo(1)); + } + + [Test] + public void SpecificTypeIsReturnedWhenSpecified() { + AddSampleData(); + + var alphaBeta = _manager.Query().ForType("alpha", "beta").Select(); + + Assert.That(alphaBeta.Count(), Is.EqualTo(2)); + Assert.That(alphaBeta.Count(x => x.Has()), Is.EqualTo(1)); + Assert.That(alphaBeta.Count(x => x.Has()), Is.EqualTo(1)); + Assert.That(alphaBeta.Count(x => x.Has()), Is.EqualTo(0)); + Assert.That(alphaBeta.Count(x => x.Has()), Is.EqualTo(0)); + + var gammaDelta = _manager.Query().ForType("gamma", "delta").Select(); + + Assert.That(gammaDelta.Count(), Is.EqualTo(2)); + Assert.That(gammaDelta.Count(x => x.Has()), Is.EqualTo(0)); + Assert.That(gammaDelta.Count(x => x.Has()), Is.EqualTo(0)); + Assert.That(gammaDelta.Count(x => x.Has()), Is.EqualTo(1)); + Assert.That(gammaDelta.Count(x => x.Has()), Is.EqualTo(1)); + } + + [Test] + public void WherePredicateRestrictsResults() { + AddSampleData(); + _manager.Create("gamma", init => { init.Record.Frap = "one"; }); + _manager.Create("gamma", init => { init.Record.Frap = "two"; }); + _manager.Create("gamma", init => { init.Record.Frap = "three"; }); + _manager.Create("gamma", init => { init.Record.Frap = "four"; }); + _session.Flush(); + var twoOrFour = _manager.Query().Where(x => x.Frap == "one" || x.Frap == "four").Select(); + + Assert.That(twoOrFour.Count(), Is.EqualTo(2)); + Assert.That(twoOrFour.Count(x => x.Has()), Is.EqualTo(2)); + Assert.That(twoOrFour.Count(x => x.Get().Record.Frap == "one"), Is.EqualTo(1)); + Assert.That(twoOrFour.Count(x => x.Get().Record.Frap == "four"), Is.EqualTo(1)); + } + + + [Test] + public void EmptyWherePredicateRequiresRecord() { + AddSampleData(); + var gammas = _manager.Query().Where().Select(); + var deltas = _manager.Query().Where().Select(); + + Assert.That(gammas.Count(), Is.EqualTo(1)); + Assert.That(deltas.Count(), Is.EqualTo(1)); + Assert.That(gammas.AsPart().Single().Record.Frap, Is.EqualTo("the frap value")); + Assert.That(deltas.AsPart().Single().Record.Quux, Is.EqualTo("the quux value")); + } + + } +} + + + diff --git a/src/Orchard.Tests/Models/DefaultModelManagerTests.cs b/src/Orchard.Tests/Models/DefaultModelManagerTests.cs index 78605cb60..2a622f440 100644 --- a/src/Orchard.Tests/Models/DefaultModelManagerTests.cs +++ b/src/Orchard.Tests/Models/DefaultModelManagerTests.cs @@ -8,6 +8,7 @@ using Orchard.Data; using Orchard.Models; using Orchard.Models.Driver; using Orchard.Models.Records; +using Orchard.Tests.Models.Records; using Orchard.Tests.Models.Stubs; namespace Orchard.Tests.Models { diff --git a/src/Orchard.Tests/Models/Records/DeltaRecord.cs b/src/Orchard.Tests/Models/Records/DeltaRecord.cs new file mode 100644 index 000000000..434854014 --- /dev/null +++ b/src/Orchard.Tests/Models/Records/DeltaRecord.cs @@ -0,0 +1,7 @@ +using Orchard.Models.Records; + +namespace Orchard.Tests.Models.Records { + public class DeltaRecord : ContentPartRecord { + public virtual string Quux { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/Models/Records/GammaRecord.cs b/src/Orchard.Tests/Models/Records/GammaRecord.cs new file mode 100644 index 000000000..c618651f4 --- /dev/null +++ b/src/Orchard.Tests/Models/Records/GammaRecord.cs @@ -0,0 +1,7 @@ +using Orchard.Models.Records; + +namespace Orchard.Tests.Models.Records { + public class GammaRecord : ContentPartRecord { + public virtual string Frap { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/Models/Stubs/Delta.cs b/src/Orchard.Tests/Models/Stubs/Delta.cs new file mode 100644 index 000000000..6e512ac23 --- /dev/null +++ b/src/Orchard.Tests/Models/Stubs/Delta.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Orchard.Data; +using Orchard.Models; +using Orchard.Models.Driver; +using Orchard.Tests.Models.Records; + +namespace Orchard.Tests.Models.Stubs { + public class Delta : ContentPart { + } + + + public class DeltaProvider : ContentProvider { + public DeltaProvider(IRepository repository) { + Filters.Add(new ActivatingFilter(x => x == "delta")); + Filters.Add(new StorageFilter(repository)); + } + } +} diff --git a/src/Orchard.Tests/Models/Stubs/Gamma.cs b/src/Orchard.Tests/Models/Stubs/Gamma.cs index 66bf0ea32..5f534c90b 100644 --- a/src/Orchard.Tests/Models/Stubs/Gamma.cs +++ b/src/Orchard.Tests/Models/Stubs/Gamma.cs @@ -1,21 +1,48 @@ -using Orchard.Data; +using System.Linq; +using FluentNHibernate.Automapping; +using FluentNHibernate.Automapping.Alterations; +using Orchard.Data; using Orchard.Models; using Orchard.Models.Driver; -using Orchard.Models.Records; +using Orchard.Tests.Models.Records; namespace Orchard.Tests.Models.Stubs { public class Gamma : ContentPart { } - public class GammaRecord : ContentPartRecord { - public virtual string Frap { get; set; } - } - public class GammaProvider : ContentProvider { - public GammaProvider(IRepository repository){ + public GammaProvider(IRepository repository) { Filters.Add(new ActivatingFilter(x => x == "gamma")); Filters.Add(new StorageFilter(repository)); } } + + + //public class ContentItemRecordAlteration : IAutoMappingAlteration { + // public void Alter(AutoPersistenceModel model) { + // model.OverrideAll(mapping => { + // var genericArguments = mapping.GetType().GetGenericArguments(); + // if (!genericArguments.Single().IsSubclassOf(typeof (ContentPartRecord))) { + // return; + // } + // }); + + // model.Override(mapping => mapping.HasOne(record => (GammaRecord)record["GammaRecord"]).Access.NoOp().Fetch.Select()); + + + // } + + // interface IAlteration { + // void Override(object mapping); + // } + + // class Alteration : IAlteration where T : ContentPartRecord { + // public void Override(object mappingObj) { + // var mapping = (AutoMapping)mappingObj; + // mapping.Id(x => x.Id).GeneratedBy.Foreign("ContentItem"); + // mapping.HasOne(x => x.ContentItem).Constrained(); + // } + // } + //} } diff --git a/src/Orchard.Tests/Orchard.Tests.csproj b/src/Orchard.Tests/Orchard.Tests.csproj index 91c9a5e61..6f468217e 100644 --- a/src/Orchard.Tests/Orchard.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Tests.csproj @@ -100,11 +100,13 @@ + + @@ -112,9 +114,12 @@ + + + diff --git a/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPost.cs b/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPost.cs index fae442b68..6486a32c6 100644 --- a/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPost.cs +++ b/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPost.cs @@ -5,7 +5,7 @@ using Orchard.Security; namespace Orchard.Blogs.Models { public class BlogPost : ContentPart { - //public Blog Blog { get; set; } + public Blog Blog { get; set; } public string Title { get { return this.As().Title; } } public string Body { get { return this.As().Body; } } public string Slug { get { return this.As().Slug; } } diff --git a/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPostProvider.cs b/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPostProvider.cs index 78ac30119..53185e4e4 100644 --- a/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPostProvider.cs +++ b/src/Orchard.Web/Packages/Orchard.Blogs/Models/BlogPostProvider.cs @@ -11,7 +11,7 @@ namespace Orchard.Blogs.Models { Filters.Add(new ActivatingFilter("blogpost")); Filters.Add(new ActivatingFilter("blogpost")); Filters.Add(new StorageFilter(repository)); - //AddOnLoaded((context, bp) => bp.Blog = contentManager.Get(context.ContentItem.Id)); + AddOnLoaded((context, bp) => bp.Blog = contentManager.Get(bp.Record.Blog.Id)); } } } \ No newline at end of file diff --git a/src/Orchard/Data/HackSessionLocator.cs b/src/Orchard/Data/HackSessionLocator.cs index 014d1edc3..e55e69a7a 100644 --- a/src/Orchard/Data/HackSessionLocator.cs +++ b/src/Orchard/Data/HackSessionLocator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; @@ -9,71 +10,71 @@ using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using NHibernate; using NHibernate.Tool.hbm2ddl; +using Orchard.Environment; using Orchard.Models; +using Orchard.Models.Records; namespace Orchard.Data { public class HackSessionLocator : ISessionLocator, IDisposable { + private readonly ICompositionStrategy _compositionStrategy; private static ISessionFactory _sessionFactory; private ISession _session; - public ISessionFactory SessionFactory { - get { - // TEMP: a real scenario would call for a session factory locator - // that would eventually imply the need for configuration against one or more actual sources - // and a means to enlist record types from active packages into correct session factory - - var database = - SQLiteConfiguration.Standard.UsingFile(HttpContext.Current.Server.MapPath("~/App_Data/hack.db")); - - var automaps = new[] { - CreatePersistenceModel(Assembly.Load("Orchard.CmsPages")), - CreatePersistenceModel(Assembly.Load("Orchard.Users")), - CreatePersistenceModel(Assembly.Load("Orchard.Roles")), - CreatePersistenceModel(Assembly.Load("Orchard")), - CreatePersistenceModel(Assembly.Load("Orchard.Media")), - CreatePersistenceModel(Assembly.Load("Orchard.Core")), - CreatePersistenceModel(Assembly.Load("Orchard.Sandbox")), - CreatePersistenceModel(Assembly.Load("Orchard.Comments")), - CreatePersistenceModel(Assembly.Load("Orchard.Tags")), - CreatePersistenceModel(Assembly.Load("Orchard.Blogs")), - }; - - return _sessionFactory ?? - Interlocked.CompareExchange( - ref _sessionFactory, - Fluently.Configure() - .Database(database) - .Mappings(m => { - foreach (var automap in automaps) { - m.AutoMappings.Add(automap); - } - }) - .ExposeConfiguration( - c => new SchemaUpdate(c).Execute(false /*script*/, true /*doUpdate*/)) - .BuildSessionFactory(), null) ?? _sessionFactory; - } + public HackSessionLocator(ICompositionStrategy compositionStrategy) { + _compositionStrategy = compositionStrategy; } - private static AutoPersistenceModel CreatePersistenceModel(Assembly assembly) { - return AutoMap.Assembly(assembly) - .Where(IsRecordType) - .Alterations(alt => alt - .Add(new AutoMappingOverrideAlteration(assembly)) - .AddFromAssemblyOf()) + private ISessionFactory BindSessionFactory() { + // TEMP: a real scenario would call for a session factory locator + // that would eventually imply the need for configuration against one or more actual sources + // and a means to enlist record types from active packages into correct session factory + + var database = + SQLiteConfiguration.Standard.UsingFile(HttpContext.Current.Server.MapPath("~/App_Data/hack.db")); + + var recordTypes = _compositionStrategy.GetRecordTypes(); + + return _sessionFactory ?? + Interlocked.CompareExchange( + ref _sessionFactory, + BuildSessionFactory(database, recordTypes), null) ?? _sessionFactory; + + } + + private static ISessionFactory BuildSessionFactory(IPersistenceConfigurer database, IEnumerable recordTypes) { + return Fluently.Configure() + .Database(database) + .Mappings(m => m.AutoMappings.Add(CreatePersistenceModel(recordTypes))) + .ExposeConfiguration(c => new SchemaUpdate(c).Execute(false /*script*/, true /*doUpdate*/)) + .BuildSessionFactory(); + } + + public static AutoPersistenceModel CreatePersistenceModel(IEnumerable recordTypes) { + return AutoMap.Source(new TypeSource(recordTypes)) + .Alterations(alt => { + foreach (var recordAssembly in recordTypes.Select(x => x.Assembly).Distinct()) { + alt.Add(new AutoMappingOverrideAlteration(recordAssembly)); + } + alt.AddFromAssemblyOf(); + alt.Add(new ContentItemRecordAlteration(recordTypes)); + }) .Conventions.AddFromAssemblyOf(); } - private static bool IsRecordType(Type type) { - return (type.Namespace.EndsWith(".Models") || type.Namespace.EndsWith(".Records")) && - type.GetProperty("Id") != null && - type.GetProperty("Id").GetAccessors().All(x => x.IsVirtual) && - !type.IsSealed && - !type.IsAbstract && - !typeof(IContent).IsAssignableFrom(type); + private class TypeSource : ITypeSource { + private readonly IEnumerable _recordTypes; + + public TypeSource(IEnumerable recordTypes) { + _recordTypes = recordTypes; + } + + public IEnumerable GetTypes() { + return _recordTypes; + } } public ISession For(Type entityType) { - return _session ?? Interlocked.CompareExchange(ref _session, SessionFactory.OpenSession(), null) ?? _session; + return _session ?? Interlocked.CompareExchange(ref _session, BindSessionFactory().OpenSession(), null) ?? _session; } public void Dispose() { diff --git a/src/Orchard/Data/IDataManager.cs b/src/Orchard/Data/IDataManager.cs new file mode 100644 index 000000000..9404dde24 --- /dev/null +++ b/src/Orchard/Data/IDataManager.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Orchard.Data { + public interface IDataManager { + } + + public class DataManager : IDataManager { + + } +} diff --git a/src/Orchard/Environment/DefaultCompositionStrategy.cs b/src/Orchard/Environment/DefaultCompositionStrategy.cs index d055ca555..238eb0447 100644 --- a/src/Orchard/Environment/DefaultCompositionStrategy.cs +++ b/src/Orchard/Environment/DefaultCompositionStrategy.cs @@ -5,6 +5,8 @@ using System.Reflection; using System.Text; using System.Web.Compilation; using Autofac; +using Orchard.Models; +using Orchard.Models.Records; using Orchard.Packages; namespace Orchard.Environment { @@ -14,6 +16,7 @@ namespace Orchard.Environment { IEnumerable GetAssemblies(); IEnumerable GetModuleTypes(); IEnumerable GetDependencyTypes(); + IEnumerable GetRecordTypes(); } public class DefaultCompositionStrategy : ICompositionStrategy { @@ -26,13 +29,13 @@ namespace Orchard.Environment { public IEnumerable GetAssemblies() { return _packageManager.ActivePackages() .Select(entry => entry.Assembly) - .Concat(new[]{typeof(IOrchardHost).Assembly}); + .Concat(new[] { typeof(IOrchardHost).Assembly }); //return BuildManager.GetReferencedAssemblies().OfType(); } public IEnumerable GetModuleTypes() { var types = _packageManager.ActivePackages().SelectMany(x => x.ExportedTypes); - types = types.Concat(typeof (IOrchardHost).Assembly.GetExportedTypes()); + types = types.Concat(typeof(IOrchardHost).Assembly.GetExportedTypes()); var nonAbstractClasses = types.Where(t => t.IsClass && !t.IsAbstract); var modules = nonAbstractClasses.Where(t => typeof(IModule).IsAssignableFrom(t)); return modules; @@ -45,5 +48,22 @@ namespace Orchard.Environment { var modules = nonAbstractClasses.Where(t => typeof(IDependency).IsAssignableFrom(t)); return modules; } + + public IEnumerable GetRecordTypes() { + var types = _packageManager.ActivePackages().SelectMany(x => x.ExportedTypes); + var recordTypes = types.Where(IsRecordType) + .Concat(new[] { typeof(ContentItemRecord), typeof(ContentPartRecord), typeof(ContentTypeRecord) }); + return recordTypes; + } + + + private static bool IsRecordType(Type type) { + return (type.Namespace.EndsWith(".Models") || type.Namespace.EndsWith(".Records")) && + type.GetProperty("Id") != null && + (type.GetProperty("Id").GetAccessors() ?? Enumerable.Empty()).All(x => x.IsVirtual) && + !type.IsSealed && + !type.IsAbstract && + !typeof(IContent).IsAssignableFrom(type); + } } } \ No newline at end of file diff --git a/src/Orchard/Models/ContentExtensions.cs b/src/Orchard/Models/ContentExtensions.cs index 710d36f9b..c3f5b5a21 100644 --- a/src/Orchard/Models/ContentExtensions.cs +++ b/src/Orchard/Models/ContentExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace Orchard.Models { @@ -47,6 +49,9 @@ namespace Orchard.Models { } + public static IEnumerable AsPart(this IEnumerable items) where T : class { + return items == null ? null : items.Where(item => item.Is()).Select(item => item.As()); + } } } diff --git a/src/Orchard/Models/ContentModule.cs b/src/Orchard/Models/ContentModule.cs new file mode 100644 index 000000000..e0f14a92c --- /dev/null +++ b/src/Orchard/Models/ContentModule.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Autofac.Builder; + +namespace Orchard.Models { + public class ContentModule : Module { + protected override void Load(ContainerBuilder builder) { + builder.Register().As().FactoryScoped(); + } + } +} diff --git a/src/Orchard/Models/DefaultContentManager.cs b/src/Orchard/Models/DefaultContentManager.cs index 966073f74..a53c299a6 100644 --- a/src/Orchard/Models/DefaultContentManager.cs +++ b/src/Orchard/Models/DefaultContentManager.cs @@ -145,6 +145,10 @@ namespace Orchard.Models { return context.Editors; } + public IContentQuery Query() { + return _context.Resolve(TypedParameter.From(this)); + } + private ContentTypeRecord AcquireContentTypeRecord(string contentType) { var contentTypeRecord = _contentTypeRepository.Get(x => x.Name == contentType); if (contentTypeRecord == null) { diff --git a/src/Orchard/Models/DefaultContentQuery.cs b/src/Orchard/Models/DefaultContentQuery.cs new file mode 100644 index 000000000..797070764 --- /dev/null +++ b/src/Orchard/Models/DefaultContentQuery.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using NHibernate; +using NHibernate.Criterion; +using NHibernate.Impl; +using NHibernate.Linq; +using Orchard.Data; +using Orchard.Models.Records; + +namespace Orchard.Models { + public class DefaultContentQuery : IContentQuery { + private readonly ISessionLocator _sessionLocator; + private ISession _session; + private ICriteria _itemCriteria; + + public DefaultContentQuery(IContentManager contentManager, ISessionLocator sessionLocator) { + _sessionLocator = sessionLocator; + ContentManager = contentManager; + } + + public IContentManager ContentManager { get; private set; } + + ISession BindSession() { + if (_session == null) + _session = _sessionLocator.For(typeof(ContentItemRecord)); + return _session; + } + + ICriteria BindItemCriteria() { + if (_itemCriteria == null) { + _itemCriteria = BindSession().CreateCriteria(); + } + return _itemCriteria; + } + + ICriteria BindCriteriaByPath(string path) { + var itemCriteria = BindItemCriteria(); + return itemCriteria.GetCriteriaByPath(path) ?? itemCriteria.CreateCriteria(path); + } + + + public IContentQuery ForType(params string[] contentTypeNames) { + BindCriteriaByPath("ContentType").Add(Restrictions.InG("Name", contentTypeNames)); + return this; + } + + + public IContentQuery Where() { + // this simply demands an inner join + BindCriteriaByPath(typeof(TRecord).Name); + return this; + } + + public IContentQuery Where(Expression> predicate) { + + // build a linq to nhibernate expression + var options = new QueryOptions(); + var queryProvider = new NHibernateQueryProvider(BindSession(), options); + var queryable = new Query(queryProvider, options).Where(predicate); + + // translate it into the nhibernate ICriteria implementation + var criteria = (CriteriaImpl)queryProvider.TranslateExpression(queryable.Expression); + + // attach the criterion from the predicate to this query's criteria for the record + var recordCriteria = BindCriteriaByPath(typeof(TRecord).Name); + foreach (var entry in criteria.IterateExpressionEntries()) { + recordCriteria.Add(entry.Criterion); + } + + return this; + } + + + public IEnumerable Select() { + return BindItemCriteria() + .List() + .Select(x => ContentManager.Get(x.Id)); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Models/IContentManager.cs b/src/Orchard/Models/IContentManager.cs index 81f26bedd..e8b0e2814 100644 --- a/src/Orchard/Models/IContentManager.cs +++ b/src/Orchard/Models/IContentManager.cs @@ -13,5 +13,7 @@ namespace Orchard.Models { IEnumerable GetDisplays(IContent contentItem); IEnumerable GetEditors(IContent contentItem); IEnumerable UpdateEditors(IContent contentItem, IUpdateModel updater); + + IContentQuery Query(); } } diff --git a/src/Orchard/Models/IContentQuery.cs b/src/Orchard/Models/IContentQuery.cs new file mode 100644 index 000000000..d282f8273 --- /dev/null +++ b/src/Orchard/Models/IContentQuery.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Orchard.Models { + public interface IContentQuery { + IContentManager ContentManager { get; } + + IContentQuery ForType(params string[] contentTypeNames); + + IContentQuery Where(); + IContentQuery Where(Expression> predicate); + + IEnumerable Select(); + + } +} \ No newline at end of file diff --git a/src/Orchard/Models/Records/ContentItemRecordAlteration.cs b/src/Orchard/Models/Records/ContentItemRecordAlteration.cs new file mode 100644 index 000000000..6e274e292 --- /dev/null +++ b/src/Orchard/Models/Records/ContentItemRecordAlteration.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; +using FluentNHibernate.Automapping; +using FluentNHibernate.Automapping.Alterations; + +namespace Orchard.Models.Records { + class ContentItemRecordAlteration : IAutoMappingAlteration { + private readonly IEnumerable _recordTypes; + + public ContentItemRecordAlteration() { + _recordTypes = Enumerable.Empty(); + } + public ContentItemRecordAlteration(IEnumerable recordTypes) { + _recordTypes = recordTypes; + } + + public void Alter(AutoPersistenceModel model) { + model.Override(mapping => { + foreach (var recordType in _recordTypes.Where(x => x.IsSubclassOf(typeof(ContentPartRecord)))) { + var type = typeof(Alteration<>).MakeGenericType(recordType); + var alteration = (IAlteration)Activator.CreateInstance(type); + alteration.Override(mapping); + } + }); + } + + interface IAlteration { + void Override(AutoMapping mapping); + } + + class Alteration : IAlteration where TPartRecord : ContentPartRecord { + public void Override(AutoMapping mapping) { + var name = typeof(TPartRecord).Name; + + + var syntheticMethod = new DynamicMethod(name, typeof(TPartRecord), null, typeof(ContentItemRecord)); + var syntheticProperty = new FakePropertyInfo(syntheticMethod); + + var parameter = Expression.Parameter(typeof(ContentItemRecord), "record"); + var syntheticExpression = (Expression>)Expression.Lambda( + typeof(Func), + Expression.Property(parameter, syntheticProperty), + parameter); + + mapping.HasOne(syntheticExpression).Access.NoOp().Fetch.Select(); + } + + private class FakePropertyInfo : PropertyInfo { + private readonly DynamicMethod _getMethod; + + public FakePropertyInfo(DynamicMethod dynamicMethod) { + _getMethod = dynamicMethod; + } + + public override object[] GetCustomAttributes(bool inherit) { + throw new NotImplementedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) { + throw new NotImplementedException(); + } + + public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) { + throw new NotImplementedException(); + } + + public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) { + throw new NotImplementedException(); + } + + public override MethodInfo[] GetAccessors(bool nonPublic) { + throw new NotImplementedException(); + } + + public override MethodInfo GetGetMethod(bool nonPublic) { + return _getMethod; + } + + public override MethodInfo GetSetMethod(bool nonPublic) { + throw new NotImplementedException(); + } + + public override ParameterInfo[] GetIndexParameters() { + throw new NotImplementedException(); + } + + public override string Name { + get { return _getMethod.Name; } + } + + public override Type DeclaringType { + get { throw new NotImplementedException(); } + } + + public override Type ReflectedType { + get { throw new NotImplementedException(); } + } + + public override Type PropertyType { + get { return _getMethod.ReturnType; } + } + + public override PropertyAttributes Attributes { + get { throw new NotImplementedException(); } + } + + public override bool CanRead { + get { return true; } + } + + public override bool CanWrite { + get { throw new NotImplementedException(); } + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) { + throw new NotImplementedException(); + } + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Models/Records/ContentPartRecord.cs b/src/Orchard/Models/Records/ContentPartRecord.cs index a40184e3c..c224731f8 100644 --- a/src/Orchard/Models/Records/ContentPartRecord.cs +++ b/src/Orchard/Models/Records/ContentPartRecord.cs @@ -1,42 +1,6 @@ -using System; -using System.Linq; -using FluentNHibernate.Automapping; -using FluentNHibernate.Automapping.Alterations; - namespace Orchard.Models.Records { public abstract class ContentPartRecord { public virtual int Id { get; set; } public virtual ContentItemRecord ContentItem { get; set; } } - - - public class ContentPartRecordAlteration : IAutoMappingAlteration { - public void Alter(AutoPersistenceModel model) { - - model.OverrideAll(mapping => { - var genericArguments = mapping.GetType().GetGenericArguments(); - if (!genericArguments.Single().IsSubclassOf(typeof(ContentPartRecord))) { - return; - } - - var type = typeof(Alteration<>).MakeGenericType(genericArguments); - var alteration = (IAlteration)Activator.CreateInstance(type); - alteration.Override(mapping); - }); - - } - - interface IAlteration { - void Override(object mapping); - } - - class Alteration : IAlteration where T : ContentPartRecord { - public void Override(object o) { - var mapping = (AutoMapping)o; - mapping.Id(x => x.Id).GeneratedBy.Foreign("ContentItem"); - mapping.HasOne(x => x.ContentItem); - } - } - } - } diff --git a/src/Orchard/Models/Records/ContentPartRecordAlteration.cs b/src/Orchard/Models/Records/ContentPartRecordAlteration.cs new file mode 100644 index 000000000..bfebb2515 --- /dev/null +++ b/src/Orchard/Models/Records/ContentPartRecordAlteration.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using FluentNHibernate.Automapping; +using FluentNHibernate.Automapping.Alterations; + +namespace Orchard.Models.Records { + public class ContentPartRecordAlteration : IAutoMappingAlteration { + public void Alter(AutoPersistenceModel model) { + + model.OverrideAll(mapping => { + var genericArguments = mapping.GetType().GetGenericArguments(); + if (!genericArguments.Single().IsSubclassOf(typeof(ContentPartRecord))) { + return; + } + + var type = typeof(Alteration<>).MakeGenericType(genericArguments); + var alteration = (IAlteration)Activator.CreateInstance(type); + alteration.Override(mapping); + }); + + } + + interface IAlteration { + void Override(object mapping); + } + + class Alteration : IAlteration where T : ContentPartRecord { + public void Override(object mappingObj) { + var mapping = (AutoMapping)mappingObj; + mapping.Id(x => x.Id).GeneratedBy.Foreign("ContentItem"); + mapping.HasOne(x => x.ContentItem).Constrained(); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Orchard.csproj b/src/Orchard/Orchard.csproj index d5098ab88..d898e5927 100644 --- a/src/Orchard/Orchard.csproj +++ b/src/Orchard/Orchard.csproj @@ -126,6 +126,8 @@ + + @@ -159,7 +161,10 @@ + + +