Orchard CMS ISessionConfigurationEvents and 1:N, N:N Relationships? - nhibernate

I have spent many days trying to implement relationships within OrchardCMS 1.9.1 between my custom contentParts to no avail.
Strewn across the internet are many others trying to achieve the same thing, who have also failed; giving me the impression that it's impossible?
Though recently I read an article at: http://www.ideliverable.com/blog/isessionconfigurationevents that gave the impression that all things possible with Fluent Nhibernate should be possible within Orchard.
So I implemented:
public class DbMapping : ISessionConfigurationEvents
{
public void Created(FluentConfiguration cfg, AutoPersistenceModel defaultModel)
{
defaultModel.UseOverridesFromAssemblyOf<ProfilePartRecord>().Alterations(x => x.AddFromAssemblyOf<ProfileOverride>());
defaultModel.UseOverridesFromAssemblyOf<LocationPartRecord>().Alterations(x => x.AddFromAssemblyOf<LocationOverride>());
}
public void Prepared(FluentConfiguration cfg) { }
public void Building(Configuration cfg) { }
public void Finished(Configuration cfg) { }
public void ComputingHash(Hash hash) { }
}
public class LocationOverride : IAutoMappingOverride<LocationPartRecord>
{
public void Override(AutoMapping<LocationPartRecord> mapping)
{
//[ Profile ] <--> [ Location ]
//mapping.Id(x => x.Id, "LocationPartRecord_id"); //As it's not in the model due to being a contentPart, NH will throw an error because of such.
mapping.Map(x => x.Type);
mapping.Map(x => x.Name);
mapping.References(x => x.ProfilePartRecord, "ProfilePartRecord_id");
}
}
public class ProfileOverride : IAutoMappingOverride<ProfilePartRecord>
{
public void Override(AutoMapping<ProfilePartRecord> mapping)
{
//[ Profile ] 0.1 <---> N [ Location ]
//NEW
mapping.HasMany(x => x.Locations)
.Inverse()
//.KeyColumn("ProfilePartRecord_id")
.Cascade.All()
.ForeignKeyCascadeOnDelete()
.ForeignKeyConstraintName("FK_Location__Profile");
}
}
MODELS:
public class ProfilePartRecord : ContentPartRecord
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
[CascadeAllDeleteOrphan]
public virtual IList<LocationPartRecord> Locations { get; set; }
public ProfilePartRecord()
{
Locations = new List<LocationPartRecord>();
}
}
public class LocationPartRecord : ContentPartRecord
{
public virtual string Type { get; set; }
public virtual string Name { get; set; }
//For HasMany
[CascadeAllDeleteOrphan]
public virtual ProfilePartRecord ProfilePartRecord { get; set; }
}
MIGRATION:
SchemaBuilder.CreateTable("ProfilePartRecord",
table => table
.ContentPartRecord()
//PK: ProfilePartRecord_id
.Column<string>("FirstName")
.Column<string>("LastName")
//System
.Column<DateTime>("CreatedAt")
);
ContentDefinitionManager.AlterPartDefinition("ProfilePart",
builder => builder.Attachable());
ContentDefinitionManager.AlterTypeDefinition("Profile", t => t
.WithPart(typeof(ProfilePart).Name)
.WithPart("UserPart")
);
ContentDefinitionManager.AlterTypeDefinition("User", t => t
.WithPart("ProfilePart")
);
SchemaBuilder.CreateTable("LocationPartRecord",
table => table
.ContentPartRecord()
//PK: LocationPartRecord_id
//FK:
.Column<int>("ProfilePartRecord_id")
.Column<string>("Type")
.Column<string>("Name")
//System
.Column<DateTime>("CreatedAt")
);
ContentDefinitionManager.AlterPartDefinition("LocationPart",
builder => builder.Attachable());
ContentDefinitionManager.AlterTypeDefinition("Location", type => type
.WithPart("CommonPart")
.WithPart("LocationPart")
.Creatable()
.Listable());
But alas, I still can't create a relationship between these two entities. I can do such via Migration, but this is very limited - as in - I can't set the relationship to Cascade.
Can anyone shed some light on whether this is possible, and if so, how? Thanks

