I'm trying to leverage NH to map to a data model that is a loose interpretation of the EAV/CR data model.
I have most of it working but am struggling with mapping the Entity.Attributes collection.
Here are the tables in question:
--------------------
| Entities |
--------------------
| EntityId PK |-|
| EntityType | |
-------------------- |
-------------
|
V
--------------------
| EntityAttributes | ------------------ ---------------------------
-------------------- | Attributes | | StringAttributes |
| EntityId PK,FK | ------------------ ---------------------------
| AttributeId FK | -> | AttributeId PK | -> | StringAttributeId PK,FK |
| AttributeValue | | AttributeType | | AttributeName |
-------------------- ------------------ ---------------------------
The AttributeValue column is implemented as an sql_variant column and I've implemented an NHibernate.UserTypes.IUserType for it.
I can create an EntityAttribute entity and persist it directly so that part of the hierarchy is working.
I'm just not sure how to map the EntityAttributes collection to the Entity entity.
Note the EntityAttributes table could (and does) contain multiple rows for a given EntityId/AttributeId combination:
EntityId AttributeId AttributeValue
-------- ----------- --------------
1 1 Blue
1 1 Green
StringAttributes row looks like this for this example:
StringAttributeId AttributeName
----------------- --------------
1 FavoriteColor
How can I effectively map this data model to my Entity domain such that Entity.Attributes("FavoriteColors") returns a collection of favorite colors? Typed as System.String?
here goes
class Entity
{
public virtual int Id { get; set; }
internal protected virtual ICollection<EntityAttribute> AttributesInternal { get; set; }
public IEnumerable<T> Attributes<T>(string attributeName)
{
return AttributesInternal
.Where(x => x.Attribute.Name == attributeName)
.Select(x => x.Value)
.Cast<T>();
}
}
class EntityAttribute
{
public virtual Attribute Attribute { get; set; }
public virtual object Value { get; set; }
}
class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
HasMany(e => e.AttributesInternal)
.Table("EntityAttributes")
.KeyColumn("EntityId")
// EntityAttribute cant be an Entity because there is no real Primary Key
// (EntityId, [AttributeId] is not unique)
.Component(c =>
{
c.References(ea => ea.Attribute, "AttributeId").Not.LazyLoad();
c.Map(ea => ea.Value, "AttributeValue").CustomType<VariantUserType>();
});
}
}
Related
I'm having trouble with table view.
I want my table to look like this one
Name | Test1 | Test2 | Test3
================================
Anna | 70 | 51 | 90
================================
Jack | 56 | 77 | 82
================================
Now I have
Name | Grade | Test |
================================
Anna | 70 | Test1 |
================================
Anna | 51 | Test2 |
================================
Anna | 90 | Test3 |
================================
Jack | 56 | Test1 |
================================
Jack | 77 | Test2 |
================================
Jack | 82 | Test3 |
================================
The number of tests is dynamic.
Is there any way to do this? Couldn't find anything.
Should I create another model? Or it could be solved by editing View?
Model
public class StudentsWork
{
[Key]
public int IdStudentsWork { get; set; }
public int? Grade { get; set; }
public DateTime? Date { get; set; }
public int Student_id { get; set; }
public int Course_id { get; set; }
public int Test_id { get; set; }
[ForeignKey("Student_id")]
public Student StudentId { get; set; }
[ForeignKey("Course_id")]
public Course CourseId { get; set; }
[ForeignKey("Test_id")]
public Test TestId { get; set; }
}
Controller
public async Task<IActionResult> Group(int? id,int id_group)
{
var group = await _context.StudentsWork
.Include(p => p.StudentId)
.ThenInclude(p => p.GroupId)
.Include(p => p.CourseId)
.Include(p => p.TestId)
.Where(c => c.Course_id == id && c.StudentId.Group_id == id_group)
.ToListAsync();
return View(group);
}
Any help would be greatly appreciated.
You can group by StudentId and select a dictionary for each group to get test name and grade :
var query = db.StudentsWork.Include(p => p.StudentId)
.Include(p => p.CourseId)
.Include(p => p.TestId)
.GroupBy(c => c.StudentId)
.Select(g => new
{
StudentName=g.Key.Name,
TestGrade = g.ToDictionary(t => t.TestId.Name, t => t.Grade)
});
var result = query.ToList();
I have two table: Customers and Products
public class Customers
{
public virtual int Id { get; set; }
public virtual string CName { get; set; }
public virtual int Age { get; set; }
public virtual string Address { get; set; }
public virtual int Salary { get; set; }
}
public class Product
{
public virtual int Id { get; set; }
public virtual string PName { get; set; }
public virtual Customers CustomerID { get; set; }
public virtual int Amount { get; set; }
}
whit this Values in DB:
------------Customer Table----------------
| id | CName | Age | Address | Salary |
------------------------------------------
| 1 | Ben | 18 | a | 1000 |
| 2 | Mark | 20 | b | 2000 |
| 3 | Ben | 18 | a | 3000 |
| 4 | Ben | 19 | c | 4000 |
| 5 | Mark | 20 | b | 5000 |
| 6 | Jane | 21 | d | 6000 |
------------Customer Table----------------
| id | PName | CustomerID_id | Amount |
------------------------------------------
| 1 | A | 1 | 5 |
| 2 | B | 2 | 10 |
| 3 | C | 1 | 15 |
| 4 | D | 2 | 20 |
| 5 | E | 2 | 25 |
| 6 | F | 6 | 30 |
| 7 | G | 6 | 40 |
When I run this query in SQL Server Management:
SELECT CName , Amount
FROM [TestNhibernate].[dbo].[Product]
Inner Join [TestNhibernate].[dbo].[Customers]
on [TestNhibernate].[dbo].[Product].[Customerid_id]
= [TestNhibernate].[dbo].[Customers].[id]
SQL result is:
-------------------
| CName | Amount |
-------------------
| Ben | 5 |
| Mark | 10 |
| Ben | 15 |
| Mark | 20 |
| Mark | 25 |
| Jane | 30 |
| Jane | 40 |
And when I run this query
SELECT CName , Sum(salary) as SumSalary, sum(amount) as SumAmount
FROM [TestNhibernate].[dbo].[Product]
Inner Join [TestNhibernate].[dbo].[Customers]
on [TestNhibernate].[dbo].[Product].[Customerid_id]
= [TestNhibernate].[dbo].[Customers].[id]
Group By Cname
results is :
----------------------------------
| CName | SumSalary | SumAmount |
----------------------------------
| Ben | 2000 | 20 |
| Jane | 12000 | 70 |
| Mark | 6000 | 55 |
----------------------------------
How can I express that in NHiberante query?
UPDATE: some attempts
I try this code
session
.QueryOver<Product>()
.JoinQueryOver<Customers>(p => p.CustomerID)
.SelectList(w => w
.Select(x => x.Amount)
.Select(z => z.CustomerID))
.List<object[]>()
this is done but when i write this code
session
.QueryOver<Product>()
.JoinQueryOver<Customers>(p => p.CustomerID)
.SelectList(w => w
.Select(x=>x.Amount)
.Select(z=>z.CustomerID.CName))
.List<object[]>()
doesn't work!
Based on the information in the question, there is some draft of the QueryOver syntax, which should help to understand. Firstly we should create some DTO, representing the result:
public class ProductDTO
{
public virtual string ClientName { get; set; }
public virtual decimal SumSalary { get; set; }
public virtual decimal SumAmount { get; set; }
}
Now, we should have business model with a bit more standard naming:
public class Product
{
...
// instead of this
//public virtual Customers CustomerID { get; set; }
// we should use
public virtual Customers Customers { get; set; }
}
Personally I would rather see Customer than Customers... but still better than CustomersID
Because our mapping here must be <many-to-one representing reference relation, not just <property - representing the value type / integer.
Now the query:
// to have access to client name
Customers client = null;
// that would be result - used for proper columns aliasing
ProductDTO dto = null;
var result = session.QueryOver<Occupation>()
.JoinQueryOver<Customers>(p => p.Customers , () => client)
.SelectList(w => w
// SUM
.SelectSum(x => x.Amount)
.WithAlias(() => dto.SumAmount)
.SelectSum(x => x.Salary)
.WithAlias(() => dto.SumSalary)
// GROUP BY
.Select(x => client.CName)
.WithAlias(() => dto.ClientName)
)
// we do have enough info to ask NHibernate for
// fully typed result - Product DTO
.TransformUsing(Transformers.AliasToBean<ProductDTO>())
.List<ProductDTO>();
That should give some idea how to do querying with NHibernate. Also, I would suggest to extend Customer with IList<Products>
There is no issue; I'm just curious as to how FluentNHibernate/NHibernate knows which rows to access.
In the following pseudocode, I have these entities:
public class User
{
public virtual int Id { get; protected set; }
public virtual IList<Friend> Friends { get; set; }
}
public class Friend
{
public virtual int Id { get; protected set; }
public virtual User User { get; set; }
public virtual String FunnyProperty { get; set; }
}
with this one-to-many relationship:
public class UserMap : ClassMap<User>
{
Id(x => x.Id);
// One user has many friends
HasMany(x => x.Friends).KeyColumn("UserId")...
}
public class FriendMap : ClassMap<Friend>
{
Id(x => x.Id);
References(x => x.User, "UserId")...
}
If I run the following code to establish the two-way relationship:
var user = new User().Friends.AddRange( /* 5 friends */ );
/* 5 friends */.ForEach(friend => friend.User = user);
and then set:
user[2].FunnyProperty = "a magic string";
then, in a visual database explorer tool, I see the table Users as:
Id
--------------------------------------
63
--------------------------------------
and the table Friends as:
Id | UserId | FunnyProperty
--------------------------------------
24 | 63 | (null)
--------------------------------------
25 | 63 | (null)
--------------------------------------
26 | 63 | a magic string
--------------------------------------
27 | 63 | (null)
--------------------------------------
28 | 63 | (null)
--------------------------------------
How does FluentNHibernate/NHibernate "remember" id 26 as the 3rd Friend object in user.Friends? Say you commit the transaction, begin another transaction elsewhere, and retrieve the same user.Friends[2]. How does FluentNHibernate/NHibernate know to access the row with id 26?
Especially when when you have a Friends table with many different Users floating about, like below, how does FluentNHibernate/NHibernate "remember" which row to access ?:
Id | UserId | FunnyProperty
--------------------------------------
24 | 24 | (null)
--------------------------------------
25 | 89 | (null)
--------------------------------------
26 | 66 | a magic string
--------------------------------------
27 | 12 | (null)
--------------------------------------
28 | 66 | (null)
--------------------------------------
28 | 89 | (null)
--------------------------------------
Your Friends table contains the property UserId which is the referenence to friends. Fluent NHibernatie reads you mapping where you have defined this.
There is also an index column in the Friends table.
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.
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.