NHibernate fluent Prevent children being updated - fluent-nhibernate

The application I have uses localization. The way it is built, is that it examines an entity (traversing down the structure) and translates every property marked as 'translate'.
The translations are stored in separate translation tables.
This is all fine but it leaves me with the problem that I now get the translated values in my 'default' values when I update the entity using the translations. And I don't want that.
Let me try and explain better.
Database:
The mapping of footprintlinevLue:
public class FootprintLineValueMap : ClassMap<FootprintLineValue> {
public FootprintLineValueMap() {
Table("FootprintLineValue");
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.FootprintLine).Column("FootprintLineId");
References(x => x.CategoryValue).Column("CategoryValueId").Cascade.None();
}
As you can see a footprintline has multiple values that reference a categoryValue. The categoryvalue is localized.
When I now retrieve footprintlines our framework will put it through our translationservice and will automatically translate the Name and Description of the CategoryValue in the corresponding culture. If it cant find a translation in CategoryValueLocal, it will use the default in CategoryValue.
However...if I save a Footprintline, it will save the translated values back into CategoryValue (overwriting the default) instead of ignore it.
CategoryValues are not value objects and could be changed so I cant make them readonly.
I tried to map the reference as Cascade.None, but that doesn't seem to do anything.
I hope there is a way to simply mark this in the mapping so we can keep on using our TranslationService instead of having to figure out another way to hande localization.

mark the properties as not updateable.
Map(x => x.Description).Not.Update();
you could even define a convention to do so
class TranslatedPropertiesConvention : AttributePropertyConvention<Translated>
{
public void Apply(FluentNHibernate.Conventions.Instances.IPropertyInstance instance)
{
instance.Not.Update();
}
}

Related

How can I detect NHibernate HasManyToMany mapping at run-time?

I am trying to detect HasManyToMany relationships in entities at run-time for testing purposes. I've had many problems with people writing bad mappings so I wrote a test to test every single mapping on our continuous integration server.
Right now I can test every entity, composite and non-composite by detecting the mapped Id property(s) and calling .Get() or a composite getter. Most of which is done using reflection. I am using GetClassMetadata while going over every entity type.
But I missed testing something that was broken, a HasManyToMany.
I am using Fluent NHibernate, and the mapping is:
mapping.HasManyToMany<ServiceType>(x => x.ServiceTypes)
.Schema("Marketplace")
.Table("ListingServiceTypes")
.ParentKeyColumn("PackageID")
.ChildKeyColumn("ServiceTypeID")
.Cascade.SaveUpdate().LazyLoad();
Now since there is no entity to "test" this relationship with, I do not run over it.
What I need to know is how can I find the properties of an object that are mapped with "HasManyToMany". I can invoke them just fine, if I could only detect them.
I do not want to have to force lazy loading of every collection because if the mapping for the individual entities are correct, the mappings will be, because we use conventions for them.
Get the source code of FluentNHibernate, and copy to your project HasManyToManyStep.cs (FluentNHibernate.Automapping.Steps)
Add your logic to ShouldMap() method. This method is called by FNH to detect Many To Many relations. You can alter the way many to many relations are determined (for example by an attribute). In your case you want probably add a an attribute by reflection to tag the properties...
Replace the default step with your new one :
public class MyMappingConfiguration : DefaultAutomappingConfiguration
{
public override IEnumerable<IAutomappingStep> GetMappingSteps(AutoMapper mapper, IConventionFinder conventionFinder)
{
var steps = base.GetMappingSteps(mapper, conventionFinder);
var finalSteps = steps.Where(c => c.GetType() != typeof(FluentNHibernate.Automapping.Steps.HasManyToManyStep)).ToList();
var idx = finalSteps.IndexOf(steps.Where(c => c.GetType() == typeof(PropertyStep)).First());
finalSteps.Insert(idx + 1, new MyCustomHasManyStep(this));
return finalSteps;
}
}
Had to do this today.
var CollectionMetaData = SessionFactory.GetCollectionMetadata(T.FullName + '.' + info.Name);
if (CollectionMetaData is NHibernate.Persister.Collection.BasicCollectionPersister)
{
if (((NHibernate.Persister.Collection.BasicCollectionPersister)CollectionMetaData).IsManyToMany)
{
//Do something.
}
}
where T is the Type and info is the property info from T.GetProperties()

Fluent NHibernate - override type for one specific property on one specific class?

