Sorry for a lengthy question. But it is worth giving all the details so please bear with me through to the end.
I'm working against a legacy database over which I do not have much control. I want to be able to map a class to multiple database tables. Here is how my tables look
Lookup
+--------+--------------+------------+
| Column | DataType | Attributes |
+--------+--------------+------------+
| Id | INT | PK |
| Code | NVARCHAR(50) | |
+--------+--------------+------------+
Culture
+--------------+--------------+------------+
| Column | DataType | Attributes |
+--------------+--------------+------------+
| Id | INT | PK |
| Culture_Code | NVARCHAR(10) | |
+--------------+--------------+------------+
Lookup_t9n
+----------------+---------------+---------------------+
| Column | DataType | Attributes |
+----------------+---------------+---------------------+
| Id | INT | PK |
| Culture_Id | INT | FK to Culture table |
| Localised_Text | NVARCHAR(MAX) | |
+----------------+---------------+---------------------+
As you can see, I have a lookup table where all lookups are stored. The display text for a lookup is localized and stored in a separate table. This table has a foreign key to culture table to indicate the culture for which the localized text exists.
My class looks like this
public class Lookup {
public virtual int Id {get; set;}
public virtual string Code {get; set;}
public virtual string DisplayText {get; set;}
}
And my FNH mapping class looks like this
public class LookupMappings : ClassMap<Lookup> {
public LookupMappings()
{
Table("Lookup");
Id(x => x.Id).Column("Id");
Map(x => x.Code).Column("Code");
Join("Lookup_t9n", join => {
join.Map(x => x.DisplayText).Column("Localised_Text"); //Note this place, my problem is here
})
}
}
In the above mapping, in Join part I want to provide some where clause like WHERE Lookup_t9n.Culture_Id = Culture.Culture_Id AND Culture.Culture_Code = System.Threading.Thread.CurrentUICulture.CultureCode.
I know this is not a valid SQL but conveys the intent I hope. Has anyone have any experience of doing such a thing.
I can add a mapping layer where I can have classes that map one-to-one with database tables and then write plain c# to map those classes back to my Lookup class. I have rather done that as an interim solution. I was wondering if I can remove that mapping layer with some smart NH use.
I do not have simple answer, like CallThis(). I would like to give you suggestion, based on how we are using similar stuff. The solution is base on the standard mapping, hidding its complexity in C# Entities. It is just a draft of the solution so I'll skip the middle Culture table, and will expect that in Lookup_t9n we do store just a culture name (en, cs...)
Let's have this class
public class Lookup {
public virtual int Id {get; set;}
public virtual string Code {get; set;}
// for simplicity skipping null checks
public virtual DisplayText { get { return Localizations.First().LocalizedText; } }
public virtual IList<Localization> Localizations {get; set;}
}
public class Localization { // mapped to Lookup_t9n
public virtual string CultureName {get; set;}
public virtual string LocalizedText {get; set;}
}
Having this, we can map the collection of Localizations as HasMany. It could even be mapped as a component (see example of component mapping)
Now, what we do need is to introduce a filter. Example with Fluent. The essential documentation: 18.1. NHibernate filters.
Simplified mapping
filter:
public class CulturFilter : FilterDefinition
{
public CulturFilter()
{
WithName("CulturFilter")
.AddParameter("culture",NHibernate.NHibernateUtil.String);
}
collection:
HasMany(x => x.Localization)
.KeyColumn("Id")
...
.ApplyFilter<CulturFilter>("CultureName = :culture"))
.Cascade.AllDeleteOrphan();
Finally, we have to introduce some AOP filter, IInterceptor... which will be triggered each time (needed) and adjust the ISession
session
.EnableFilter("CulturFilter")
.SetParameter("culture"
,System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
And now we have Localized string based on current culture, while using standard mapping of localized values as a collection.
Related
I have a Note class which has a relationship through Client class, and therefore has a property
public virtual Client Client {get; set;}
but how can I add a map to the client_id column, for example having
public virtual int? ClientId {get; set;}
You don't.
If you need to get the value of the FK, you can just do this:
var clientId = note.Client == null? (int?)null : note.Client.Id;
Accessing the id will not cause loading of the Client proxy.
If you need to set the value (and you have an id):
note.Client = session.Load<Client>(clientId);
In the mapping class you would have:
Map(x => x.ClientId);
Using FluentNhiberante is there a way to map the following:
Parent Table (Employee)
EmployeeId INT Primary Key
FirstName
LastName
EmployeeTypeId
Lookup Table (EmployeeType)
EmployeeTypeId INT Primary Key
EmployeeTypeDescription
My class is defined as:
public class Employee
{
int EmployeeId {get; set;}
...
string EmployeeTypeDescription {get; set;}
}
Is there a way via the FluentNhibernate mapping to populate the EmployeeTypeDescription property in the Employee object from the EmployeeTypeDescription table by looking up using the EmployeeTypeId column in Employee?
I'm pretty sure the normal and proper way to do this is by using References in the mapping file and then by adding a EmployeeType property to the Employee class and accessing the description using Employee.EmployeeType.EmployeeTypeDescription. I'm unable to change the code to do that at this time so am wondering how to just set the EmployeeTypeDescription property for now.
it should be possible tweaking the examplecode below:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
...
Join("EmployeeType", join =>
{
join.KeyColumn("EmployeeTypeId");
join.Map(k => k.TherapieOK, "somecolumn"));
}
...
}
}
You can map the class to a view.
You're correct as far as the normal way to do this.
Hoping you can help me with a mapping problem I have. At the moment I have a view which returns something such as:
Name | Title | Price | Date | Format | FormatPriority |
Example data may be:
Bob | Credits | 340 | 01/01/2010 | BAR | 10 |
Bob | Credits | 340 | 01/01/2010 | FOO | 20 |
Bob | Credits | 340 | 01/01/2010 | WOO | 40 |
What I want is a domain model which looks like this:
string Name;
string Title;
int Price;
DateTime Date;
IEnumerable Formats;
Format class would then have:
string Type
int Priority
At the moment we are using the ClassMap approach within Fluent NHibernate (not auto config). How would we map this? The Component doesn't seem to support a collection and this isn't a HasMany relationship as it's coming back as part of the same query.
Any ideas??
Thanks
Ben
Disclaimer: This is such a huge hack, it pains me to post it.
This is based on the schema you've provided, so it might need to be modified to accomodate a different design. There probably is a much better way to do this, but hopefully this should get you going again at least.
The issue is you have a bit of a mismatch in your model and query. Your query returns multiple rows which you intend to be for a single entity with multiple components, but NHibernate is geared to interpret that as multiple entities each with a single component.
NHibernate supports collections of components, but only when they're stored in a separate table/view. These components are joined via a foreign key back to the entity table. If you can change your design to support this, please do so!
If not, the only option I could think of is a self-join on your view. It won't produce the most optimised query, but it should do the trick.
You didn't mention what your entity was called, so I've gone with Transaction.
public class Transaction
{
public virtual string Name { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
public virtual DateTime Date { get; set; }
public virtual ISet<Format> Formats { get; set; }
}
public class Format
{
public virtual string Type { get; set; }
public virtual int Priority { get; set; }
// OVERRIDE EQUALITY MEMBERS!
}
The mapping I've used is:
public class TransactionMap : ClassMap<Transaction>
{
public TransactionMap()
{
Table("vwTransactions");
Id(x => x.Name);
Map(x => x.Title);
Map(x => x.Price);
Map(x => x.Date);
HasMany(x => x.Formats)
.Table("vwTransactions")
.KeyColumn("Name")
.Component(c =>
{
c.Map(x => x.Type, "Format");
c.Map(x => x.Priority, "FormatPriority");
})
.Fetch.Join();
}
}
So you can see the mapping is pointed at the vwTransactions view. You didn't specify an id in your schema, so I've used Name as a identity (this is important). Skip down to the HasMany now, you can see that also points at vwTransactions; NHibernate will see this and do a self-join on the view. Then the key column is set to Name, the same as the entity Id; this way NHibernate will use that to resolve the references between the component and the entity, rather than trying to use an integer foreign key. The Fetch.Join will force NH to eagerly fetch this relationship, so at least we save a bit there. Last thing of note, the Formats property is an ISet, if you don't do this you'll end up with duplicate components.
If you now create a criteria (or hql) query for Transaction, you'll get back your entities with their components; however, you'll get duplicates due to the multiple rows being brought back per entity. This is fairly common, and easily resolved using the DistinctRootEntity transformer.
var transactions = session.CreateCriteria(typeof(Transaction))
.SetResultTransformer(Transformers.DistinctRootEntity)
.List<Transaction>();
That should be it, you'll now end up with just one entity (based on your dataset) with 3 components.
Nasty, I know.
Can someone help me with the best way to map the following situation in fluent nHibernate? The Address class is used in both Client and Company. How can I store it most efficient in SQL? And what should the mapping look like? I've thought about multiple options, but I'm not experienced enough with nHibernate for these situations:
use 1 address entity and 1 table and use a denominator column to distinguish between address for client and address for company -> how to implement this in nHibernate?
use 1 address entity and 2 tables (ClientAddresses and CompanyAddresses) --> but I can only define 1 table in the mapping of the class Address
use 2 address entities and 2 tables --> not so elegant
I've just stumbled upon this problem when I started implementing the company class and realized it also needed multiple addresses. Up till now I had a Address and Client class and had a one-to-many mapping between them. In the database the Address had an extra column called ClientId. But with introducing the Company class I'm stuck...
Any help would greatly be appreciated.
I'm currently working in the sharparch 1.5 framework, which uses automapping and my mapping files are like this:
public class AddressMap : IAutoMappingOverride<Address>
{
public void Override(AutoMapping<Address> mapping)
{
mapping.Table("addresses");
mapping.Id(x => x.Id, "AddressGuid")
.UnsavedValue(Guid.Empty)
.GeneratedBy.GuidComb();
mapping.References(x => x.Client, "ClientGuid");
}
}
Below some more code the illustrate the problem:
Address
public class Address
{
public virtual string StreetLine1 { get; set; }
public virtual string StreetLine2 { get; set; }
public virtual string PostalCode { get; set; }
public virtual string City { get; set; }
public virtual string Country { get; set; }
}
which has the following table:
tablename = addresses
fields= AddressGuid, StreetLine1, StreetLine2, PostalCode, City, Country
Client
public class Client
{
public IList<Address> Addresses {get;set;}
}
Company
public class Company
{
public IList<Address> Addresses {get;set;}
}
It looks like you can implement #1 with nHibernate's <any> mapping. Note that in this case you cannot specify foreign-key constraints.
an example of <any>
Fluent nHibernate syntax
You could model the relationships as a many-to-many: many companies to many addresses, and many clients to many addresses.
In both your Company and Client mappings:
mapping.HasManyToMany(x => x.Addresses);
This will create two additional tables: one mapping between companies and addresses, another mapping between clients and addresses.
In theory this could allow sharing situations (some companies and clients all sharing have the same address row) which you probably don't want, but as long as your application logic doesn't allow that to happen, you'll be fine and you won't have to do anything tricky with nhibernate.
I have a scenario in NHibernate where I have a one-to-many relationship between entities Employee and EmployeeStatus.
Employee has properties eg: ID, Name and an IList of EmployeeStatus, whilst EmployeeStatus, for the purposes of this question, just has it's own ID and some free text.
I don't need to hold a reference to Employee from EmployeeStatus, the management of status' will be done purely through the Employee entity - adding to the IList property. IE: I want to quite simply be able to do the following;
Employee e = new Employee();
e.Name = "Tony";
e.StatusList.Add( new EmployeeStatus("Status A") );
e.StatusList.Add( new EmployeeStatus("Status B") );
session.Save(e);
I've tried various methods, including creating a one way one-to-many mapping where inverse is false, cascade set to all-delete-orphan, which all looks like it should work, but it generates an exception about being unable to set the EmployeeId in EmployeeStatus. I'm led to believe that this is because NHibernate wants to do an insert with EmployeeId as NULL and then update it to the ID of the parent.
I guess I'm missing something here, so quite simply - can anyone tell me what my mapping file should look like to achieve the above?
Thanks in advance
Tony
-- edit: Heres a rough idea of the classes as requested --
public class Employee
{
private IList<EmployeeStatus> _statusList;
public Employee()
{
_statusList = new List<EmployeeStatus>();
}
public virtual int Id{ get; set; }
public virtual string Name{ get; set; }
public virtual IList<EmployeeStatus> StatusList
{
get
{
return _statusList;
}
}
}
public class EmployeeStatus
{
public virtual int Id{ get; set; }
public virtual string StatusText{ get; set; }
public EmployeeStatus()
{
}
public EmployeeStatus(string statusText)
{
StatusText = statusText;
}
}
The scenario you've described is just a basic one-to-many mapping. Here is the Fluent NHibernate mapping for this:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
WithTable("Employee");
HasMany(employee => employee.StatusList)
.Cascade.All();
}
}
You do not need to maintain a reference from EmployeeStatus back to Employee to achieve this.
Turns out that what I want to do isn't possible - you have to have a bi-directional association, and must set the child's parent reference. Not a massive problem I suppose, but didn't want to hold references in the child that I don't need within my code directly.
I may not of explained clearly, but an employee status cannot be linked to more than one employee. It's definitely 1 (employee) to many (status')
In the physical database, the status entity has an employeeID field, which isn't in the domain - IE: I hold no reference back to employee from the status entity, but the physical field should be inferred from the owner of the collection - In fact, it does do this if I set the EmployeeID field in the status table to nullable - it actually executes 2 SQL statements - an insert and then an update, the EmployeeID being set in the update.
Thanks,
Tony
Can you post the code for the classes?
Are you trying to keep a history of statuses for an Employee?
-- Edit --
Looks like you are going to need many-to-many, since the child in the relationship (EmployeeStatus) has no reference back to the parent (Employee).
-- Edit 2 --
If you want the insert to be done as 1 call to the DB, you are going to need to add an Employee property to the EmployeeStatus class, and set the Inverse=true. And I'm pretty sure that you are going to need to add some logic which sets the bi-directional relationship in the objects. I.E.
public void AddStatus(EmployeeStatus status)
{
this.StatusList.Add(status);
status.Employee = this;
}