Mapping multiple properties of a same type with HasMany via automapping - fluent-nhibernate

I am trying to map properties of the same type on a OneToMany association. I tried to distinguish with Description but kinda stuck here.
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
[Description("From")]
public virtual IList<Message> FromMessageList { get; set; }
[Description("To")]
public virtual IList<Message> ToMessageList { get; set; }
}
public class Message
{
public virtual int MessageId { get; set; }
public virtual string Text { get; set; }
[Description("From")]
public virtual User FromUser { get; set; }
[Description("To")]
public virtual User ToUser { get; set; }
}
public class DefaultHasManyConvention : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
if (instance.OtherSide.Property.GetDescription() == instance.Member.GetDescription())
{
if (instance.Member.GetDescription() != null)
instance.Key.Column(instance.Member.GetDescription() + "Id");
else
instance.Key.Column(instance.OtherSide.Property.Name + "Id");
instance.Fetch.Select();
}
}
}
public class DefaultReferenceConvention : IReferenceConvention
{
public void Apply(IManyToOneInstance instance)
{
if (instance.Property.GetDescription() != null)
instance.Column(instance.Property.GetDescription() + "Id");
else
instance.Column(instance.Property.Name + "Id");
instance.Fetch.Select();
}
}

For one to many relationships I generally use coding like :
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
[Description("From")]
public virtual ICollection<Message> FromMessageList { get; set; }
[Description("To")]
public virtual ICollection<Message> ToMessageList { get; set; }
}
public class Message
{
public int MessageId { get; set; }
public string Text { get; set; }
[Description("From")]
public virtual User FromUser { get; set; }
// From user foreign key column
[ForeignKey("FromUser")]
public int FromUserId {get;set;}
[Description("To")]
public virtual User ToUser { get; set; }
// ToUser foreign key column
[ForeignKey("ToUser")]
public int ToUserId {get;set;}
}
Try to use ICollection instead of IList - this solved many issues for me.
Add foreign key column names; it makes mapping simpler and filtering in queries easier.

Related

How do I insert nested objects correctly in Ef

I had a problem trying to add object from dto.
I have a DTO like this
public class CustomerCourseSessionDto : IDto
{
public int Id { get; set; }
public int? ConnectorId { get; set; }
public List<SomeObject>? SomeObject { get; set; }
public List<OtherObject>? OtherObject { get; set; }
}
and I'm trying to insert this DTO sent from the UI. I'm using a loop(foreach) for this, but it didn't feel right to me.
Maybe entityframework suggests a method for this, idk...
Other objects are like this
public class SomeObject: IEntity
{
public int Id { get; set; }
public int? ConnectorId { get; set; }
}
public class OtherObject: IEntity
{
public int Id { get; set; }
public int? ConnectorId { get; set; }
}
Thanks for the suggestions.
Here is where I use the loop:
[TransactionScopeAspect]
public IResult AddWithDto(CustomerCourseSessionDto courseSessionDto)
{
foreach (var item in courseSessionDto.Participants)
{
_someObjectService.Add(item);
}
foreach (var item in courseSessionDto.Lessons)
{
_otherObjectService.Add(item);
}
_customerCourseSessionDal.Add(new CustomerCourseSession
{
Id = courseSessionDto.Id,
CustomerCourseId = courseSessionDto.CustomerCourseId,
});
return SuccessResult with message ;
}
You should design your entities to have a link to each table using foreign keys.
public class CustomerCourseSession
{
public int Id { get; set; } // Primary Key
public string Name { get; set; }
public virtual ICollection<CourseParticipant> Participants { get; set; }
public virtual ICollection<CourseLesson> Lessons { get; set; }
}
public class CourseParticipant
{
public int Id { get; set; }
public int CustomerCourseSessionId { get; set; } // Foreign Key from CustomerCourseSession
// other fields here
public virtual CustomerCourseSession CustomerCourseSession { get; set;}
}
public class CourseLesson
{
public int Id { get; set; }
public int CustomerCourseSessionId { get; set; } // Foreign Key from CustomerCourseSession
// other fields here
public virtual CustomerCourseSession CustomerCourseSession { get; set;}
}
Then you can just call:
var entity = MapDtoToEntity(courseSessionDto);
_dbContext.CustomerCourseSessions.Add(entity);
_dbContext.SaveChanges();

