Fluent NHibernate - Detached table mapping - c#

Is there a way I can setup mapping for a table that doesn't have a direct reference to another table? It actually gets it's reference from another table that I do have a direct reference from.
This is what I have so far, but I'm not sure how to map the "LookupValue" in my MetaData model. It would need to map to MetaData if the [mdd].DefinitionType equals the [mdl].LookupType and the [md].DataValue equals the [mdl].LookupKey.
public class MetaData {
public virtual long TableID { get; set; }
public virtual MetaDataDefinition Definition { get; set; }
public virtual int DefinitionID { get; set; }
public virtual String DataValue { get; set; }
public virtual MetaDataLookup LookupValue { get; set; }
public override bool Equals(object obj) { ... }
public over int GetHashCode() { ... }
}
public class MetaDataDefinition {
public virtual long ID { get; set; }
public virtual string DefinitionName { get; set; }
public virtual string DefinitionType { get; set; }
}
public class MetaDataLookup {
public virtual string Type { get; set; }
public virtual string LookupKey { get; set; }
public virtual string LookupValue { get; set; }
public override bool Equals(object obj) { ... }
public over int GetHashCode() { ... }
}
public class MetaDataMap : ClassMap<MetaData> {
public MetaDataMap() {
Table("PPOMetaData");
CompositeId()
.KeyProperty(x => x.TableID, "TableID")
.KeyProperty(x => x.DefinitionID, "DefinitionID");
References(x => x.Defintion, "DefinitionID").Not.LazyLoad().Cascade.All().Fetch.Join();
Map(x => x.TableID);
Map(x => x.DataValue);
}
}
public class MetaDataDefinitionMap : ClassMap<MetaDataDefinition> {
public MetaDataDefinitionMap() {
Table("MetaDataDefinitions");
Id(x => x.ID);
Map(x => x.DefinitionName);
Map(x => x.Type);
}
}
public class MetaDataLookupMap : ClassMap<MetaDataLookup> {
public MetaDataLookupMap() {
CompositeId()
.KeyProperty(x => x.LookupType)
.KeyProperty(x => x.LookupKey);
Map(x => x.LookupValue);
}
}
Ideally, I want to have it run a query similar to this:
SELECT data.TableID, data.DefinitionID, def.DefinitionName, data.DataValue,lu.LookupValue AS DataValue
FROM dbo.PPOMetadata AS data
INNER JOIN dbo.MetaDataDefinitions AS def ON def.ID = data.DefinitionID
LEFT OUTER JOIN dbo.MetaDataLookup AS lu ON lu.LookupType = def.Type AND lu.LookupKey = data.DataValue
WHERE data.TableID = 1
In terms of update ability, the only thing I would ever create, update or delete would be in the MetaData table. The definitions and Lookup values would never change (at least from this part of the application). Is mapping the "MetaDataLookup" directly to the MetaData model possible? If so, can someone point me in the right direction of what I should be looking at?
Thanks!

I came up with a workaround that seems to be working and might take some of the complexity out. Instead of trying to handle the complex joins in a ClassMap, I built a view in Sql Server that does this for me. In my application, I built a new Model and ClassMap for the view. I haven't implemented any update logic yet, but I think I'll have the update logic work directly on the MetaData model, while the read logic (which needs everything joined together) will use the new MetaDataView model.
I'm still curious if complex joins like this are possible in Fluent NHibernate, but for now this solution seems to be working for me.

Related

C# Fluent NHibernate 0-1 0-many 1-many Mapping with custom rules