This may not get you the whole way, but I believe it will help. One thing I have done for performance reasons as well as to establish relationships at the database level between my parts is to use the "CreateForeignKey" and "CreateIndex" in the Migration. Here is an example that should work for you
// Add foreign key
SchemaBuilder.CreateForeignKey(
"FK_LocationProfile",
"LocationPartRecord", new[] { "ProfilePartRecord_id" },
"ProfilePartRecord", new[] { "Id" });
// Add index
SchemaBuilder.AlterTable("LocationPartRecord",
table => table
.CreateIndex("IDX_ProfilePartRecord_Id", "ProfilePartRecord_Id")
);
With these relationships defined, I wonder if that will in any way impact the NHibernate work you are doing.
As for how we have done the overall goal I believe you are trying to achieve, you can monitor the "ProfilePart" "Delete" event in the "LocationPart" handler and apply your own cascading delete logic there to ensure that there are no "LocationPart" left around.

Related

Fluent Nhibernate - how do i specify table schemas when auto generating tables in SQL CE 4

I am using SQL CE as a database for running local and CI integration tests (normally our site runs on normal SQL server). We are using Fluent Nhibernate for our mapping and having it create our schema from our Mapclasses. There are only two classes with a one to many relationship between them. In our real database we use a non dbo schema. The code would not work with this real database at first until i added schema names to the Table() methods. However doing this broke the unit tests with the error...
System.Data.SqlServerCe.SqlCeException : There was an error parsing the query. [ Token line number = 1,Token line offset = 26,Token in error = User ]
These are the classes and associatad MapClasses (simplified of course)
public class AffiliateApplicationRecord
{
public virtual int Id { get; private set; }
public virtual string CompanyName { get; set; }
public virtual UserRecord KeyContact { get; private set; }
public AffiliateApplicationRecord()
{
DateReceived = DateTime.Now;
}
public virtual void AddKeyContact(UserRecord keyContactUser)
{
keyContactUser.Affilates.Add(this);
KeyContact = keyContactUser;
}
}
public class AffiliateApplicationRecordMap : ClassMap<AffiliateApplicationRecord>
{
public AffiliateApplicationRecordMap()
{
Schema("myschema");
Table("Partner");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.CompanyName, "Name");
References(x => x.KeyContact)
.Cascade.All()
.LazyLoad(Laziness.False)
.Column("UserID");
}
}
public class UserRecord
{
public UserRecord()
{
Affilates = new List<AffiliateApplicationRecord>();
}
public virtual int Id { get; private set; }
public virtual string Forename { get; set; }
public virtual IList<AffiliateApplicationRecord> Affilates { get; set; }
}
public class UserRecordMap : ClassMap<UserRecord>
{
public UserRecordMap()
{
Schema("myschema");
Table("[User]");//Square brackets required as user is a reserved word
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Forename);
HasMany(x => x.Affilates);
}
}
And here is the fluent configuraton i am using ....
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(
MsSqlCeConfiguration.Standard
.Dialect<MsSqlCe40Dialect>()
.ConnectionString(ConnectionString)
.DefaultSchema("myschema"))
.Mappings(m => m.FluentMappings.AddFromAssembly(typeof(AffiliateApplicationRecord).Assembly))
.ExposeConfiguration(config => new SchemaExport(config).Create(false, true))
.ExposeConfiguration(x => x.SetProperty("connection.release_mode", "on_close")) //This is included to deal with a SQLCE issue http://stackoverflow.com/questions/2361730/assertionfailure-null-identifier-fluentnh-sqlserverce
.BuildSessionFactory();
}
The documentation on this aspect of fluent is pretty weak so any help would be appreciated
As usual, 10 minutes after posting i answer my own questions. The trick was to not declare the schema in the the ClassMaps. Instead i used the DefaultSchema method in the fluent configuration. So my actual 'live' configuration looks like this :
var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("connectionStringKey"))
.DefaultSchema("myschema"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<AffiliateApplicationRecord>());
return configuration;
And my integration tests look like this:
return Fluently.Configure()
.Database(
MsSqlCeConfiguration.Standard
.Dialect<MsSqlCe40Dialect>()
.ConnectionString(ConnectionString))
.Mappings(m => m.FluentMappings.AddFromAssembly(typeof(AffiliateApplicationRecord).Assembly))
.ExposeConfiguration(config => new SchemaExport(config).Create(false, true))
.ExposeConfiguration(x => x.SetProperty("connection.release_mode", "on_close")) //This is included to deal with a SQLCE issue http://stackoverflow.com/questions/2361730/assertionfailure-null-identifier-fluentnh-sqlserverce
.BuildSessionFactory();
Hopefully someone else will get somevalue out of this...
I think this has more to do with SQL Server CE than nhibernate. I am pretty sure that Sql Server CE will not accept schemas at all. its not a supported feature.
See the Create Table Documentation on MSDN

