Many-to-One with fixed values for a missing column - c#

I'm dealing with a legacy database that has a locked schema. The problem I'm facing is that a number of the tables are keyed off known/fixed/hard-coded entity type Id values instead of having a column values. This means that I can't use the normal References construct.
For the tables with the ENTITY_TYPEID I can do this:
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
References(x => x.Type)
.Columns("ENTITY_SECTION","ENTITY_TYPEID");
}
}
which happily populates the Entity.Type.
For the tables of a fixed/known/hard-coded type I need to map to a hard-coded value instead of the ENTITY_TYPE column, so to para-phrase in code:
public class EntityXMap : ClassMap<EntityX>
{
public EntityXMap(int entityType)
{
References(x => x.Type)
.Columns("ENTITY_SECTION", "ENTITY_TYPE = 123" );
}
}
HasMany() has a Where() construct that I could use in that case...
Any ideas how to achieve something similar here?

maybe overkill but you could try
// add to config
var typemap = new TypeMap();
typemap.Id(x => x.Section, "ENTITY_SECTION");
typemap.Where("ENTITY_TYPE = 123");
typemap.EntityName("Type for EntityX");
References(x => x.Type)
.Column("ENTITY_SECTION")
.EntityName("Type for EntityX");

Related

Can NHibernate be configured to automatically call .CustomType<T> for all columns of a certain type?

I have a model that has a property of an Enum type:
public virtual Units Unit { get; set; } // Units is an enum
I have a class called EnumMapper which handles some custom mapping logic I have for use with my database. In my mapping, I have:
Map(x => x.Unit).CustomType<EnumMapper<Units>>();
This works great. No problems at all. However, I have quite a few models that have properties of type Units as well. Rather than call .CustomType<T>() on each one of these, I'm wondering if I can add something to my FluentConfiguration object to tell NHibernate to use this type mapping on any and all properties of type Units. Here's how I configure NHibernate so far:
private ISessionFactory InitializeSessionFactory()
{
sessionFactory = Fluently.Configure()
.Database(DatabaseConfiguration)
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<DatabaseAdapter>()
.Conventions.Add(Table.Is(x => x.EntityType.Name.ToLowerInvariant())) // All table names are lower case
.Conventions.Add(ForeignKey.EndsWith("Id")) // Foreign key references end with Id
.Conventions.Add(DefaultLazy.Always()))
.BuildSessionFactory();
return sessionFactory;
}
I have a feeling that it's as simple as calling .Conventions.Add() on something else, but I can't seem to get it right.
Figured out one solution using ConventionBuilder:
.Conventions.Add(ConventionBuilder.Property.When(
c => c.Expect(x => x.Type == typeof(GenericEnumMapper<Units>)),
x => x.CustomType<EnumMapper<Units>>()
))
It's not super pretty, but it works. I think a better solution might be to write my own convention that automatically maps an enum to its correct custom type. I will post that here if I get it working.
Update: Cleaned this up a bit. I've added a static method to my EnumMapper<T> class:
public class EnumMapper<T> : NHibernate.Type.EnumStringType<T>
{
// Regular mapping code here
public static IPropertyConvention Convention
{
get
{
return ConventionBuilder.Property.When(
c => c.Expect(x => x.Type == typeof (GenericEnumMapper<T>)),
x => x.CustomType<EnumMapper<T>>()
);
}
}
}
Now I can just configure it with:
.Conventions.Add(EnumMapper<Units>.Convention)

Same table NHibernate mapping