I need to configure NHibernate Mappings using Fluent library for some relations of my DB.
I created a Web Api MVC Project on VS 2017 with NHibernate v4.1.1.4000 and FluentNHibernate v2.0.3.
I've those tables:
Typology (Id, Description, ..., ExpressionId)
Expression (Id, Name, Script, Language)
Relations:
1 Typology can reference 0 or 1 Expression
1 Expression can be referenced by 0 or N Typology
I need to configure mappings so that manipulating the objects I can:
Change the Expression referenced on a Typology, and update it so that on the DB the value of ExpressionId column changes.
Delete a reference to an Expression on a Typology, so that the ExpressionId will be NULL and the corresponding Expression remains on the DB, even if it is not referenced by any other Typology.
Create a new Expression object and associate it to an existing (or newly created) Typology so that the new Expression is saved to DB and Typology (eventually created) reference it, like this:
Typology typology = _repositoryTypologies.GetById(1);
Expression newExp = new Expression()
{
Name = "S2",
Script = "public void Test()",
//...
Language = "cs"
};
typology.CompletionScript = newExp;
_repositoryTypologies.Save(typology);
If possible, if I delete an Expression, all the Typologies that reference it, will be updated with ExpressionId = NULL.
I already tried with some mappings but I didn't obtained the desired results.
I've been looking a bit here and there but I don't understand yet what is the best mapping for this case. Someone uses References() even if there's a 0-1 relation, someone else recommend to use HasOne()/HasMany().
These are my actual classes:
public class Typology
{
[Key]
public virtual int Id { get; protected set; }
[Required]
public virtual string Description { get; set; }
//...
public virtual Expression CompletionScript { get; set; }
}
public class TypologyMap : ClassMap<Typology>
{
public TypologyMap()
{
Table("Typologies");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("TypologyId");
Map(x => x.Description).Column("Description").Not.Nullable();
//...
HasOne(x => x.CompletionScript).Cascade.SaveUpdate();
}
}
public class Expression
{
[Key]
public virtual int Id { get; protected set; }
[Required]
public virtual string Name { get; set; }
public virtual string Script { get; set; }
public virtual string Language { get; set; }
}
public class ExpressionMap : ClassMap<Expression>
{
public ExpressionMap()
{
Table("Expressions");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("ExpressionId");
Map(x => x.Name).Column("Name").Not.Nullable();
Map(x => x.Script);
Map(x => x.Language);
}
}
Your Expression will need some kind of collection for its related Topologies. Something like this:
public class Expression
{
[Key]
public virtual int Id { get; protected set; }
[Required]
public virtual string Name { get; set; }
public virtual string Script { get; set; }
public virtual string Language { get; set; }
// At its most basic...
public virtual List<Topology> Topologies { get; set; }
}
Then you can map it:
public class ExpressionMap : ClassMap<Expression>
{
public ExpressionMap()
{
Table("Expressions");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("ExpressionId");
Map(x => x.Name).Column("Name").Not.Nullable();
Map(x => x.Script);
Map(x => x.Language);
HasMany(x => x.Topologies);
}
}
You should be able to use References() on the Expression side of the mapping. Although some might recommend ManyToOne(). I'm not too sure of their nuances.
While this should provide a functional solution, you'll need to toughen it up a bit by considering access to the collection, cascading, etc.

Fluent NHibernate Mapping Help static view referencing with compositeID