I have a class that has a password property that I want to store encrypted in the db. The property is a string type, and I have a custom type EncryptedStringType that I want NHibernate to use to map this to the database. Here is my relevant automapping code:
var mappings = AutoMap.AssemblyOf<Business>()
.Where(x=>x.IsSubclassOf(typeof(EntityBase)))
.IgnoreBase(typeof(EntityBase))
.Conventions.Add
(
ConventionBuilder.Id.Always(x =>
x.GeneratedBy.HiLo(HILO_TABLE, HILO_COLUMN, HILO_MAX_LO)),
ConventionBuilder.HasMany.Always(x => x.Cascade.AllDeleteOrphan()),
Table.Is(o => Inflector.Pluralize(o.EntityType.Name)),
PrimaryKey.Name.Is(o => "Id"),
ForeignKey.EndsWith("Id"),
DefaultLazy.Always(),
DefaultCascade.All()
);
I cannot figure out the syntax to override the type for the UserPassword property of the Business class though. I thought I should be able to do something with overrides like:
mappings.Override<Business>(map=> /* Not sure what to do here */);
Any help is appreciated.
Found the answer myself.
mappings.Override<Business>(map =>
{
map.Map(x => x.UserPassword).CustomType<EncryptedStringType>();
});
You could always create a mapping override class. Any conventions that can still be applied will be, but you can basically specify mappings similarly to a ClassMap that override the default conventions.
Using the call to mappings.Override(), it'd look something like:
mappings.Override<Business>(map=>map.Map(x=>x.UserPassword).CustomType(typeof(EncryptedStringType)));

Fluent NHibernate - Setting CutomType IIdConvention

I have the following IIdConvention for a FluentNHibernate automapping. I want all of my id properties to use a custom type that is represented by a string property but the CustomType is never applied to my mappings.
public class PrimaryKeyHasTableName : FluentNHibernate.Conventions.IIdConvention
{
public void Apply(FluentNHibernate.Conventions.Instances.IIdentityInstance instance)
{
instance.Column(instance.EntityType.Name + "Id");
instance.CustomType<CustomIdType>();
}
}
When I looked into the FluentNHibernate source it appears that the Type for the id property has already been set so it is not being set by my convention.
If I use a ClassMap to map the class manually I have not problem setting the CustomType for the Identity property.
Id(x => x.Id)
.Column("UserId")
.CustomType<OnFileIdType>();
Does anybody know how I can successfully set the custom id property using a convention?
Or get my convention to run earlier in the mapping process so that the Type isn't already set by the time my code runs.
Also, here's my configuration code:
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(connString))
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<BaseEntity>();
m.AutoMappings.Add(AutoMap.AssemblyOf<BaseEntity>()
.Where(t => t.Namespace.EndsWith("Models.Domain"))
.Conventions.AddFromAssemblyOf<BaseEntity>()
.UseOverridesFromAssemblyOf<BaseEntity>()
);
})
.ExposeConfiguration(CreateSchema)
.BuildSessionFactory();
Thanks.
I don't think you can achieve what you want with conventions.
One thing you can try is having all your entities subclass from an abstract entity that defines the Id property and has the custom type mapping for it.
I wouldn't recommend this however, It will just open room for more automapping problems.
Just go for the manual CustomType for each class map.
I'm having the exact same problem, it would be great if FNH's IIdConvention supported this.
After trying a few things and reading this I have resigned myself to implementing IAutoMappingOverride for entities using a custom type for their Id.
public class ProductMap : IAutoMappingOverride<Product>
{
public void Override(AutoMapping<Product> mapping)
{
mapping.Id(x => x.Id).CustomType<ProductId>();
}
}
Using automapping override instead of ClassMap will continue to automap the rest of your properties. Alter your AutoMappings initialisation code to include
.Overrides.AddFromAssemblyOf<BaseEntity>()
I am getting the same problem. I believe that it is arising from the invocation of Conventions.AddFromAssemblyOf<BaseEntity>(), which is causing your custom convention to not be applied.
(By the way, the posted code will work only if your convention classes are in the same assembly as your BaseEntity - it might be safer to use Conventions.AddFromAssemblyOf<PrimaryKeyHasTableName>())
A bug was raised about AddFromAssemblyOf back in February 2011: Fluent NHibernate - Setting CutomType IIdConvention. There is no resolution posted.
The work-around appears to be to add each of the Conventions explicitly
Conventions.Add<PrimaryKeyHasTableName>()
To check this, you can add the following line after the line which adds the automappings, in order to export the mappings to an hbm.xml file on your hard drive, and take a look at the generated mappings.
m.AutoMappings.ExportTo(#"C:\ExportedMappings")
Also, I suggest you add a breakpoint into the Apply method and run the code to ensure that it is being invoked.)

Fluent NHibernate - mapping an Entity as a different type