How can i go about defining a same table relation mapping (mappingbycode) using Nhibernate
for instance let's say I have a class:
public class Structure{
public int structureId;
public string structureName;
public Structure rootStructure;
}
that references the same class as rootStructure.
mapper.Class<Structure>(m =>
{
m.Lazy(true);
m.Id(u => u.structureId, map => { map.Generator(Generators.Identity); });
m.Property(c => c.structureName);
m.? // Same table mapping
}
;
Thanks
there is no special mapping for recursive mappings i am aware of. Just map it like you would map a collection of a different class. In your case this should work (untested though):
m.OneToOne(c => c.rootStructure, a => a.Lazy(LazyRelation.Proxy))
NHibernate will assume that the foreign key for this relation is stored on column rootStructure of the table associated to that class.

Can you specifiy the identity column when using the Fluent Nhibernate Table-Per-Subclass strategy?

I am creating a Fluent N hibernate subclass mapping that currently looks something like this:
public class TaskDownloadMap: SubclassMap<TaskDownload>
{
public TaskDownloadMap()
{
Table("TasksDownload");
Map(x => x.ExtraProperty1, "ExtraProperty1")
.Nullable();
Map(x => x.ExtraProperty2, "ExtraProperty2")
.Nullable();
}
}
When I try and save one of these entities I get an exception:
Test.TaskRepositoryTest.DeleteTest:
NHibernate.Exceptions.GenericADOException : could not insert: [TaskManager.Entities.TaskDownload#269][SQL: INSERT INTO TasksDownload (ExtraProperty1, ExtraProperty2, Task_id) VALUES (?, ?, ?)]
----> System.Data.SqlClient.SqlException : Invalid column name 'Task_id'.
It's because the Id column I have set on my subclass's table is named "TaskId". Is there some to override the default naming scheme that nhibernate is trying to use? I don't seem to have the ability to specify the "Id" column in the subclass and I can't find anyone else even talking about it.
The parent mapping looks like this:
public class TaskMap: ClassMap<Task>
{
public TaskMap()
{
Table("Tasks");
Id(x => x.Id, "Id")
.GeneratedBy.Identity();
.
.
.
}
}
Like I said before I am going with the Table-Per-Subclass strategy so these are 2 different tables. I can change the key on my TasksDownload table to be "Task_id" but I'd rather just be able to specify that it's "TaskId" in my mapping so I can keep to my naming convention.
You cannot configure the Subclass id's with the fluent API because it's handled by the built in mapping convetions. But you can write a custom convention (with some extra acceptance if required).
A sample solution:
public class JoinedSubclassIdConvention : IJoinedSubclassConvention,
IJoinedSubclassConventionAcceptance
{
public void Apply(IJoinedSubclassInstance instance)
{
instance.Key.Column(instance.EntityType.BaseType.Name + "Id");
}
public void Accept(IAcceptanceCriteria<IJoinedSubclassInspector> criteria)
{
criteria.Expect(x => x.EntityType == typeof(TaskDownload));
}
}
Then you add your convetion to the configuration:
Fluently.Configure()
//...
.Mappings(m =>
{
m.FluentMappings
//...
.Conventions.Add<JoinedSubclassIdConvention>();
});

nhibernate "cascade="all-delete-orphan" error

i have 3 tables in my database:
Projects (id, name)
Tags (id, name)
ProjectsTagss (id, projectId, tagid)
As you can see the ProjectsTags table is a bridge table
here is my fluent nhibernate mapping
ProjectMap.cs:
Map(x => x.Name).Not.Nullable();
HasMany(x => x.ProjectsTags).AsBag().Inverse()
.Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);
ProjectsTagsMap.cs:
References(x => x.Project).Not.Nullable();
References(x => x.Tag).Not.Nullable();
TagMap.cs:
Map(x => x.Name).Not.Nullable();
As you can see, i historically didn't have the Tag table linked to anything else. I now need to generate a report to show Tag and how often that tag is used so i need to join from Tag to ProjectsTag. i tried adding this line into the tagsmap:
HasMany(x => x.ProjectsTags).AsBag().Inverse()
.Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);
but when i go to update the name on a tag object and commit, i get this error:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance
can anyone see anything wrong with what i added that would be causing this nhibernate exception when i simply update the Tag table. Again my goal is to be able to do something like:
Tag.ProjectTags.Count();
Here is some additional code as requested:
my Tag Class:
public class Tag
{
public virtual IList<ProjectTag> ProjectTags { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
Somewhere in your code, you should have dereferenced the original collection on your Project domain. I suspect that your code goes like this:
var project = Session.Get<Project>();
project.ProjectsTags = new List<ProjectsTags> { someProjectsTagsInstance };
Session.Save(project);
If this is the case, you should do this instead:
var project = Session.Get<Project>();
project.ProjectsTags.Clear();
project.ProjectsTags.Add(someProjectsTagsInstance);
Session.Save(project);
While a collection is not modified, NH can still think that it is. Something like this could be caused by a ghost update. From NHibernate 3.0 Cookbook, Jason Dentler (page 184): "As part of automatic dirty checking, NHibernate compares the original state of an entity to
its current state. An otherwise unchanged entity may be updated unnecessarily because a
type conversion caused this comparison to fail".
Ghost update of collection can be caused by code that looks like this:
public class Tag
{
private IList<ProjectTag> projectsTags;
public virtual IEnumerable<ProjectTag> ProjectsTags
{
get
{
return new ReadOnlyCollection<ProjectTag>(projectsTags);
}
set
{
projectsTags = (IList<ProjectTag>)value;
}
}
}
ProjectsTags property returns the collection in readonly wrapper, so client code cannot add or remove elements to/from the collection.
The error will appear even when name of a tag is not changed:
private void GhostTagUpdate(int id)
{
using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var tag = session.Get<Tag>(id);
transaction.Commit();
}
}
}
ProjectsTags collection should be mapped with CamelCaseField access strategy to avoid ghost updated:
HasMany(x => x.ProjectsTags)
.Access.CamelCaseField()
.AsBag().Inverse().Cascade.AllDeleteOrphan().Fetch.Select().BatchSize(80);
Anyway...
Your association seems to be diabolically complex. If ProjectsTags table should contains only id of tag and id of project, then it would be simpler to use FNH many-to-many bidirectional mapping:
public class Tag2Map : ClassMap<Tag2>
{
public Tag2Map()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Projects)
.AsBag()
.Cascade.None()
.Table("ProjectsTags")
.ParentKeyColumn("TagId")
.ChildKeyColumn("ProjectId");
}
}
public class Project2Map : ClassMap<Project2>
{
public Project2Map()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Tags)
.AsBag()
.Cascade.None()
.Inverse()
.Table("ProjectsTags")
.ParentKeyColumn("ProjectId")
.ChildKeyColumn("TagId");
}
}
Now there is no need for ProjectTag entity in the model. The count of how many times is given tag used can be retrieved in two ways:
Direct way: tag.Projects.Count() - but it retrieves all projects from database.
Query way:
var tag = session.Get<Tag2>(tagId);
var count = session.Query<Project2>().Where(x => x.Tags.Contains(tag)).Count();

