FluentNHibernate mapping with a filter, is it possible? - filtering

I'm new to NHibernate, and FluentNHibernate. I'm trying to figure out the best way to setup mappings between tables in different schemas as well as potentially doing some filtering in the mappings.
In my scenario I have doctors who are associated with hospitals where a doctor can be associated with multiple hospitals. At some hospitals a doctor might be blacklisted from performing certain procedures. There are also verying degrees of blacklisting and I'm only interested in those whose black list type is zero. Because the tables reside in different schemas and are managed by different IT groups the column names for joining don't always match. I'm trying to figure out the best way to map that relationship.
I have part of this working, but I'm not sure how to get the whole relationship setup so that I only get those hospitals where a doctor has a specific black list designation.
Here's a trimmed down look at my entities.
public class Doctor
{
pubilc virtual int DoctorId { get; set; }
public virtual string Name { get; set; }
public virtual IList<BlackListSite> BlackListedSites { get; set; }
}
public class BlackListSite
{
public virtual int DoctorId { get; set; }
public virtual int HospitalId { get; set; }
public virtual int ProcedureId { get; set; }
public virtual int BlackListTypeId { get; set; }
public virtual IList<Facility> Facilities { get; set; } //these are the hospitals
}
public class Facility //this represents a hospital
{
public virtual int FacilityId{ get; set; }
public virtual string Name { get; set; }
}
Here's how I'm mapping the BlackListSite and Hospital, which is working.
public class BlackListSitesMap : ClassMap<BlackListSites>
{
public BlackListSitesMap()
{
Schema("schema1");
Table("RestrictedDoctors");
Id(x => x.BlackListId).GeneratedBy.Identity();
Map(x => x.DoctorId);
Map(x => x.HospitalId);
Map(x => x.Status);
HasMany(x => x.Facilities).Cascade.All().Not.LazyLoad()
.KeyColumns.Add("HospitalID",
mapping => mapping.Name("FacilityID"));
}
}
public class FacilityMap : ClassMap<Facility>
{
public FacilityMap()
{
Schema("schema2");
Table("Facility");
Id(x => x.FacilityId);
Map(x => x.Name);
Map(x => x.Active);
}
}
A query like this gets me a list of blacklisted sites where blacklist type is 0.
public void TestBlackList()
{
using (var session = SessionManager.Session())
{
var blacklist = session.Query<BlackListSites>().
Where(b => b.DoctorId == 1 && b.HospitalId != null
&& b.Status == 0).ToList();
}
}
I'm not sure how to tie the blacklisted sites to the Doctor so that when I request a doctor I get a list of sites where the doctor is blacklisted with a blacklist type of zero. Any ideas?
If I can't do this on the mapping, can I somehow setup the query on Doctor to filter the blacklist where BlackListTypeId = 0? That may be the most ideal anyway. It would set me up in the future to request other Blacklist types.