Afternoon all. I'm trying to create a mapping for a flight segment database where at the bottom of the mapping tree a FlightSegment references an origin and destination table with a compositeID consisting of a three letter code and a Boolean determining whether the code is or isn't a city.
Below are the relevant simplified class structures:
public class GTIFlightSegment
{
public virtual int ID { get; protected set; }
public virtual GTIOriginAirport Origin { get; set; }
public virtual GTIDestinationAirport Destination { get; set; }
public virtual GTIFlightSegmentGroup Parent { get; set; }
}
public class GTIAirport
{
public virtual string Code { get; set; }
public virtual string Name { get; set; }
public virtual string City { get; set; }
public virtual string CountryCode { get; set; }
public virtual GTIGeoCode GeoCode {get; set; }
public virtual string Terminal { get; set; }
public virtual bool IsCity { get; set; }
public GTIAirport()
{
GeoCode = new GTIGeoCode();
IsCity = false;
}
public override bool Equals(object obj)
{
var other = obj as GTIAirport;
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return this.Code == other.Code && this.IsCity == other.IsCity;
}
public override int GetHashCode()
{
unchecked
{
int hash = GetType().GetHashCode();
hash = (hash * 31) ^ Code.GetHashCode();
hash = (hash * 31) ^ IsCity.GetHashCode();
return hash;
}
}
}
public class GTIOriginAirport : GTIAirport
{
public virtual GTIFlightSegment Parent { get; set; }
public GTIOriginAirport() : base()
{
}
}
public class GTIDestinationAirport : GTIAirport
{
public virtual GTIFlightSegment Parent { get; set; }
public GTIDestinationAirport() : base()
{
}
}
And below are the mappings I've created so far for the objects:
public class GTIFlightSegmentMap : ClassMap<GTIFlightSegment>
{
public GTIFlightSegmentMap()
{
Id(x => x.ID);
References(x => x.Destination).Columns(new string[] { "DestinationCODE", "DestinationIsCity" }).Cascade.All();
References(x => x.Origin).Columns(new string[] { "OriginCODE", "OriginIsCity"}).Cascade.All();
References(x => x.Parent).Not.Nullable();
}
}
public class GTIAirportMap : ClassMap<GTIAirport>
{
public GTIAirportMap()
{
Table("GTIAirport");
ReadOnly();
CompositeId()
.KeyProperty(x => x.Code, "CODE")
.KeyProperty(x => x.IsCity, "isCity");
Map(x => x.Name).Column("Airport");
Map(x => x.City);
Map(x => x.CountryCode);
Component(x => x.GeoCode, m =>
{
m.Map(x => x.Latitude);
m.Map(x => x.Longitude);
});
}
}
public class GTIOriginAirportMap : SubclassMap<GTIOriginAirport>
{
public GTIOriginAirportMap()
{
KeyColumn("CODE");
KeyColumn("isCity");
HasOne(x => x.Parent).PropertyRef(x => x.Origin);
}
}
public class GTIDestinationAirportMap : SubclassMap<GTIDestinationAirport>
{
public GTIDestinationAirportMap()
{
KeyColumn("CODE");
KeyColumn("isCity");
HasOne(x => x.Parent).PropertyRef(x => x.Origin);
}
}
What I'm trying to achieve is that when a FlightSegment is created in the system it will store the DestinationIsCity/DestinationCode and the OriginIsCity/OriginCode in the flight segments table but not try and update the GTIAirport reference table view. But when the objects are retrieved from the database with a query over, the rich information from the GTIAirport reference table will be fetched.
Or possibly have the Code and IsCity in the DepartureAirport and OriginAirport tables respectively with a ID reference back to the flight segment.
No matter what connotations I'm trying I'm hitting a wall of some kind. Basically I've got myself in a bit of a mess. I've flipped the relationships between the Airport and segment, swapping out to only references. Cascaded, not cascaded.
Common Errors being encountered whilst playing with the mappings:
1) The UPDATE statement conflicted with the FOREIGN KEY constraint
2) {"broken column mapping for: Destination.id of: GilesSabreConnection.Profiling.Model.BookingEngineModel.Model.GTIFlightSegment, type component[Code,IsCity] expects 2 columns, but 1 were mapped"}
3) {"Foreign key (FK582A9C81E6C3913B:GTIFlightSegment [Destination_id])) must have same number of columns as the referenced primary key (GTIDestinationAirport [CODE, isCity])"}
In case it's needed the fluent config is:
return Fluently.Configure().Database(MsSqlConfiguration.MsSql2012.ConnectionString(#"Data Source=Dev2;Initial Catalog=Sandbox;Integrated Security=True")).Mappings(m => m.FluentMappings.AddFromAssemblyOf<Profile>()).ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(true, true)).BuildSessionFactory();
Any help to point me in the right direction from the database and fluent gurus would be greatly appreciated.
Many thanks in advance.
Realised that by setting the GTIAirport Map as readonly I was inherently making the Destination and Origin maps as readonly so the mapping could never work no matter how I was configuring the relationships.
So I've split the static reference database of detailed airport info accessed via the View named GTIAiport into two, so the Boolean in the composite key is no longer required. I now have the flight segment mapping simply logging the Airport origin/destination codes within the flight segment table using the Component class in fluent. In the Airport class itself now have a function to independently fetch the rich data from the static reference database table on request using the stored Airport code.
Couldn't figure out a way to do this with fluent. Interested to know if there is a way.
public GTIFlightSegmentMap()
{
Id(x => x.ID);
Component(x => x.Destination, m =>
{
m.Map(y => y.Code).Column("DestinationCode");
});
Component(x => x.Origin, m =>
{
m.Map(y => y.Code).Column("OriginCode");
});
;
References(x => x.Parent).Not.Nullable();
}
}

How to map a related table with no primary key with fluent-NHibernate

Looks a common situation to me: I have two tables:
documents:
dID (pk, int), dName(varchar)
and document_options:
dID (int), oType(int), oValue(varchar)
I would like to have a class Document with a property Options (a List of DocumentOption class)
Since document_options has no PK I cannot use HasMany, and rows from this table don't seem like 'real' entities anyway...
I see a way to generate an auto-number key for document options and map with HasMany, or maybe create a composite ID, but I'd like to know if there is a better option that I don't know about.
In this case, DocumentOptions is a value object, since it has no identity of its own and has no meaning outside of the document it belongs to. So, you would use Component to map the collection properties to the value object.
public class Document : Entity // don't worry about Entity; it's a base type I created that contains the Id property
{
public virtual string Name { get; set; }
public virtual IList<DocumentOptions> Options { get; protected set; }
public Document()
{
Options = new List<DocumentOptions>();
}
}
public class DocumentOptions
{
public virtual int Type { get; set; }
public virtual string Value { get; set; }
}
And the mapping:
public DocumentMap()
{
Table("documents");
Id(c => c.Id)
.Column("dId")
.GeneratedBy.HiLo("10");
Map(c => c.Name)
.Column("dName");
HasMany(c => c.Options)
.Component(c =>
{
c.Map(c2 => c2.Value).Column("oValue");
c.Map(c2 => c2.Type).Column("oType");
})
.Table("document_options")
.KeyColumn("dId")
.Cascade.AllDeleteOrphan();
}
If I understand correctly I had to map options as a list of components:
HasMany(x => x.DocumentOptions)
.Table("document_options")
.KeyColumn("dID")
.Component(c => {
c.Map(x => x.Option, "oID");
c.Map(x => x.Value, "oValue");
})
.Fetch.Subselect(); //This type of join isn't strictly needed, is used for SQL optimization
classes FYI:
public class Options {
public virtual int Option { get; set; }
public virtual int Value { get; set; }
}
public class Document {
public virtual int ID { get; set; }
public virtual String Name { get; set; }
public virtual IList<DocumentOption> DocumentOptions { get; set; }
}

HasMany Mapping but getting one element or get some of

public class Category
{
public virtual int Id { set; get; }
public virtual string Name { set; get; }
public virtual int CategoryOrder { set; get; }
public virtual IEnumerable<News> LatestNews { set; get; }
}
public sealed class CategoryMap :ClassMap<Category>
{
public CategoryMap()
{
LazyLoad();
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.CategoryOrder);
HasMany(x => x.LatestNews);
}
}
IRepository<Category> newsRepo = new NHibernateRepository<Category>();
using(var session = newsRepo.GetSessionFactory().OpenSession())
using(var transaction = session.BeginTransaction())
{
var result = session.Query<Category>().OrderBy(x => x.CategoryOrder);
transaction.Commit();
}
I have this category class Which I want to display a (only one) News per category. Is this correct mapping? or should i change it to Map
When i run this, it gets all the news per category. But i want the latest news per category (only one). I can get the latest news by querying News.DateUpdated.
How should i change the query to get one news per category?
or how do I get some of the News? ie: limit the number of news I can query?
I think you can have your cake and eat it too. You have a list of News items, so just leave that. But add in another property that gets just the News item you want.
public class Category
{
public virtual int Id { set; get; }
public virtual string Name { set; get; }
public virtual int CategoryOrder { set; get; }
public virtual IEnumerable<News> AllNews { set; get; }
public virtual News LatestNews
{
get
{
// probably needs some work
return this.AllNews.OrderByDescending(n => n.SomeDateField).Take(1);
}
}
}
public sealed class CategoryMap :ClassMap<Category>
{
public CategoryMap()
{
LazyLoad();
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.CategoryOrder);
HasMany(x => x.AllNews);
}
}
you can use lazy mode extra to achieve this,
public News GetLatestNews(Category cat)
{
var session = SessionFactory.CurrentSession;
var news = session.CreateFilter(cat.LatestNews , "order by categoryorder").SetFirstResult((page - 1) * pageSize).SetFirstResult(0).SetMaxResults(1).List().FirstOrDefault();
return news;
}
however if you try to access the LatestNews collection directly, this won't be of any use as it will load all the news objects associated with Category from db which would lower the performance.