I have a class which I would like to map as a component onto any table which contains it:
public class Time
{
public int Hours { get; set; }
public int Minutes { get; set; }
public int Seconds { get; set; }
}
I would like to store this class as a bigint in the database - the same as how TimeSpan is stored but my class has completely different behaviour so I decided to create my own.
I'm using FLH's automapper and have this class set as a component (other classes have Time as a property). I've got as far as creating an override but am not sure how to go about mapping it:
I gave it a try this way:
public class TimeMappingOverride : IAutoMappingOverride<Time>
{
public void Override(AutoMapping<Time> mapping)
{
mapping.Map(x => x.ToTimeSpan());
mapping.IgnoreProperty(x => x.Hours);
mapping.IgnoreProperty(x => x.Minutes);
mapping.IgnoreProperty(x => x.Seconds);
}
}
But got this error:
Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MethodCallExpression'.
How should I go about this?
Details of components can be found here: http://wiki.fluentnhibernate.org/Fluent_mapping#Components
But first of all, you can't map a method.
Assuming you change ToTimeSpan() to a property AsTimeSpan, there are two ways to do it, only the harder of which will work for you because you are using automapping:
Create a ComponentMap<Time> -- once done, your existing mapping will just work. This is not compatible with automapping.
Declare the component mapping inline:
mapping.Component(x => x.AsTimeSpan, component => {
component.Map(Hours);
component.Map(Minutes);
component.Map(Seconds);
});
You'll have to do this every time, though.
Of course, this doesn't address "I would like to store this class as bigint…"
Are you saying you want to persist it as seconds only? If so, scratch everything at the top and again you have two options:
Implement NHibernate IUserType (ugh)
Create a private property or field that stores the value as seconds only, and wire only this up to NHibernate. The getters and setters of the pubic properties will have to convert to/from seconds.
I personally haven't worked with AutoMappings yet, but my suggestion would be to look into NHibernate's IUserType to change how a type is being persisted. I believe that's a cleaner way of defining your custom mapping of Time <-> bigint.
Reading the code above, Map(x => x.ToTimeSpan()) will not work as you cannot embed application-to-database transformation code into your mappings. Even if that would be possible, the declaration misses the transformation from the database to the application. A IUserType, on the other hand, can do custom transformations in the NullSafeGet and NullSafeSet methods.

Automapping doesn't have an Id mapped

My Entity Class:
public class Building
{
/// <summary>
/// internal Id
/// </summary>
public virtual long Id { get; set; }
..............
}
My Mapping:
var model = AutoMap.AssemblyOf<Building>()
.Setup(s => s.FindIdentity = p => p.Name == "Id")
.Where(t => t.Namespace == "SpikeAutoMappings");
var database = Fluently.Configure()
.Database(DatabaseConfigurer)
.Mappings(m=>m.AutoMappings.Add(model));
I need somebody to help me see what is wrong because I keep having this error when run unit test:
Initialization method TestProject1.MappingTestBase.TestInitialize threw exception. FluentNHibernate.Cfg.FluentConfigurationException: FluentNHibernate.Cfg.FluentConfigurationException: An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
---> FluentNHibernate.Visitors.ValidationException: The entity doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id)..
both answers above are right; unless you specify differently, the automapper assumes that you have an int Id field.
If your Id is long, the automapper might not recognize it correctly.
try defining a MappingOverride for your class(es), like so:
public class UserMappingOverride : IAutoMappingOverride<User>
{
#region IAutoMappingOverride<User> Members
public void Override(AutoMapping<User> mapping)
{
mapping.Id(u => u.Name);
}
#endregion
}
the Id() function allows you to override the automapper's convention of what the ID field should be.
for further info on overriding, see http://wiki.fluentnhibernate.org/Auto_mapping#Overrides.
Cheers,
Jhonny
Generally, using AutoMapping is a poor policy because the filed Id must exist in your database tables. Instead, consider using a fluent mapping generator, such as NMG to handle your mapping.
In this case, you would first want to download/install the application, then generate the Mapping Files from your database (Oracle, SQL and various others).
In order to create the Mapping Files, first create an /Entities/ folder within your project. Next, configure the generator software as follows:
Preferences
Generated Property Name = Same as database column name (No change)
Mapping Style = Fluent Mapping
Field or Property = Auto Property
Languages available: C# and VB
Folder : [your project folder]\Entities
Namespace : [your project namespace].Entities
Assembly Name: [your project name].Entities
Next, either Generate All or Generate the Specific Table.
All of the *.cs and *Map.cs files should now be created in your project (you can add them with Add Existing Item... if they don't show up).
Using Fluent, you will see something like the following:
Id(x => x.keyName_ID)
.Column(x => x.keyname_ID)
.GeneratedBy
.Sequence("keyname_ID")
or
Id(x => x.keyName_ID)
.Column(x => x.keyname_ID)
.GeneratedBy
.Identity()
.Column("keyname_ID")
or
Id(x => x.keyName_ID)
.Column(x => x.keyname_ID)
.GeneratedBy
.Assigned()
So, now we need to specify the Id using FluentMapping with Fluent nHibernate. To do this, you need to overwrite the Id line of on code in each of the Map files in the solution. Simply add:
Id(x => x.KeyName_ID)
.GeneratedBy
.GetGeneratorMapping()
.IsSpecified("KeyName_ID");
Where keyname_id is the column name of the id in your database, rather than the one created.
Notice that in your mapping at the BuildSession you must have:
(...).Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<[one of your entities]>()
);
And, now Id is mapped. :) I hope this helps!
My experience with Automapping is that as long as your Entity class has the line:
public virtual int Id { get; private set; }
the automapper will treat it as an ID with no further help from the programmer (i.e. no need for the FindIdenity code you are using in your AutoMap call).
The only difference I see in your ID declaration is that you use a type long instead of int. Don't know if this matters or not.