i would change the classes to
public class Doctor
{
pubilc virtual int DoctorId { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<BlackListSite> BlackListedSitesTypeZero { get; set; }
}
public class BlackListSite
{
public virtual Doctor Doctor { get; set; }
public virtual Facility Hospital { get; set; }
public virtual int ProcedureId { get; set; }
public virtual int BlackListTypeId { get; set; }
public virtual IList<Facility> Facilities { get; set; } //these are the hospitals
}
public class DoctorMap : ClassMap<Doctor>
{
public DoctorMap()
{
...
HasMany(x => x.BlackListedSitesTypeZero)
.KeyColumn("DoctorId")
.Where("status = 0");
}
}
then in the mappings specify the where condition
public class BlackListSiteMap : ClassMap<BlackListSite>
{
public BlackListSiteMap()
{
Schema("schema1");
Table("RestrictedDoctors");
Id(x => x.BlackListId).GeneratedBy.Identity();
References(x => x.Doctor, "DoctorId");
References(x => x.Hospital, "HospitalId");
Map(x => x.Status);
}
}

Related

Configuring multiple one-to-many relations between same entities when the relations propagate deeper

I have 6 classes where the first class Money, goes deep 5 levels with objects. Whatever I try I cannot get this representation, so I hope someone would be kind to offer some help. At least for first 2,3 levels then I can continue.
public class Money
{
public Money()
{
Dollars = new HashSet<MoneyDetails>();
Pesos = new HashSet<MoneyDetails>();
Pounds = new HashSet<MoneyDetails>();
}
public int Id { get; set; }
public virtual ICollection<MoneyDetails> Dollars { get; }
public virtual ICollection<MoneyDetails> Pesos { get; }
public virtual ICollection<MoneyDetails> Pounds { get; }
public string Note { get; }
}
public class MoneyDetails
{
public MoneyDetails()
{
Valuations = new HashSet<Valuations>();
}
public int Id { get; set; }
public string Description { get; set; }
public double Value { get; set; }
public virtual ICollection<Valuation> Valuations { get; set; }
}
public class Valuations
{
public Valuations ()
{
Lows = new HashSet<Deep>();
Highs = new HashSet<Deep>();
}
public int Id { get; set; }
public string Sum { get; set; }
public virtual ICollection<Deep> Lows { get; set; }
public virtual ICollection<Deep> Highs { get; set; }
}
public class Deep
{
public Deep()
{
Shallows = new HashSet<Shallow>();
}
public int Id { get; set; }
public object Data { get; set; }
public virtual ICollection<Shallow> Shallows { get; set; }
}
EDIT :
I'm using Entity Framework Core.
Following is the configuration I tried myself.
You can see below how I started, I just don't know how to go deeper into objects and make relationships between them so they are connected.
public void Configure(EntityTypeBuilder<Money> builder)
{
builder.ToTable("Money");
builder.HasKey(e => e.Id);
builder.HasMany(s => s.Dollars)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
builder.HasMany(s => s.Pesos)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
builder.HasMany(s => s.Pounds)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
}
You are using -
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
in your configuration code, but you don't have a Money navigation property or a MoneyId foreign-key property in MoneyDetails.
Since you are not using navigation and foreign-key properties in any of your entity models, I'd suggest not to configure the relations manually. Configure other properties in the Configure method if you need, but do not configure the relations. That way, EF will automatically create nullable foreign-keys in your tables and use them as Shadow Property.
EDIT - A better solution for you :
Even though the suggestion above will create all your tables with all the relations, I'm confused about how you plan to use those relations. For example, in the Money entity model Dollars, Pesos, Pounds are all collections of MoneyDetails. Therefore, all the following queries -
var money = myDbContext.Money.Include(p=> p.Dollars).FirstOrDefault(p=> p.Id == someId);
var money = myDbContext.Money.Include(p=> p.Pesos).FirstOrDefault(p=> p.Id == someId);
var money = myDbContext.Money.Include(p=> p.Pounds).FirstOrDefault(p=> p.Id == someId);
will give you the same result - the Money with the specified Id, with a list of all related MoneyDetails. So, there's no point of having three collection properties and three different relations.
Try the following approach to filter related data (you need EF Core 5.0) -
Create enum to identify the entity type -
public enum MoneyDetailsType { Dollar = 1, Peso = 2, Pound = 3 }
public enum DeepType { High = 1, Low = 2 }
Modify your entity models like -
public class Money
{
public Money()
{
MoneyDetails = new HashSet<MoneyDetails>();
}
public int Id { get; set; }
public string Note { get; }
public virtual ICollection<MoneyDetails> MoneyDetails { get; set; }
}
public class MoneyDetails
{
public MoneyDetails()
{
Valuations = new HashSet<Valuations>();
}
public int Id { get; set; }
public string Description { get; set; }
public double Value { get; set; }
public MoneyDetailsType Type { get; set; } // added - type
public int MoneyId { get; set; } // added - foreign-key
public Money Money { get; set; } // added - navigation property (optional)
public virtual ICollection<Valuations> Valuations { get; set; }
}
public class Valuations
{
public Valuations ()
{
Deeps = new HashSet<Deep>();
}
public int Id { get; set; }
public string Sum { get; set; }
public int MoneyDetailsId { get; set; } // added - foreign-key
public MoneyDetails MoneyDetails { get; set; } // added - navigation property (optional)
public virtual ICollection<Deep> Deeps { get; set; }
}
public class Deep
{
public int Id { get; set; }
public string Data { get; set; }
public DeepType Type { get; set; } // added - type
public int ValuationsId { get; set; } // added - foreign-key
public Valuations Valuations { get; set; } // added - navigation property (optional)
}
Notice, in Deep entity, currently you have Data as of type object which is not allowed. You have to change it to some primitive type. I'm using it as string. I have also omitted the Shallows property since you haven't added the Shallow model.
Your configuration methods should look like -
public void Configure(EntityTypeBuilder<Money> builder)
{
builder.ToTable("Money");
builder.HasMany(p => p.MoneyDetails)
.WithOne(p => p.Money)
.HasForeignKey(p => p.MoneyId);
}
public void Configure(EntityTypeBuilder<MoneyDetails> builder)
{
builder.ToTable("MoneyDetails");
builder.Property(p => p.Type).IsRequired(true).HasConversion(new EnumToStringConverter<MoneyDetailsType>());
builder.HasMany(p => p.Valuations)
.WithOne(p => p.MoneyDetails)
.HasForeignKey(p => p.MoneyDetailsId);
}
public void Configure(EntityTypeBuilder<Valuations> builder)
{
builder.ToTable("Valuations");
builder.HasMany(p => p.Deeps)
.WithOne(p => p.Valuations)
.HasForeignKey(p => p.ValuationsId);
}
public void Configure(EntityTypeBuilder<Deep> builder)
{
builder.ToTable("Deep");
builder.Property(p => p.Type).IsRequired(true).HasConversion(new EnumToStringConverter<DeepType>());
}
If you don't want to include the navigation properties in your entity models, then you can just keep the .WithOne() method empty in your configuration, like -
builder.HasMany(p => p.MoneyDetails)
.WithOne()
.HasForeignKey(p => p.MoneyId);
Now you can query like -
var money = myDbContext.Money
.Include(p=> p.MoneyDetails.Where(r=> r.Type == MoneyDetailsType.Dollar))
.FirstOrDefault(p=> p.Id == someId);
and it will give you the Money with a list of only Dollar type MoneyDetails.

Related Entity Query

I have a database with thee tables - Organisations, Locations and OrganisationLocations for example. Organisations holds information on companies, Locations holds information on offices address, and OrganisationLocations holds the many-to-many relationship info about the companies and their addresses. For example Company A might be locations X and Y, Company B might be in locations Y and Z.
These are the generated classes
public partial class Locations
{
public Locations()
{
OrganisationLocations = new HashSet<OrganisationLocations>();
}
public long Id { get; set; }
public string Description { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string PostCode { get; set; }
public string Name { get; set; }
public virtual ICollection<OrganisationLocations> OrganisationLocations { get; set; }
}
public partial class Organisations
{
public Organisations()
{
OrganisationLocations = new HashSet<OrganisationLocations>();
}
public long Id { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public string ContactName { get; set; }
public string Email { get; set; }
public string Telephone { get; set; }
public virtual ICollection<OrganisationLocations> OrganisationLocations { get; set; }
}
public partial class OrganisationLocations
{
public long Id { get; set; }
public long OrganisationId { get; set; }
public long LocationId { get; set; }
public virtual Locations Location { get; set; }
public virtual Organisations Organisation { get; set; }
}
The DB Context looks like this:
public class PfApiContext : DbContext
{
public PfApiContext(DbContextOptions<PaymentAPIContext> options)
: base(options)
{
}
public DbSet<Locations> Locations { get; set; }
public DbSet<Organisations> Organisations { get; set; }
public virtual DbSet<OrganisationLocations> OrganisationLocations { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrganisationLocations>(entity =>
{
entity.HasOne(d => d.Location)
.WithMany(p => p.OrganisationLocations)
.HasForeignKey(d => d.LocationId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_84m7dbl1gv1p6tg1wquwm8j5u");
entity.HasOne(d => d.Organisation)
.WithMany(p => p.OrganisationLocations)
.HasForeignKey(d => d.OrganisationId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_r0mkkndb6c2tr9nl0rgjm068t");
});
}
}
How would I get, for example, all the company data for all the companies in Location X? At the point I'm querying this I actually know the LocationID, so the SQL would be
SELECT * FROM [dbo].[Organisations] WHERE Id IN (SELECT [OrganisationId] FROM [dbo].[OrganisationLocations] WHERE [LocationId] = 1)
This is probably really simple and I'm just being stupid, but I'm new to EFCore and LINQ, and the syntax of this has me scratching my head.
Selecting locations based on a location name I can understand
var locationUsers = await _pfApiContext.UserLocations.Where
(o => o.LocationName == locationName).ToListAsync();
but this is confusing me something terrible.
Thanks for any help
I would remove the virtual collections and objects from all three classes and then remove the Id field (assuming it's the primary key) in the OrganisationLocations table. The OrganisationId and the LocationId fields in the OrganisationLocations should be both the primary keys and the foreign keys in order to make it a many-to-many relationship between OrganisationLocations, Locations, and Organisation. Then you can use Linq to do a simple join query to get the info you're looking for. For example:
var orgs = (from ol in PfApiContext.OrganisationLocations
inner join o in PfApiContext.Organisation on o.Id equals ol.OrganisationId
inner join l in PfApiContext.Locations on ol.LocationId equals l.Id
where ol.LocationId = 1
select new Organisation()
{
Id = o.Id,
Name = o.Name,
Owner = o.Ower,
etc...
}

EF projections many to many not loading

I have four classes defined as follows:
public class Operator : Base
{
public string Name { get; set; }
public string URL { get; set; }
public ICollection<Address> Addresses { get; set; }
public ICollection<Contact> Contacts { get; set; }
public ICollection<Application.Application> Applications { get; set; }
}
public class Address : Base
{
public String Street{ get; set; }
public int? ParentId { get; set; }
public Operator Parent { get; set; }
public ICollection<Application.Application> Applications { get; set; }
}
public class Contact : Base
{
public string Name { get; set; }
public int? ParentId { get; set; }
public Operator Parent { get; set; }
public ICollection<Application.Application> Applications { get; set; }
}
public class Application : Base
{
[MaxLength(300)]
public String Name { get; set; }
public ICollection<Operator.Operator> Operators { get; set; }
public ICollection<Operator.Address> Addresses { get; set; }
public ICollection<Operator.Contact> Contacts { get; set; }
}
public class Base
{
public int Id { get; set; }
//public int BaseObjectId { get; set; }
TimeZoneInfo _easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
private DateTime _modifiedDate;
public DateTime ModifiedDate
{
get { return this._modifiedDate; }
set
{
this._modifiedDate = DateTime.SpecifyKind(value, DateTimeKind.Unspecified);
this._modifiedDate = TimeZoneInfo.ConvertTimeFromUtc(this._modifiedDate, _easternZone);
}
}
private DateTime _createdDate;
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedDate
{
get { return this._createdDate; }
set
{
this._createdDate = DateTime.SpecifyKind(value, DateTimeKind.Unspecified);
this._createdDate = TimeZoneInfo.ConvertTimeFromUtc(this._createdDate, _easternZone);
}
}
public bool Disabled { get; set; }
}
public class DB : DbContext
{
public DbSet<EF.Complaint.Complaint> Complaints { get; set; }
public DbSet<EF.Category.Category> Categories { get; set; }
public DbSet<EF.Action.Action> Actions { get; set; }
public DbSet<EF.Medium.Medium> Mediums { get; set; }
public DbSet<EF.Priority.Priority> Priorities { get; set; }
public DbSet<EF.Complaint.Comment> Comments { get; set; }
public DB()
{
this.Database.Log = s => { System.Diagnostics.Debug.WriteLine(s); };
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
modelBuilder.Configurations.Add(new ComplaintConfig());
modelBuilder.Configurations.Add(new CategoryConfig());
modelBuilder.Configurations.Add(new ActionConfig());
modelBuilder.Configurations.Add(new MediumConfig());
modelBuilder.Configurations.Add(new PriorityConfig());
modelBuilder.Configurations.Add(new CommentConfig());
base.OnModelCreating(modelBuilder);
}
}
Operator, Contact and Address can all belong to a particular application. So you could have a structure like this:
Operator 1 - belongs to App1 and App2
Child contact 1 - belongs to App1
Child Contact 2 - belongs to App2
Child Address 1 - belongs to App2
I am trying to build a method that returns a list of Operators for a particular Application and includes Addresses and Contacts of that operator that also belong to that Application
Here is a query I have concocted so far
public IEnumerable<Operator> GetForApp(string name)
{
return (Context.Operators.Where(x => x.Applications.Any(y => y.Name == name))
.Select(x => new
{
x,
Addresses = x.Addresses.Where(y => y.Applications.Any(z => z.Name == name)),
Contacts = x.Contacts.Where(y => y.Applications.Any(z => z.Name == name)),
Applications = x.Applications
}).AsEnumerable()
.Select(n => n.x));
}
This works in a sense that the basic members of Operator get loaded as well as Addresses and Contacts get loaded and filtered correctly...What doesn't get loaded is Applications and I can't figure out why. They only difference I see is that Addresses/Contacts and Operator is many-to-one and Applications and Operator is many-to-many.
You must use lazy loading feature or include your related object directly in your query.
public class Operator : Base
{
public string Name { get; set; }
public string URL { get; set; }
public ICollection<Address> Addresses { get; set; }
public ICollection<Contact> Contacts { get; set; }
// by adding virtual keyword EF could generate proxy classes and
// fetch actual objects when are needed.
public virtual ICollection<Application.Application> Applications { get; set; }
}
Or in your query directly include Application:
public IEnumerable<Operator> GetForApp(string name)
{
return (Context.Operators.Where(x => x.Applications.Any(y => y.Name == name))
.Include(x=>x.Applications)
.Select(x => new
{
x,
Addresses = x.Addresses.Where(y => y.Applications.Any(z => z.Name == name)),
Contacts = x.Contacts.Where(y => y.Applications.Any(z => z.Name == name)),
Applications = x.Applications
}).AsEnumerable()
.Select(n => n.x));
}

Mapping Odd relationship in Entity Framework 6

I have a set of 3 models, which is an odd many-to-many-ish relationship.
public class Metric {
public int Id { get; set; }
public string Name { get; set; }
// ...
}
public class ActionPlan {
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
//...
public virtual ICollection<Metric> Metrics { get; set; }
}
public class PlanMetric {
public int PlanId { get; set; }
public int MetricId { get; set; }
public decimal GoalValue { get; set; }
public virtual ActionPlan Plan { get; set; }
public virtual Metric Metric { get; set; }
}
I have the relationships mapped as follows:
public class PlanMetricMapping : EntityTypeConfiguration<PlanMetric> {
public PlanMetricMapping() {
ToTable("PlanMetric");
HasKey(m => new {
m.MetricId,
m.PlanId
});
Property(m => m.GoalValue)
.IsRequired()
.HasPrecision(10, 2);
HasRequired(m => m.Metric)
.WithMany()
.HasForeignKey(m => m.MetricId);
HasRequired(m => m.Plan)
.WithMany()
.HasForeignKey(m => m.PlanId);
}
}
public class ActionPlanMapping : EntityTypeConfiguration<ActionPlan> {
public ActionPlanMapping() {
ToTable("ActionPlan");
HasKey(m => m.Id);
// ...
//HasMany(m=>m.Metrics) // how do I get to this data?
}
}
The problem is
1) EF is creating an ActionPlan_Id field in my Metric table, and I'm not sure why.
2) I don't know how to set up my mapping to be able to navigation from a Plan to it's Metrics.
EF is creating an ActionPlan_Id field because you have
public virtual ICollection<Metric> Metrics { get; set; }
in your ActionPlan definition, which EF interprets as a one-to-many relationship between ActionPlan and Metric. It seems like you want
public virtual ICollection<PlanMetric> PlanMetrics { get; set; }
instead.
Then, in order to get to an ActionPlan's metrics, you could go through that collection, perhaps through a Select().

"The referential relationship will result in a cyclical reference that is not allowed."

This is scenario one, which works fine:
public class Domain
{
public int DomainId { get; set; }
[InverseProperty("Domain")]
public virtual ICollection<Person> Persons { get; set; }
[InverseProperty("Domain")]
public virtual ICollection<Group> Groups { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public virtual Domain Domain { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public virtual Domain Domain { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
And this is scenario two, which fails.
public class Domain
{
// Same as scenario 1...
}
public class Person
{
public int PersonId { get; set; }
public int DomainId { get; set; } // <--- new
[ForeignKey("DomainId")] // <--- new
public virtual Domain Domain { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public int DomainId { get; set; } // <--- new
[ForeignKey("DomainId")] // <--- new
public virtual Domain Domain { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
The error message in scenario 2 is the following:
The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = FK_dbo.GroupMembers_dbo.Persons_MemberId ]
Both scenarios have this mapping (many to many), inside OnModelCreating's method.
modelBuilder.Entity<Group>()
.HasMany(group => group.Members)
.WithMany(member => member.Groups)
.Map(m =>
{
m.ToTable("GroupMembers");
m.MapLeftKey("GroupId");
m.MapRightKey("MemberId");
});
What am I doing wrong?! What I want to achieve is perfectly reasonable
Either you're using a different version of EF to me (I'm using 5), or you're not including some code which is causing your problem. I created a context containing the code you've provided, and the only error I got was an error about multiple cascade paths (Domains being deleted via Persons or via Groups). I removed Cascade delete on one of the relationships, and it works fine.
Here's my entire context class which works with no errors:
public class TestContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Domain> Domains { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>()
.HasMany(group => group.Members)
.WithMany(member => member.Groups)
.Map(m =>
{
m.ToTable("GroupMembers");
m.MapLeftKey("GroupId");
m.MapRightKey("MemberId");
});
modelBuilder.Entity<Domain>()
.HasMany(d => d.Groups).WithRequired(g => g.Domain)
.WillCascadeOnDelete(false);
}
}