Getting Data from all tables instead of one with HQL Query that should only get 1 table's data

I have created 3 tables in my database and put data into them. The 3 tables all have foreign keys joining them together. Below are the table classes and there mappings. When I run the query listed at the end I get IList<> of the objects and they have the data from all 3 tables. However, my HQL query is only from the top most table. How can I get back just the results from the top most table?
These are my classes:
public class Technology
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual int SortOrder { get; set; }
public virtual string Abbreviation { get; set; }
public virtual IList<TechnologyDescription> TechnologyDescriptions { get; private set; }
public Technology()
{
TechnologyDescriptions = new List<TechnologyDescription>();
}
public virtual void AddTechnologyDescription(TechnologyDescription technologyDescription)
{
technologyDescription.Technology = this;
TechnologyDescriptions.Add(technologyDescription);
}
}
public class TechnologyDescription
{
public virtual int Id { get; private set; }
public virtual Technology Technology { get; set; }
public virtual string Description { get; set; }
public virtual DescriptionType DescriptionType { get; set; }
}
public class DescriptionType
{
public virtual int Id {get; private set;}
public virtual string Type { get; set; }
}
These are my mapping objects:
public class TechnologyMap : ClassMap<Technology>
{
public TechnologyMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.SortOrder);
Map(x => x.Abbreviation);
HasMany(x => x.TechnologyDescriptions)
.Inverse()
.Cascade.All();
}
}
public class TechnologyDescriptionMap : ClassMap<TechnologyDescription>
{
public TechnologyDescriptionMap()
{
Id(x => x.Id);
References(x => x.Technology);
Map(x => x.Description);
References(x => x.DescriptionType);
}
}
public class DescriptionTypeMap : ClassMap<DescriptionType>
{
public DescriptionTypeMap()
{
Id(x => x.Id);
Map(x => x.Type);
}
}
And this is my HQL code:
IQuery q = session.CreateQuery("from Technology T");
IList technologies = q.List();
I don't know if it is possible using HQL, but using NHibernate's Criteria API, you can do this:
ICriteria criteria = session.CreateCriteria (typeof(Technology));
criteria.SetFetchMode ("TechnologyDescriptions", FetchMode.Lazy);
var list = criteria.List<Technology>();
However, this is probably not really what you want. The TechnologyDescriptions won't be fetched right now, but they will be fetched once you access them (that is: the first time you call the TechnologyDescriptions property).
When working with NHibernate, you shouldn't think in terms of 'data'. Rather, you should think in terms of 'entities'.
When retrieving an entity, you want to retrieve the entity entirly (directly, or in a lazy fashion). It is not possible to retrieve an entity partially, and this is quite obvious;
What should NHibernate do with an entity that you've retrieved partially, when you try to save that entity ?
Something else that pops in my mind:
I suppose you want to retrieve the Technologies, and nothing related because you want to display them in an overview or something like that ?
In such case, you should take a look at 'Transformations'.
You could for instance create an additional class which is called TechnologyView, which looks like this:
public class TechnologyView
{
public int Id
{
get;
private set;
}
public string Name
{
get;
private set;
}
public string Abbreviation
{
get;
private set;
}
private TechnologyView()
{
// Private constructor, required for NH
}
public TechnologyView( int id, string name, string abbreviation )
{
this.Id = id;
this.Name = name;
this.Abbreviation = abbreviation;
}
}
Once you've done this, you must inform NHibernate about the existance of this class.
You do this by Importing the class in an hbm.xml file for instance . (I do not know how to do it using Fluent).
<import class="MyNamespace.TechnologyView" />
After that, you can create a query (using HQL or Criteria) which retrieves TechnologyView instances. NHibernate is smart enough to generate a performant SQL query.
using HQL:
IQuery q = s.CreateQuery ("select new TechnologyView (t.Id, t.Name, t.Abbreviation) from Technology t");
using Criteria:
ICriteria criteria = s.CreateCriteria (typeof(Technology));
criteria.SetResultTransformer (Transformers.AliasToBean (typeof(TechnologyView));
var result = criteria.List<TechnologyView>();
I think what you're looking for is for the TechnologyDescriptions to be lazy loaded. That way the descriptions get loaded from the database only when they are accessed (NHibernate will issue a second db query. Note that this can lead to N+1 selects in some situations and you might prefer the all at once query depending on the usage.)
By NHibernate xml mappings default to lazy loading of collections. In the past it seems that the Fluent NHibernate did not have the same default. You need to add .LazyLoad() to the mapping.
Recently it looks like lazy loading has become the default fluent mapping:
Is the default behavior with Fluent NHibernate to lazy load HasMany<T> collections?

Categories

Resources