Fluent Nhibernate Mapping - one-to-many inside a Value Object?

Hi I'm struggling do refine/refactoring a domain model and trying to move logic from application services into my domain model. Now I'm stuck with a NHibernate issue.
The model is a WorkEvaluation class that contains a Questionaire Template with Questions and it also contains a collection of QuestionWeight classes. The thing is that WorkEvaluation class also has an important property HitInterval that belongs closed to the QuestionWeight collection in WorkEvaluation. The concept is that you conduct an evaluation by answering a lot of questions (the anserws are excluded in this example) and finaly you apply some weights (percent weights) that modify answer scores. That means you can make some questions more important and other less important. Hit interval is also a tuning parameter that you use when you calculate TOTAL WorkEvaluation score (including weight modifications) and the result is for example: Totalscore = 100, Hitinterval 5% than we get a totalinterval of 95-105 and can be used to match other evaluations.
Enough of background.
I Want to encapsulate both list of QuestionWeights and HitInterval in a Value Object QuestionScoreTuning since these belongs together and should be applied at the same time.
And I also want to add some business logic into QuestionScoreTuning that do not belongs to workEvaluation.
How do I map i Fluent Nhibernate a Value Object (Component) that has the one-to-many collection and HitInterval and the reference back? This is my current code:
public class WorkEvaluation : DomainBase<long>, IAggregateRoot
{
public void ApplyTuning(QuestionScoreTuning tuning)
{
QuestionScoreTuning = tuning;
//TODO Raise Domain Event WorkEvaluationCompleted -
// which should recalculate all group scores
}
public QuestionScoreTuning QuestionScoreTuning { get; protected set; }
}
public class QuestionScoreTuning : ValueObject
{
private IList<QuestionWeight> _questionWeights;
public QuestionScoreTuning(IList<QuestionWeight> listOfWeights, long hitInterval)
{
_questionWeights = listOfWeights;
HitInterval = hitInterval;
}
public long HitInterval { get; protected set; }
protected override IEnumerable<object> GetAtomicValues()
{
return _questionWeights.Cast<object>();
}
/// <summary>
/// A list of all added QuestionWeights for this WorkEvaluation
/// </summary>
public IList<QuestionWeight> QuestionWeights
{
get { return new List<QuestionWeight>(_questionWeights); }
protected set { _questionWeights = value; }
}
protected QuestionScoreTuning()
{}
}
public class QuestionWeight : DomainBase<long>, IAggregateRoot
{
public QuestionWeight(Question question, WorkEvaluation evaluation)
{
Question = question;
WorkEvaluation = evaluation;
}
public Weight Weight { get; set; }
public Question Question { get; protected set; }
public WorkEvaluation WorkEvaluation { get; protected set; }
public override int GetHashCode()
{
return (Question.GetHashCode() + "|" + Weight).GetHashCode();
}
protected QuestionWeight()
{}
}
Fluent Mappings:
public class WorkEvaluationMapping : ClassMap<WorkEvaluation>
{
public WorkEvaluationMapping()
{
Id(x => x.ID).GeneratedBy.Identity();
References(x => x.SalaryReview).Not.Nullable();
References(x => x.WorkEvaluationTemplate).Column("WorkEvaluationTemplate_Id").Not.Nullable();
Component(x => x.QuestionScoreTuning, m =>
{
m.Map(x => x.HitInterval, "HitInterval");
m.HasMany(x => x.QuestionWeights).KeyColumn("WorkEvaluation_id").Cascade.All();
});
}
}
public class QuestionWeightMapping : ClassMap<QuestionWeight>
{
public QuestionWeightMapping()
{
Not.LazyLoad();
Id(x => x.ID).GeneratedBy.Identity();
Component(x => x.Weight, m =>
{
m.Map(x => x.Value, "WeightValue");
m.Map(x => x.TypeOfWeight, "WeightType");
});
References(x => x.Question).Column("Question_id").Not.Nullable().UniqueKey(
"One_Weight_Per_Question_And_WorkEvaluation");
References(x => x.WorkEvaluation).Column("WorkEvaluation_id").Not.Nullable().UniqueKey(
"One_Weight_Per_Question_And_WorkEvaluation");
}
}
All I want to accomplish is to move collection of QuestionWeights and HitInterval into a Value Object (Component mapping) since these will still be inside db table WorkEvaluation.
P.S I've look at some example solution DDDSample.net (Eric Evans DDD example in c#) and they accomplished this with the Itinerary class that takes a list as ctor parameter and is mapped as a Cargo component. Difference is that example has a list of valueobjects Leg BUT Leg has references to Location which is an entity class.
Hopefully maybe someone knows how to accomplish this. Thanks in advance...
/Bacce
Well. I Finally solved it. Now my WorkEvaluation object can be Applied with a QuestionScoreTuning object (a valueobject) that contains the list of weight and hitinterval. This turns out great and if anyone want more info about having collections inside value objects and mapping them in fluent NH, please ask here with a comment. I can supply code examples...

