I have a problem with NHibernate for a longtime which I solved by non-optimal ways/workarounds.
First of all, I'm using WCF REST to communicate with my client application. As you know, serializing persisted entities is not a best practise and always causes other problems. Thus, I always map my entities to DTO's with NHibernates Transformers. The problem is that I have entities which are more complex to use Transformers to convert them.
How can I map sub entities to sub dto's by using transformers or any other nhibernate feature?
Note: I don't want to use 3rd parties like Automapper.
These are the Entities and DTO's which I want to map. Variable names are exactly same with each other.
Entity Classes:
EntityType
public class crmEntityType : EntityModel<crmEntityType>
{
public crmEntityType()
{
Association = new List<crmEntityType>();
Fields = new List<crmCustomField>();
}
public virtual int ID { get; set; }
public virtual string Title { get; set; }
public virtual ICollection<crmEntityType> Associations { get; set; }
public virtual ICollection<crmCustomField> Fields { get; set; }
}
CustomFields
public class crmCustomField : EntityModel<crmCustomField>
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual crmEntityType EntityType { get; set; }
}
DTO's
EntityTypeDTO
[DataContract]
public class EntityTypeDTO
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Title { get; set; }
[DataMember]
public IList<CustomFieldDTO> Fields { get; set; }
[DataMember]
public int[] Associations { get; set; }
}
CustomFieldDTO
[DataContract]
public class CustomFieldDTO
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int EntityType { get; set; }
[DataMember]
public int FieldType { get; set; }
}
I found my solution by spending my day and night to work it out. Finally, I've got the best solution I could find. I hope it helps someone in my condition some day.
This linq query works with just one database round-trip. I think it maps the classes in memory.
return (from entityType in Provider.GetSession().Query<crmEntityType>()
.Fetch(x => x.Association)
.Fetch(x => x.Fields)
.AsEnumerable()
select new EntityTypeDTO()
{
ID = entityType.ID,
Title = entityType.Title,
Association = entityType.Association.Distinct()
.Select(asc => asc.ID).ToArray<int>(),
Fields = entityType.Fields.Distinct()
.Select(fi => new CustomFieldDTO
{ ID = fi.ID,
Name = fi.Name,
Value = fi.Value,
EntityType = fi.EntityType.ID,
Type = fi.Type
}).ToList()
}).ToList();
Related
I am working on Entity Framework Core Code First approach and ASP.Net Core 2.1 making 3 tables:
Person class
public class Person
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public PeopleProfessions PeopleProfessions { get; set; }
}
Professions' class
public class Profession
{
public string Id { get; set; }
public string Name{ get; set; }
public PeopleProfessions PeopleProfessions { get; set; }
}
peopleprofessions' class
public class peopleprofessions
{
[ForeignKey("PersonId ")]
public string PersonId { get; set; }
public ICollection<Person> People { get; set; }
[ForeignKey("ProfessionId")]
public string ProfessionId{ get; set; }
public ICollection<Profession> Professions { get; set; }
}
On my Context:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<peopleprofessions>().HasKey(up => new { up.PersonId, up.ProfessionId });
}
Bearing this in mind:
People can have multiple professions.
The professions table is only for reading stored data like "Accountant".
I have doubts about how I can make table 3 only contain the foreigners and that it can meet the needs that I just mentioned.
I have tried to make the relationship appropriately but I also noticed that in tables 1 and 2 it requests both Id of the table people's professions.
I don't know if I am lost or if I am looking wrong or if there is an alternative to that situation. Thanks for any help you can give me.
You have the use of Collections on the navigation items a bit backwards. For your primary entities (Person and Profession), they should have collections, since it's one-to-many. But for the PeopleProfessions, each record is a single link to a specific entity, so no collection there just a direct object reference.
public class Person
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<PeopleProfessions> PeopleProfessions { get; set; }
}
public class Profession
{
public string Id { get; set; }
public string Name{ get; set; }
public ICollection<PeopleProfessions> PeopleProfessions { get; set; }
}
public class PeopleProfessions
{
public string PersonId { get; set; }
public Person Person { get; set; }
public string ProfessionId { get; set; }
public Profession Profession { get; set; }
}
You can, but don't need to specify a ForeignKey attribute because you are following EFs naming conventions(it will figure it out for you). Your OnModelCreating looks correct for the composite key.
You may want to consider removing the plural from PeopleProfessions (just call the class PeopleProfession) since one instance represents a single People-Profession relationship. I typically do this and but the navigation name in the entities remains plural, since it can represent more than one, i.e.
public ICollection<PeopleProfession> PeopleProfessions { get; set; }
So currently I have an application model of:
Note -> Thing
(A note can relate to many things)
A thing can be numerous entities (for this example lets use Computer & Car)
e.g
Note -> Computer
-> Car
So right now, I have the schema of
Note -> ComputerNote -> Computer
Note -> CarNote -> Car
The problem is that because the entity links are in separate tables, it requires a new query to be written rather than just using filtering in the WHERE clause.
Ideally it would be nice to have an EntityId & EntityTypeId column & on the Note table that would hold the primary key of the related entity and the type of the entity. Thus application logic could look for all Car notes where the car is x without a separate query for each type, but.. this would mean I lose referential integrity. Is there a better way, or is what I have suggested an acceptable design?
Entity Framework Model's:
public partial class Note
{
public Note()
{
NoteComputer = new HashSet<NoteComputer>();
NoteCar = new HashSet<NoteCar>();
NoteThing = new HashSet<NoteThing>();
}
public int Id { get; set; }
public string Value { get; set; }
public string CreatedByUserId { get; set; }
public DateTime CreatedDateTime { get; set; }
public ICollection<NoteComputer> NoteComputer { get; set; }
public ICollection<NoteCar> NoteCar { get; set; }
public ICollection<NoteThing> NoteThing { get; set; }
}
public partial class NoteCar
{
public int Id { get; set; }
public int NoteId { get; set; }
public int CarId { get; set; }
public Car Car { get; set; }
public Note Note { get; set; }
}
public partial class NoteComputer
{
public int Id { get; set; }
public int NoteId { get; set; }
public int ComputerId { get; set; }
public Computer Computer { get; set; }
public Note Note { get; set; }
}
public partial class NoteThing
{
public int Id { get; set; }
public int NoteId { get; set; }
public int ThingId { get; set; }
public Thing Thing { get; set; }
public Note Note { get; set; }
}
As there seems to be no nice way to handle this at the database level, I've found it best to handle this at the application level using the concrete type database schema, to produce dynamic joins.
Example in Entity Framework Core:
public GenericEntityProvider
{
private readonly IEnumerable<IEntityProvider> _entityProviders;
private readonly DatabaseContext _context;
public GenericEntityProvider(IEnumerable<IEntityProvider> entityProviders, DatabaseContext context)
{
_entityProviders = entityProviders;
_context = context;
}
public IEnumerable<Note> Get(Type type, int id) {
var provider = _entityProviders.GetPredicate(type, id);
return _context.Notes.Where(provider);
}
}
public CarNoteProvider : IEntityProvider
{
public Expression<Func<Note, bool>> GetPredicate(Type type, int id)
{
return x => x.CarNote.Any(cn => cn.CarId == id);
}
}
We are using EF5, Code First approach to an MVC4 app that we're building. We are trying to update 1 property on an entity but keep getting errors. Here's what the class looks like which the context created:
public partial class Room
{
public Room()
{
this.Address = new HashSet<Address>();
}
public int RoomID { get; set; }
public Nullable<int> AddressID { get; set; }
public Nullable<int> ProductVersionID { get; set; }
public string PhoneNumber { get; set; }
public string AltPhone { get; set; }
public string RoomName { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Notes { get; set; }
public virtual ICollection<Address> Address { get; set; }
}
Here's our ViewModel for the view:
public class RoomDetailsViewModel
{
//public int RoomID { get; set; }
public string RoomName { get; set; }
public string PhoneNumber { get; set; }
public string AltPhone { get; set; }
public string Notes { get; set; }
public string StateCode { get; set; }
public string CountryName { get; set; }
public string ProductVersion { get; set; }
public int PVersionID { get; set; }
public List<SelectListItem> ProductVersions { get; set; }
public Room Room { get; set; }
}
Here's the Controller Action being called on "Save":
[HttpPost]
public virtual ActionResult UpdateRoom(RoomDetailsViewModel model)
{
var db = new DBContext();
bool b = ModelState.IsValid;
var rooms = db.Rooms;
var rm = rooms.Where(r => r.RoomID == model.Room.RoomID).Single();
//List<Address> address = db.Addresses.Where(a => a.AddressID == rm.AddressID).ToList<Address>();
rm.ProductVersionID = model.PVersionID;
//rm.Address = address;
db.Entry(rm).Property(r => r.ProductVersionID).IsModified = true;
//db.Entry(rm).State = System.Data.EntityState.Modified;
db.SaveChanges();
return View("RoomSaved", model);
}
All this view does is display data and allow the user to change the Product Version (from a SelectList), so, in the Room Entity, all we are updating is the ProductVersionID property, nothing else. We can get the data to display properly but when we click "save", we get this error:
An object of type 'System.Collections.Generic.List`1[[Models.Address,
Web.Mobile.TestSite, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]]' cannot be set or removed from the Value
property of an EntityReference of type 'Models.Address'.
As you can see by the Controller Action, we've tried several different things but all seem to produce this error. I've tried to populate the model.Room.Address collection with an Address, without, but still get this error.
I read this StackOverflow article and this article as well but neither have solved my problem.
ANY help with this would be greatly appreciated!
After hours and hours of digging, turns out that EF did not import some of the PK's for my DB tables. What tipped me off to this was on the Room class, the PK RoomID did not have the [Key] attribute on it. I tried to reimport the table through the edmx but it never came through as a key (even though it's clearly marked PK in the DB). So, to get around it, I created a partial class of my DBContext and override the OnModelCreating event and included the key, like so:
public partial class DBContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Models.Room>().HasEntitySetName("Rooms");
modelBuilder.Entity<Models.Room>().HasKey(r => r.RoomID);
}
}
Once this was done, the Action saved the record as hoped.
I hope this helps someone else!
I have the following situation with fluent nhibernate:
public class Stuff
{
public Stuff()
{
Entities = new List<Entity>();
}
public virtual int Id { get; set; }
public virtual IList<Entity> Entities { get; set; }
}
public abstract class Entity
{
public virtual int Id { get; set; }
public virtual string Type { get; set; }
public virtual Stuff Stuff { get; set; }
}
public class Person : Entity
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class Animal : Entity
{
public virtual string Species { get; set; }
}
And then, i have the following code to use automap and generate these mappings:
var sessionFactory =
Fluently.Configure().Database(persistenceConfigurer).Mappings(
m =>
m.AutoMappings.Add(
AutoMap.Source(new Types(typeof(Entity), typeof(Person), typeof(Animal), typeof(Stuff))))
.ExportTo(#"e:\")).ExposeConfiguration(BuildSchema).BuildSessionFactory();
however, what's happening is that i get the following exception:
---> NHibernate.MappingException: Association references unmapped class: ConsoleApplication1.Models.Entity
if i make the entity class non abstract this works, however, i'd like to avoid having that table in the database but still maintain the hierarchy concept with the re
You need to add your auto mappings like this
AutoMap.AssemblyOf<Entity>(yourConfiguration).IgnoreBase<Entity>();
Not only will this ignore your Entity base class but you don't need to add each entity manually so long as each model inherits from Entity.
So, I need to map my class to database table, but sometimes I need lazy loading to be on, sometimes to be off.
Example:
I made duplicates of these two classes described below, and I map them using FNH but with lazy loading on in original, and off in duplicate.
http://i.stack.imgur.com/goG30.png
Basically, I want to be able to get a team from DB that has all lists with TeamMembers fetched but those members should not have all their teams and everything else, just plain info about a TeamMember. Also when i get an TeamMember from DB, I want all its teams to contain only plain info's.
"http://i.stack.imgur.com/7OkyD.png" ->they don't allow new users to post pics or links.
So if I have only those two classes, then if lazy loading is turned on on any side, one of the situations explained before is not satisfied. If lazy is off on both sides I get a whole bunch of data that I don't need nor want.
At first original and duplicate had same names and were in different packages, but I got an exception that mapping was ambiguous. If there is a way for this to work, that would be ideal.
Is there a way to do this?
I couldn't find an answer so I changed name of duplicate to be NameOfOriginal+Lite.
Mapping was parsed but when I wanted to get a team from database, I get an exception:
{"ORA-00904: \"SUPERVIZ_\".\"TEAMMEMBERLITE_ID\": invalid identifier\n"}
So, apparently FNH reads a class name and ads an "_ID" and uses that as a ID for my duplicate class and that causes the problem. I tried with
.ParentKeyColumn("")
.ChildKeyColumn("")
but no success.
I hope I didn't confuse you too much :)
[DataContract]
public class Team
{
[DataMember]
public virtual int Team_id { get; private set; }
[DataMember]
public virtual String Name { get; set; }
[DataMember]
public virtual String Description { get; set; }
[DataMember]
public virtual TeamMember Deputy { get; set; }
[DataMember]
public virtual TeamMember Leader { get; set; }
[DataMember]
public virtual IList<TeamMember> TeamMembers { get; set; }
[DataMember]
public virtual IList<TeamMember> Supervizors { get; set; }
...
}
[DataContract]
public class TeamMember
{
[DataMember]
public virtual int TeamMember_id { get; set; }
[DataMember]
public virtual String First_name { get; set; }
[DataMember]
public virtual String Last_name { get; set; }
[DataMember]
public virtual String Sid { get; set; }
[DataMember]
public virtual IList<Team> SupevisingTeams { get; set; }
[DataMember]
public virtual IList<Team> LeaderInTeams { get; set; }
[DataMember]
public virtual IList<Team> DeputyInTeams { get; set; }
[DataMember]
public virtual IList<Team> MemberInTeams { get; set; }
...
}
You can map a class multiple times if you give the mapping an entity name. You will need to use ClassMap for this rather than auto mapping.
Using Fluent NHibernate:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
public class FooMap1 : ClassMap<Foo>
{
public FooMap1()
{
Table("Foo");
EntityName("Foo");
Id(x => x.Id);
Map(x => x.Name);
}
}
public class FooMap2 : ClassMap<Foo>
{
public FooMap2()
{
Table("Foo");
EntityName("Bar");
Id(x => x.Id);
Map(x => x.Name);
}
}
You need to specify the entity name when building the query to make nhibernate use the correct mapping:
using (var session = _sessionFactory.GetCurrentSession())
{
return session.CreateCriteria("Bar")
.Add(Restrictions.Eq("Name", "Andrew"))
.List<Foo>();
}