Is it possible to link one table to another with entity framework core without FKs?

I have 2 tables company and user. Company will have one created by user and one modified user - these will be admin users. User will belong to one company but one admin user could create or modify multiple companies.
I'm having a hard time using entity framework core in my .net core app to join company and user so when I get a company record I have the created by and modified user information.
My company and user classes look like this:
public class Company
{
[Key]
public Guid Id { get; set; }
public DateTime Created { get; set; }
public Guid Created_By { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
public DateTime Modified { get; set; }
public Guid Modified_By { get; set; }
public virtual ApplicationUser ModifiedByUser { get; set; }
public string Company_Name { get; set; }
}
public class ApplicationUser: IdentityUser<Guid>
{
[Column("ID")]
public override Guid Id { get; set; }
[Column("CREATED")]
public DateTime Created { get; set; }
[Column("CREATED_BY")]
public Guid? CreatedBy { get; set; }
[Column("MODIFIED")]
public DateTime Modified { get; set; }
[Column("MODIFIED_BY")]
public Guid? ModifiedBy { get; set; }
[Column("FIRST_NAME")]
public string FirstName { get; set; }
[Column("LAST_NAME")]
public string LastName { get; set; }
[Column("EMAIL")]
public override string Email { get; set; }
[Column("NORMALIZED_EMAIL")]
public override string NormalizedEmail { get; set; }
[Column("EMAIL_CONFIRMED")]
public override bool EmailConfirmed { get; set; }
[Column("USER_NAME")]
public override string UserName { get; set; }
[Column("NORMALIZED_USER_NAME")]
public override string NormalizedUserName { get; set; }
[Column("COMPANY_ID")]
public Guid CompanyId { get; set; }
[Column("PHONE_NUMBER")]
public override string PhoneNumber { get; set; }
[Column("PHONE_NUMBER_CONFIRMED")]
public override bool PhoneNumberConfirmed { get; set; }
[Column("TITLE")]
public string Title { get; set; }
[Column("ACTIVE")]
public bool Active { get; set; }
[Column("ROLE_ID")]
public int UserRoleId { get; set; }
[Column("TYPE_ID")]
public int TypeId { get; set; }
[Column("PASSWORD_HASH")]
public override string PasswordHash { get; set; }
[Column("SECURITY_STAMP")]
public override string SecurityStamp { get; set; }
[Column("CONCURRENCY_STAMP")]
public override string ConcurrencyStamp { get; set; }
[Column("TWO_FACTOR_ENABLED")]
public override bool TwoFactorEnabled { get; set; }
[Column("LOCKOUT_END")]
public override DateTimeOffset? LockoutEnd { get; set; }
[Column("LOCKOUT_ENABLED")]
public override bool LockoutEnabled { get; set; }
[Column("ACCESS_FAILED_COUNT")]
public override int AccessFailedCount { get; set; }
}
My DbContext class looks like this:
public class DbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<Guid>, IdentityRoleClaim<Guid>, IdentityUserToken<Guid>>
{
public DbContext(DbContextOptions<DbContext> options) : base(options)
{
}
public virtual DbSet<Company> Companies { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Company>().ToTable("COMPANY").Property<Guid>("Created_By");
builder.Entity<Company>().HasOne(x => x.CreatedByUser).WithOne().HasForeignKey("Created_By");
builder.Entity<ApplicationUser>().ToTable("USER");
builder.Entity<ApplicationUser>().HasKey(x => x.Id);
builder.Entity<ApplicationUserClaim>().ToTable("USER_CLAIMS");
builder.Entity<ApplicationRole>().ToTable("IDENTITY_ROLES");
builder.Entity<IdentityUserRole<Guid>>().HasKey(p => new { p.UserId, p.RoleId });
builder.Entity<ApplicationUserRole>().ToTable("IDENTITY_USER_ROLES");
}
}
And I was trying to get companies like this:
public async Task<List<Company>> GetAllCompanies()
{
return await _locationDbContext.Companies.ToListAsync();
}
Currently I am getting this error:
System.InvalidOperationException: 'You are configuring a relationship between 'Company' and 'ApplicationUser' but have specified a foreign key on 'Created_By'. The foreign key must be defined on a type that is part of the relationship.'
Is there an easier way to do this? Really all I want is the username of the user that created of modified the company record? If I was doing this with just sql I would just use a basic Join but Im not sure how to do that with entity framework. Worst case I would just get all the companies and then loop through doing a select on the user table where ID = Created_By
As #IvanStoev noticed without FKs it is not possible. But you can still use EF to join 2 tables.
In your case you have to unmap user from company and maybe it is a good idea to make user Guid nullable:
public class Company
{
[Key]
public Guid Id { get; set; }
public string Company_Name { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
public Guid Created_By { get; set; }
public Guid Modified_By { get; set; }
[NotMapped]
public ApplicationUser CreatedByUser { get; set; }
// or better
[NotMapped]
public string CreatedByUser { get; set; }
[NotMapped]
public ApplicationUser ModifiedByUser { get; set; }
//or better
[NotMapped]
public string ModifiedByUser { get; set; }
}
and remove
builder.Entity<Company>().HasOne(x => x.CreatedByUser).WithOne().HasForeignKey("Created_By");
you still can join them like this
var companies= (
from c in _locationDbContext.Companies
join uc in _locationDbContext.ApplicatonUser on c.Created_By equals uc.Id
join um in _locationDbContext.ApplicatonUser on c.Modified_By equals um.Id
select new Company
{
....
CreatedByUser = uc,
ModifiedByUser = um
// or usually
CreatedByUser = uc.FirstName + " " + uc.LastName,
ModifiedByUser = um.FirstName + " " + um.LastName,
}).ToList();
This is one way of accomplishing this.
This is a simplified version of class Company:
public class Company
{
public int CompanyID { get; set; }
public string Name { get; set; }
//
// Relations
public string CreatorID { get; set; }
public ApplicationUser Creator { get; set; }
public string LastModifiedByID { get; set; }
public ApplicationUser LastModifiedBy { get; set; }
}
CreatorID and LastModifiedByID will be used by EF for determining the FK's. These are the fields you have to work with when updating the Db, not Creator and LastModfiedBy, yet you can also do it but with more lines of code.
Now, you can add companies the usual way, this code searches for the company, and creates one if not found, just sample code, not serious, really:
var companyName = "My company";
var company = context.Companies.Include(c => c.Creator).Include(c => c.Creator).FirstOrDefault(c => c.Name == companyName);
if (company == null)
{
company = new Company
{
Name = "My company",
CreatorID = user.Id,
LastModifiedByID = user.Id
};
context.Companies.Add(company);
context.SaveChanges();
}
And retrieving the companies with all relations filled is a matter or using calls to Include() and ThenInclude(), like here:
var companies = context.Companies.Include(c => c.Creator).Include(c => c.Creator).ToList();
I omitted all filtering logic for the sake of simplicity.

Fluent NHibernate mapping throws an exception of 'Id is not mapped'

I need help with auto mapping in Fluent Nhibernate. Here's the tables I want to have in my app (they are many of them, but I want to start from mapping only a few of them)
Well, I'd like to use the AutoMapping functionality because I don't want to write the mapping classes for more than 100 tables...
Anyway, here's the error thrown when creating the SessionFactory (the code is at the end of this post)
The entity 'FilterConfig' doesn't have an Id mapped.
Use the Id method to map your identity property. For example: Id(x => x.Id).
Entities (I hope I created them correctly):
public partial class UserLogin
{
public UserLogin()
{
this.UserMessages = new List<UserMessage>();
this.UserMessagesReceivers = new List<UserMessagesReceiver>();
}
public virtual int ID { get; set; }
public virtual int UserTypeID { get; set; }
public virtual int? StudentID { get; set; }
public virtual int? HeadmasterID { get; set; }
public virtual int? ParentID { get; set; }
public virtual string UniqueID { get; set; }
public virtual bool ShowMyPhoneNumber { get; set; }
public virtual bool IsBanned { get; set; }
public virtual string Login { get; set; }
public virtual string Password { get; set; }
public virtual bool WasPasswordSent { get; set; }
public virtual string Email { get; set; }
public virtual string UserPicture { get; set; }
public virtual IList<UserMessage> UserMessages { get; set; }
public virtual IList<UserMessagesReceiver> UserMessagesReceivers { get; set; }
}
public partial class UserMessage
{
public UserMessage()
{
this.UserMessagesReceivers = new List<UserMessagesReceiver>();
this.UserMessagesReplies = new List<UserMessagesReply>();
}
public virtual int ID { get; set; }
public virtual DateTime Date { get; set; }
public virtual DateTime? LastCheckDate { get; set; }
public virtual int UserLoginID { get; set; }
public virtual string Description { get; set; }
public virtual bool HasNonCheckedReplies { get; set; }
public virtual UserLogin UserLogin { get; set; }
public virtual IList<UserMessagesReceiver> UserMessagesReceivers { get; set; }
public virtual IList<UserMessagesReply> UserMessagesReplies { get; set; }
}
public partial class UserMessagesReceiver
{
public UserMessagesReceiver()
{
this.WasMessageChecked = false;
this.UserMessagesReplies = new List<UserMessagesReply>();
}
public virtual int ID { get; set; }
public virtual int UserMessagesID { get; set; }
public virtual int ReceiverLoginID { get; set; }
public virtual bool WasMessageChecked { get; set; }
public virtual DateTime? LastCheckedDate { get; set; }
public virtual UserLogin UserLogin { get; set; }
public virtual UserMessage UserMessage { get; set; }
public virtual IList<UserMessagesReply> UserMessagesReplies { get; set; }
}
public partial class UserMessagesReply
{
public UserMessagesReply()
{
}
public virtual int ID { get; set; }
public virtual DateTime Date { get; set; }
public virtual int UserMessagesID { get; set; }
public virtual int? UserMessagesReceiverID { get; set; }
public virtual string Description { get; set; }
public virtual UserMessage UserMessage { get; set; }
public virtual UserMessagesReceiver UserMessagesReceiver { get; set; }
}
Configuration:
public class AutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool IsId(Member member)
{
return member.Name == member.DeclaringType.Name + "ID";
}
}
private static AutoPersistenceModel CreateAutomappings()
{
return AutoMap.AssemblyOf<AutomappingConfiguration>(new AutomappingConfiguration());
}
private static ISessionFactory CreateSessionFactory()
{
var cfg = new AutomappingConfiguration();
return Fluently.Configure()
.Database(MySQLConfiguration.Standard
.ConnectionString("..."))
.Mappings(m => m.AutoMappings
.Add(AutoMap.AssemblyOf<UserLogin>(cfg))
.Add(AutoMap.AssemblyOf<UserMessage>(cfg))
.Add(AutoMap.AssemblyOf<UserMessagesReceiver>(cfg))
.Add(AutoMap.AssemblyOf<UserMessagesReply>(cfg)))
.BuildSessionFactory();
}
in the configuration you said that the Ids are named like UserLoginID but in the class they are defined as public virtual int ID { get; set; } so change
return member.Name == member.DeclaringType.Name + "ID";
to
return member.Name == "ID";
Some additional info:
CreateAutomappings() seems to be not used
AutomappingConfiguration should at least override ShouldMap(Member) to filter on the namespace (e.g. member.Namespace.StartsWith(typeof(UserMessage).Namespace)) otherwise sooner or later utility classes and the like will be mapped as well
AutoMap.AssemblyOf<> should be called per assembly containing types not per type

Entity Relationship - CodeFirst - Multiple Relationships

I'm trying to create the following object for my DB (CodeFirst),
And i have the following problem:
when i try to add new one or many object to a new srevice provider slots (as follows:)
ServiceProviderSlots slot = new ServiceProviderSlots();
slot.ServiceProviderID = 1;
slot.SlotServices.Add(new ServiceProviderServices() { ServiceProviderServiceID = 1 });
ctx.ServiceProviderSlots.Add(slot);
I get an error, because i'm not creating a new object, and i'm missing foreign keys, but i wish to use data which already exists on that table.
ERROR:
{"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_dbo.ServiceProviderServices_dbo.ServiceProviders_ServiceProviderID\". The conflict occurred in database \"qunadodb\", table \"dbo.ServiceProviders\", column 'ServiceProviderID'.\r\nThe statement has been terminated."}
Maybe it's related to this?
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
Which i used becuase i couldn't create the db otherwise, i had another error.
See below structure:
public class BusinessCategories
{
[Key]
public int CategoryID { get; set; }
public String Title { get; set; }
}
public class BusinessTypes
{
[Key]
public int TypeID { get; set; }
public String Title { get; set; }
public int CategoryID { get; set; }
public virtual BusinessCategories Categories { get; set; }
}
public class ServiceProviders
{
[Key]
public int ServiceProviderID { get; set; }
public String Name { get; set; }
public Int32 TypeID { get; set; }
public virtual BusinessTypes Type { get; set; }
}
public class ServiceProviderServices
{
[Key]
public Int32 ServiceProviderServiceID { get; set; }
public String Price { get; set; }
public Int32 ServiceProviderID { get; set; }
public virtual ServiceProviders ServiceProvider { get; set; }
public Int32 SubServiceID { get; set; }
public virtual SubServices SubService { get; set; }
}
public class ServiceProviderSlots
{
public ServiceProviderSlots()
{
SlotServices = new List<ServiceProviderServices>();
}
[Key]
public Int32 SlotID { get; set; }
public String Name { get; set; }
public Int32 ServiceProviderID { get; set; }
[ForeignKey("ServiceProviderID")]
public virtual ServiceProviders ServiceProvider { get; set; }
public virtual ICollection<ServiceProviderServices> SlotServices { get; set; }
}
this message means that you probably yet have a ServiceProvider with Id = 1 in the database.
in this cas you have to attach the entity to the context.

How to map a derived class using an EntityBase class on FluentNHibernate

I have an EntityBase class for FluentNHibernate:
public abstract class EntityBase<T>
{
public EntityBase()
{
}
public static T GetById(int id)
{
return (T)Hibernate.Session.Get<T>(id);
}
public virtual void Save()
{
using (var transaction = Hibernate.Session.BeginTransaction())
{
Hibernate.Session.SaveOrUpdate(this);
transaction.Commit();
}
}
public static IList<T> List()
{
return Hibernate.Session.CreateCriteria(typeof(T)).List<T>();
}
public static IList<T> ListTop(int i)
{
return Hibernate.Session.CreateCriteria(typeof(T)).SetMaxResults(i).List<T>();
}
public virtual void Delete()
{
using (var transaction = Hibernate.Session.BeginTransaction())
{
Hibernate.Session.Delete(this);
transaction.Commit();
}
}
}
I have a base member class also a table in database:
abstract public class BaseMember:EntityBase<BaseMember>
{
public virtual int Id { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
public virtual string RecordDate { get; protected set; }
public BaseMember()
{
}
}
I have another Member class that is deriving from BaseMember class:
public class IndividualMember : BaseMember
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string PhoneNumber { get; set; }
public virtual string MobilePhoneNumber { get; set; }
public virtual DateTime BirthDate { get; set; }
public virtual bool Gender { get; set; }
public virtual string ProfileImage { get; set; }
public virtual string AddressDefinition { get; set; }
public virtual string ZipCode { get; set; }
public virtual DateTime RecordDate { get; set; }
public IndividualMember()
{
}
}
How can I map those classes with BaseMember and IndividualMember tables in db?
There are different types of Inheritance mapping strategies in Fluent NHibernate.
You can use SubclassMap mapping for derived class.
Strategies : Table-per-class-hierarchy, Table-per-subclass and Table Per Concrete Class.
For table-per-class-hierarchy strategy, you just need to specify the discriminator column.
For more reference :
http://www.codeproject.com/Articles/232034/Inheritance-mapping-strategies-in-Fluent-Nhibernat
https://github.com/jagregory/fluent-nhibernate/wiki/Fluent-mapping#wiki-subclasses