NHibernate 3 LINQ : How to filter IQueryable to select only objects of class T and its subclasses?

I want to upgrade my application to use NHiberante 3 instead of NHibernate 2.1.2 but faced some problems with the new LINQ provider. This question is about one of them. Assume that I have a following hierarchy of classes:
public abstract class PageData
{
public int ID { get; set; }
public string Title { get; set; }
}
public class ArticlePageData : PageData
{
public DateTime PublishedDate { get; set; }
public string Body { get; set; }
}
public class ExtendedArticlePageData : ArticlePageData
{
public string Preamble { get; set; }
}
I use Fluent NHibernate to map these classes to the database:
public class PageDataMap : ClassMap<PageData>
{
public PageDataMap()
{
Table("PageData");
Id(x => x.ID);
Map(x => x.Title);
DiscriminateSubClassesOnColumn("PageType");
}
}
public class ArticlePageDataMap : SubclassMap<ArticlePageData>
{
public ArticlePageDataMap()
{
Join("ArticlePageData", p =>
{
p.KeyColumn("ID");
p.Map(x => x.PublishedDate);
p.Map(x => x.Body);
});
}
}
public class ExtendedArticlePageDataMap : SubclassMap<ExtendedArticlePageData>
{
public ExtendedArticlePageDataMap ()
{
Join("ExtendedArticlePageData", p =>
{
p.KeyColumn("ID");
p.Map(x => x.Preamble);
});
}
}
And then I want to query all pages and do some filtering:
IQueryable<PageData> pages = session.Query<PageData>();
...
var articles = pages.OfType<ArticlePageData>().Where(x => x.PublishedDate >= (DateTime.Now - TimeSpan.FromDays(7))).ToList();
NHibernate 3.0.0 fails with the NotSupported exception in this case, but there is bugfix NH-2375 in the developing version of NH which leads this code to work. But, unfortunately, OfType() method filters the objects by exact type and only selects objects of ArticlePageData class. The old Linq to NH provider selects ArticlePageData and ExtendedArticlePageData in the same case.
How can I do such filtering (select only objects of class T and its subclasses) with the new Linq to NH provider?
session.Query<T>().OfType<SubT>() makes little sense, and it won't let you filter on properties of the subclass. Use session.Query<SubT>() instead.
You can use
var articles = pages.AsEnumerable().OfType<ArticlePageData>().Where(x => x.PublishedDate >= (DateTime.Now - TimeSpan.FromDays(7))).ToList();
and wait for NHibernate 3.0.1.
or maybe you can use
session.Query<ArticlePageData>()
instead of
session.Query<PageData>()

Cascade Saves with Fluent NHibernate AutoMapping

