I have the following data structure :
+---------+
|Resume |
+---------+
|Id (PK) |
|IsActive |
|... |
|.. |
|. |
+---------+
+--------------------+
|Resume_Translation |
+--------------------+
|ResumeId (PK, FK) |
|Language (PK) |
|Title |
|Description |
|... |
|.. |
|. |
+--------------------+
So I could have such a data with two joined tables :
+----------------------------------------------------------+
|Id | IsActive | ResumeId | Language | Title | Description |
+----------------------------------------------------------+
|1 | true | 1 | 'fr' | 'One' | 'One desc' |
|1 | true | 1 | 'pl' | 'Raz' | 'Raz Opis' |
|2 | true | 2 | 'fr' | 'B' | 'bla bla' |
|3 | true | 3 | 'fr' | 'C' | 'C bla bla' |
+----------------------------------------------------------+
From my domain point of view I care only about Resume entity. I don't want to have Resume entity with its collection of Resume_Translations because I would only have one Resume entity with a current translation.
public class Resume
{
public virtual int Id{ get; protected internal set; }
public virtual string Language { get; protected internal set; }
public virtual string Title { get; protected internal set; }
public virtual string Description { get; protected internal set; }
public virtual bool IsActive { get; protected internal set; }
}
My current mapping with Fluent NHibernate is as follows :
public class ResumeMap : ClassMap<Resume>
{
public ResumeMap()
{
Table("Resume");
Id(x => x.Id);
Map(x => x.IsActive);
// other properties
Join("Resume_Translation", m =>
{
m.Fetch.Join();
m.Map(x => x.Language).Length(5);
m.Map(x => x.Title).Length(100);
m.Map(x => x.Description).Length(200);
});
}
}
I can get what I want from the repository without problem just passing in the WHERE predicate the Id of Resume and the Language I want to.
However I have some problems with Inserting and Updating the values.
My question is: How I would define a mapping that NHibernate Inserts a new record only in Resume_Translation table instead of Updating the record for the current entity ?
So what I want to achieve is if I have in the database the following record :
|2 | true | 2 | 'fr' | 'B' | 'bla bla' |
Join is good for one to one relationship between tables so if I get this into my entity and I change the language and translation, nhibernate is performing an update and I can understand it. If I try to add a new entity with the same Id by different language and translation, nhibernate yields an error that a key already exists and I understand it also.
So, certainly I'm going down the wrong path, but If some one could point me to the correct solution on how I could achieve a mapping that I want I would greatly appreciate.
Another question, how do you deal with a entities and theirs translations from the business point of view ?
Thanks, in advance for your help.
Thomas
Stefan is on the right track. I've tweaked his suggestion to have a bi-directional association which would make updating a lot easier. One catch with this approach is that you need to manually assign the Resume property of the ResumeTranslation instance when inserting so that NHibernate will properly assign the Resume table key to the ResumeTranslation row. So, given the associations you are mapping, this is how it would look in Fluent NH:
public class ResumeTranslation
{
public virtual string Title { get; protected internal set; }
public virtual string Description { get; protected internal set; }
//Needed for bi-directional association:
public virtual Resume Resume { get; set; }
}
public class ResumeTranslationMap : ClassMap<ResumeTranslation>
{
public ResumeTranslationMap()
{
Table("ResumeTranslation");
CompositeId()
.KeyReference(kp => kp.Resume, "ResumeId")
.KeyProperty(kp => kp.Language, "Language");
Map(x => x.Title);
Map(x => x.Description);
}
}
public class ResumeMap : ClassMap<Resume>
{
public ResumeMap()
{
Table("Resume");
Id(x => x.Id);
Map(x => x.IsActive);
// other properties
HasMany(c => c.Translations)
.Inverse()
.KeyColumn("id") //May not be required but here for reference
.Cascade.All();
}
}
Seems like a one to many relationship to me. I would personally have a collection of ResumeTranslation objects within my Resume object. I would then map this as a standard one to many.
You could then add another property ActiveResumeTranslation to your Resume entity that is representative of your current translation.
What about using a dictionary, using the language as a key?
public class ResumeTranslation
{
public virtual string Title { get; protected internal set; }
public virtual string Description { get; protected internal set; }
}
public class Resume
{
public virtual int Id{ get; protected internal set; }
// language is the key to the translation
// you may even want to hide the dictionary from the public interface of
// this class and only provide access to a "current" language.
public virtual IDictionary<string, ResumeTranslation> Translations { get; private set; }
public virtual bool IsActive { get; protected internal set; }
}
And map it accordingly as a map with a composite-element (sorry, I'm not using fluent, so don't ask me how it would look like). It would exactly match your database model.
Related
I have entities that look like this:
class Parent1
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Child> Children { get; set; }
}
class Parent2
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Child> Children { get; set; }
}
class Child
{
public int Id { get; set; }
public string Name { get; set; }
public string ParentType { get; set; }
public int ParentId { get; set; }
}
basically in my case I have a Child table that contains the reference to different type of parents, but only one at a time. I am unable to understand how to create a ModelBuilder mapping to address this.
when I use entity.HasMany(d => d.Children) I don't see a way to map it with ParentType = "Parent1" and ParentId = d.Id
is it even possible in the EFCore 2.X
Update 1:
I have a constraint that I cannot change my table design that looks like this on the child_table
------------------------------------------------
| Id | Name | Parent_Type | Parent_Id |
------------------------------------------------
| 1 | C1 | Parent1 | 1 |
| 2 | C2 | Parent2 | 1 |
| 2 | C3 | Parent2 | 1 |
| 2 | C4 | Parent1 | 1 |
| 2 | C5 | Parent2 | 1 |
------------------------------------------------
You have to add both parent entities to the child entity and make the foreign-keys nullable.
Adjust your child-model to look like this
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public Parent1 Parent1 { get; set; }
public int? ParentId1 { get; set; }
public Parent2 Parent2 { get; set; }
public int? ParentId2 { get; set; }
}
In you DbContext in the OnModelCreating(ModelBuilder modelBuilder) method, do the following
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent1>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent1)
.HasForeignKey(c => c.ParentId1);
modelBuilder.Entity<Parent2>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent2)
.HasForeignKey(c => c.ParentId2);
modelBuilder.Entity<Child>();
// more configuration
}
Now you can either have Parent1 or Parent2 or even both. In general your model doesn't make much sense like this but it would work. Maybe you need to rethink the model design you are trying to use.
You can find more samples on how to work with relationships in EF here.
it's hard for me to explain, so I'll start with an example.
I'm beggining to implement an object changing history in my project.
I want to track date of the change, the changer, the object changed, etc. and persist that info in my DB.
I designed a table similar to this (fits my needs the most):
Table CHANGES
change_id (pk) | change_date | changer_id (fk) | object_id | table_name | column_name
with an exemplary rows
120 | 2013-11-20 | 55 | 88 | "invoices" | "number"
121 | 2013-11-25 | 53 | 99 | "employees" | "name"
Now, i would like to have this kind of entity objects:
class Employee
{
public virtual string Name {get; set;}
public virtual IList<Changes> ChangesList {get; set;}
}
class Invoice
{
public virtual string Number {get; set;}
public virtual IList<Changes> ChangesList {get; set;}
}
The ChangesList from both Invoice and Employee should come from the same table in DB of course. Is it possible and how to achieve this by mapping? Are there any alternatives/minor changes to make this possible?
Before you invent your own take a look at NHibernate.Envers which handles auditing.
If it does not fit your needs it is possible to map it like
public ChangeMap
{
Id(x => x.Id);
Map(x => x.Date, "change_date");
References(x => x.Changer, "changer_id");
...
Map(x => x.TableName, "table_name");
}
// in EmployeeMap
HasMany(x => x.ChangesList).Where("table_name = 'employees'");
// in InvoiceMap
HasMany(x => x.ChangesList).Where("table_name = 'invoices'");
I'm trying to do a 1:1 relationship in FN but its being a bit of a pain.
I looked at http://avinashsing.sunkur.com/2011/09/29/how-to-do-a-one-to-one-mapping-in-fluent-nhibernate/ which seemed to confirm this should work but hey ho, it keeps trying to insert records into the child tables before the parent which means the child-inserts dont contain the CustomerId which is required as its a foreign key constraint.
Tables
+-----------------------+ +----------------------+
| tblCustomer | | tblCustomerPhoto |
|-----------------------| |----------------------|
| | 1:1 | |
| CustomerID (PK) |+---->| CustomerID (FK) |
| OtherJunk... | | Photo (Image) |
| | | |
| | | |
+-----------------------+ +----------------------+
Models
public class Customer
{
public virtual int CustomerID { get; private set; }
/* public virtual Other stuff */
}
public class CustomerPhoto
{
public virtual int CustomerID { get;set;}
public virtual Byte[] Photograph { get; set; }
}
Maps
public class CustomerPhotoMap : ClassMap<CustomerPhoto>
{
public CustomerPhotoMap()
{
Id(x => x.CustomerID)
.Column("CustomerID")
.GeneratedBy.Assigned();
Map(x => x.Photograph)
.CustomSqlType("Image")
.CustomType<Byte[]>()
.Nullable();
}
}
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.CustomerID)
.GeneratedBy.Identity()
.Column("CustomerID");
HasOne<CustomerPhoto>(x => x.CustomerPhoto)
.LazyLoad()
.ForeignKey("CustomerID");
}
}
Test
class CustomerMappingFixture : IntegrationTestBase
{
[Test]
public void CanMapCustomer()
{
new PersistenceSpecification<Customer>(Session)
.CheckReference(x => x.CustomerPhoto, new CustomerPhoto(){ Photograph = Arrange.GetBitmapData(ImageFormat.Jpeg) })
.VerifyTheMappings();
}
}
Now the foreign key column on the CustomerPhoto was set to not-null and I was repeating this in the notation on the CustomerPhotoMapping.
On the basis of ( https://stackoverflow.com/a/2286491/529120 ) I changed that to nullable and removed the notation from the mapping.
Regardless of which, NHibernate returns
System.NullReferenceException : Object reference not set to an instance of an object.
and appears to be trying to insert a CustomerPhoto record first, passing zero as the CustomerId; then creating the Customer record, then trying to select the customer and photo using a left outer join. Which obviously wont work as at no point has it attempted to update the ID in the photo table.
Few things I noticed
Using CheckReference to verify this mapping is probably incorrect. I'm pretty sure this is only for many-to-one relationships. Therefore it makes sense that it's trying to insert the CustomerPhoto before the Customer. I would write a test using straight up NH sessions here. I found it to be more trouble than it's worth to use PersistenceSpecification for many of my mappings that were non-trivial.
The one to one mappings looked like they were a bit off (proposed solution below)
When mapping an image column to a byte array I don't think there is a need to declare a custom type. The mapping below has worked fine for me. I think the other stuff you have on that property mapping is un-needed.
I have a mapping almost identical to yours and this is what I use:
public class Customer
{
public virtual int CustomerID { get; private set; }
public virtual CustomerPhoto CustomerPhoto { get; set; }
/* public virtual Other crap */
}
public class CustomerPhoto
{
public virtual Customer Customer { get; set; }
public virtual Byte[] Photograph { get; set; }
}
public class CustomerPhotoMap : ClassMap<CustomerPhoto>
{
public CustomerPhotoMap()
{
Id(x => x.Id)
.Column("CustomerID")
.GeneratedBy.Foreign("Customer");
Map(x => x.Photograph).Length(Int32.MaxValue);
}
}
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.CustomerID).GeneratedBy.Identity()
.Column("CustomerID");
HasOne(x => x.CustomerPhoto)
.Cascade.All();
}
}
Working on a legacy application at the moment and we are replacing the data access with NHibernate, since the old data access didn't do eager/lazy loading, parts of the system were doing 2000+ queries to create relationships...
Everything is fine except I have absolutely no idea how to map one scenario.
We have a 'Shift' and a 'MultiResourceShift'
public class Shift
{
public int Id { get; protected set; }
public string EmployeeName { get; set; }
public IEnumerable Breaks { get; set; }
...
}
Multi Resource Shift on the otherhand, is effectively a Shift, that can have multiple employees assigned to it.
public MultiResourceShift : Shift
{
public IEnumerable Employees { get; set; }
public bool IsMultiResource { get; set; }
}
The database structure is mapped as a one-to-many like:
+--------------------+
| Shift |
+--------------------+
| ShiftId |
| EmployeeName |
| IsMultiResource |
| ... |
+--------------------+
+--------------------+
| MultiResourceShift |
+--------------------+
| MultiResourceId |
| ShiftId |
| EmployeeId |
| ... |
+--------------------+
Ideally this needs to be queried where the result returns a collection of Shifts, or MultiResourceShifts.
At the moment this is achieved by iterating over the reader, and if it's a Shift it's Mapped and added to the collection, if it's a Multi Resource, an instance of MultiResourceShift is created and populdated from the shift data, and the Employees are loaded, and it's then added to the collection.
Anyone know if this is possible, how I would map it and query it.
http://nhforge.org/blogs/nhibernate/archive/2011/02/16/get-load-polymorphism-in-nhibernate-3.aspx
This blog post on the NH blog is exactly what I was after and solved the problem. We changed our model slightly to create the discriminator.
I'm having some issues mapping a complex many to many relationship in fluentnhibernate. I have a legacy db which looks something like this:
Foos: | Id | Foo |
FooBars: | FooId | BarId |
Bars: | Id | Bar | CultureId |
which I am trying to map to the following object model:
class Foo
{
property virtual int Id { get; set; }
property virtual string Foo { get; set; }
property virtual IList<Bar> Bars { get; set; }
}
class Bar
{
property virtual int Id { get; set; }
property virtual int CultureId { get; set; }
}
with the mappings:
public class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Table("foos");
Id(v => v.Id);
Map(v => v.Foo);
HasManyToMany(v => v.Bars)
.Table("FooBars")
.ParentKeyColumn("FooId")
.ChildKeyColumn("BarId")
.Cascade.All();
}
}
public class BarMapping : ClassMap<Bar>
{
public BarMapping()
{
Table("bars");
Id(v => v.Id);
Map(v => v.Bar);
Map(v => v.CultureId);
}
}
The problem is I have multiple Bar's with the same Id for different CultureIds
e.g.
I would have a table that looks like:
Id|Bar|CultureId
1, Hello, 1
1, Bonjour, 2
1, Gutentag, 3
At the moment, the Bars property for the above table will return 3 elements but the Bar property on it will return "Hello" for all three elements (presumably because they all have the same identity). So my question is, how can I either stop this happening or can anyone suggest a way of filtering rows that do not have the correct culture id (note, this is dynamic & based on the current culture)?
You can't create dynamic where clauses in your mappings. You're going to need to query this collection instead of accessing it via the parent, using a Criteria or HQL query. You could read up on filters, but they still require a query.