Fluent NHibernate - Mapping a dictionary of component/value type objects as a HasMany

I have a class, Item that has many Rates. They are keyed by an enum, RateType.
public class Item
{
int Id {get;set;}
IDictionary<RateType, Rate> Rates {get;set;}
// some other stuff
}
public class Rate
{
RateType Type {get;set;}
decimal Amount {get;set;}
decimal Quantity {get;set;}
}
I am overriding my mapping thusly:
public void Override(FluentNHibernate.Automapping.AutoMapping<Item> mapping)
{
mapping.HasMany(x => x.Rates)
.AsMap(x => x.Type)
.KeyColumns.Add("Item_Id")
.Table("InvoiceItem_Rates")
.Component(x => x.Map(r => r.Amount))
.Component(x => x.Map(r => r.Quantity))
.Cascade.AllDeleteOrphan()
.Access.Property();
}
This has two problems with it.
1) When I fetch an item, the Type is placed as the key of the Dictionary without problems. However, it is not assigned to the Type property within the Rate.
2) I'm expecting three columns in the table InvoiceItem_Rates (Item_Id, Type, Quantity, and Amount. However, Amount is suspiciously absent.
Why are these things happening? What am I doing wrong?
This isn't perfect in my opinion as the enum key value is actually being stored as an integer instead of a string, but probably isn't an issue.
The key here is that you can't have multiple calls to Component as it's going to overwrite your previous Component call with whatever the last one is.
The correct way to call Component() is as below:
Id(x => x.Id);
HasMany(x => x.Rates)
.AsMap(x => x.Type)
.KeyColumn("Item_Id")
.Table("InvoiceItem_Rates")
.Component(x =>
{
x.Map(r => r.Amount);
x.Map(r => r.Quantity);
})
.Cascade.AllDeleteOrphan();

Categories

Resources