How do I "turn on" cascading saves using AutoMap Persistence Model with Fluent NHibernate?
As in:
I Save the Person and the Arm should also be saved. Currently I get
"object references an unsaved transient instance - save the transient instance before flushing"
public class Person : DomainEntity
{
public virtual Arm LeftArm { get; set; }
}
public class Arm : DomainEntity
{
public virtual int Size { get; set; }
}
I found an article on this topic, but it seems to be outdated.
This works with the new configuration bits. For more information, see http://fluentnhibernate.wikia.com/wiki/Converting_to_new_style_conventions
//hanging off of AutoPersistenceModel
.ConventionDiscovery.AddFromAssemblyOf<CascadeAll>()
public class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public bool Accept( IOneToOnePart target )
{
return true;
}
public void Apply( IOneToOnePart target )
{
target.Cascade.All();
}
public bool Accept( IOneToManyPart target )
{
return true;
}
public void Apply( IOneToManyPart target )
{
target.Cascade.All();
}
public bool Accept( IManyToOnePart target )
{
return true;
}
public void Apply( IManyToOnePart target )
{
target.Cascade.All();
}
}
Updated for use with the the current version:
public class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public void Apply(IOneToOneInstance instance)
{
instance.Cascade.All();
}
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.All();
}
public void Apply(IManyToOneInstance instance)
{
instance.Cascade.All();
}
}
The easiest way I've found to do this for a whole project is to use DefaultCascade:
.Conventions.Add( DefaultCascade.All() );
Go to "The Simplest Conventions" section on the wiki, for this, and a list of others.
Here's the list from the Wiki:
Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")
A word of warning - some of the method names in the Wiki may be wrong. I edited the Wiki with what I could verify (i.e. DefaultCascade and DefaultLazy), but can't vouch for the rest. But you should be able to figure out the proper names with Intellisense if the need arises.
The Convention Method Signatures have changed. For the new answer that does exactly what this question asks see THIS QUESTION.
You can also make cascading the default convention for all types. For example (using the article you linked to as a starting point):
autoMappings.WithConvention(c =>
{
// our conventions
c.OneToOneConvention = o => o.Cascade.All();
c.OneToManyConvention = o => o.Cascade.All();
c.ManyToOneConvention = o => o.Cascade.All();
});

How do I override a convention's cascade rule in fluent nhibernate

I have two classes
public class Document
{
public virtual int Id { get; set; }
public virtual IList<File> Files { get; set; }
}
public class File
{
public virtual int Id { get; protected set; }
public virtual Document Document { get; set; }
}
with the following convention:
public class HasManyConvention : IHasManyConvention
{
public bool Accept(IOneToManyPart target)
{
return true;
}
public void Apply(IOneToManyPart target)
{
target.Cascade.All();
}
}
and these mapping overrides
public class DocumentMappingOverride : IAutoMappingOverride<Document>
{
public void Override(AutoMap<Document> mapping)
{
mapping.HasMany(x => x.Files)
.Inverse()
// this line has no effect
.Cascade.AllDeleteOrphan();
}
}
public class FileMappingOverride : IAutoMappingOverride<File>
{
public void Override(AutoMap<File> mapping)
{
mapping.References(x => x.Document).Not.Nullable();
}
}
I understand that I need to make an IClassConvention for Document to
change the cascade behaviour, however I can't get this to work!
If i do this:
public class DocumentConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return target.EntityType == typeof(Document);
}
public void Apply(IClassMap target)
{
target.SetAttribute("cascade", "all-delete-orphan");
}
}
I get: "The 'cascade' attribute is not declared."
If i do this:
public class DocumentConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return target.EntityType == typeof(Document);
}
public void Apply(IClassMap target)
{
target.HasMany<Document, File>(x => x.Files)
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
Then I get:
"Duplicate collection role mapping Document.Files"
so i added:
mapping.IgnoreProperty(x => x.Files);
to my document mapping, but then Files is always empty.
What am I doing wrong?
How can I override the cascade rule for a single HasMany relationship?
Thanks
Andrew
P.s. Sorry for the cross post with this but I need to get this solved asap.
I know this was forever ago (in computer time) and you might have already solved this. In case you haven't or someone else with a similar question sees this, here goes:
I think you need to create a class that implements IHasManyConvention. IClassConvention modifies an IClassMap (the <class> element) target. cascade is not a valid attribute for <class> so that accounts for the first error. On your second attempt, you were re-mapping the collection, resulting in the "duplicate collection" error.
IHasManyConvention targets an IOneToManyPart, upon which you should be able to call Cascade.AllDeleteOrphan() or just SetAttribute("cascade", "all-delete-orphan") if the former didn't work for some reason.
EDIT
Sorry, I missed that you already had a IHasManyConvention. Since you want to override your convention for just one type, you should just change the Accept method on your convention for that type. Instead of return true;, pull in what you had on your DocumentConvention:
return target.EntityType == typeof(Document);
I believe that OneToManyPart.EntityType references the containing entity type (i.e. Document).