NHibernate Left Join - c#

A very beginner question:
I have two classes, Review and ReviewSentences:
public class Review
{
public virtual int recordId { get; set; }
public virtual string reviewerId { get; set; }
public virtual string reviewerName { get; set; }
public virtual string country { get; set; }
public virtual string zipCode { get; set; }
public virtual string reviewProduct { get; set; }
public virtual string reviewText { get; set; }
public virtual string reviewTextLanguage { get; set; }
public virtual double sentimentScore { get; set; }
public virtual bool isScoreRefined { get; set; }
}
pulic class ReviewSentences
{
public virtual int recordId { get; set; }
public virtual int reviewId { get; set; }
public virtual int sentenceId { get; set; }
public virtual string sentence { get; set; }
public virtual double sentimentScore { get; set; }
}
The property ReviewSentences.reviewId is a foreign key referring to Review.recordId. One review can have many sentences (Review:ReviewSentences is 1:Many)
I have been trying for a long time now but unable to replicate the following query in terms of NHibernate with session.CreateCriteria:
select * from Reviews r
left join
ReviewSentences rs
on
r.RecordId = rs.ReviewId
where rs.ReviewId is null
The query gives me all reviews from the Review table that do not have any records in the ReviewSentences table.

It is a matter of mapping you should include an array of ReviewSentences in your Review class and map it correctly.
public class Review
{
public virtual int recordId { get; set; }
public virtual string reviewerId { get; set; }
public virtual string reviewerName { get; set; }
public virtual string country { get; set; }
public virtual string zipCode { get; set; }
public virtual string reviewProduct { get; set; }
public virtual string reviewText { get; set; }
public virtual string reviewTextLanguage { get; set; }
public virtual double sentimentScore { get; set; }
public virtual bool isScoreRefined { get; set; }
public virtual IList<ReviewSentences> sentences { get; set; }
}
pulic class ReviewSentences
{
public virtual int recordId { get; set; }
public virtual int reviewId { get; set; }
public virtual int sentenceId { get; set; }
public virtual string sentence { get; set; }
public virtual double sentimentScore { get; set; }
}
then in the mapping you should refer sentences as a reference.
but you did not said which kind of mapping your using (Fluent, conformist, etc.)

Fixed the mapping in hbm.xml files and got the required results using:
var reviews= session.CreateCriteria<Review>("r")
.CreateCriteria("r.sentences", JoinType.LeftOuterJoin)
.Add(Restrictions.IsNull("recordId"))
.List<Review>();

Related

Entity Framework creates a new record per foreign key in another table while seeding

I'm trying to seed a database using EF.
I have a table that holds products (phones) and a Category table that differentiates between different types of products.
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public DateTimeOffset? CreationDate { get; set; }
public DateTimeOffset? UpdateDate { get; set; }
public virtual List<IProduct> Products{ get; set; }
public Category()
{
this.CreationDate = DateTimeOffset.UtcNow;
}
}
public interface IProduct
{
int ProductId { get; set; }
string Brand { get; set; }
string Model { get; set; }
decimal? Price { get; set; }
string Image { get; set; }
int CategoryId { get; set; }
Category Category { get; set; }
}
public class Phone: IProduct
{
public int ProductId { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public string network_technology { get; set; }
public string bands_2G { get;set; }
public string bands_3G{ get; set; }
public string bands_4G { get; set; }
public string network_speed { get; set; }
public string GPRS { get; set; }
public string EDGE { get; set; }
public string announced { get; set; }
public string status { get; set; }
public string dimentions { get; set; }
public float? weight_g { get; set; }
public float? weight_oz { get; set; }
public string SIM { get; set; }
public string display_type { get; set; }
public string display_resolution { get; set; }
public string display_size { get; set; }
public string OS { get; set; }
public string CPU { get; set; }
public string Chipset { get; set; }
public string GPU { get; set; }
public string memory_card { get; set; }
public string internal_memory { get; set; }
public string RAM { get; set; }
public string primary_camera { get; set; }
public string secondary_camera { get; set; }
public string loud_speaker { get; set; }
public string audio_jack { get; set; }
public string WLAN { get; set; }
public string bluetooth { get; set; }
public string GPS { get; set; }
public string NFC { get; set; }
public string radio { get; set; }
public string USB { get; set; }
public string sensors { get; set; }
public string battery { get; set; }
public string colors { get; set; }
public decimal? Price { get; set; }
public string Image { get; set; }
}
I don't know what am I doing wrong but after I update the database from nuget console, a new Category record is created per seeded product(phone). That's exactly the opposite of what I want. I want all the phones to have one categoryId that refers to Phones category. does anyone know what's wrong?
Entity Type Configurations (fluent api):
public class CategoryConfiguration : EntityTypeConfiguration<Category>
{
public CategoryConfiguration()
{
ToTable("Categories");
HasKey(m => m.CategoryId);
}
}
public class PhoneConfiguration : EntityTypeConfiguration<Phone>
{
public PhoneConfiguration()
{
ToTable("Phones");
HasKey(m => m.ProductId);
}
}
Seed method:
protected override void Seed(BestPhone.Data.BestPhoneDbContext context)
{
context.Categories.AddOrUpdate(new Category(){Name = "Phones", CategoryId = 1});
...
//getting records from a csv file and holding them in an array.
var records = csvReader.GetRecords<Phone>().ToArray();
foreach (var record in records)
{
record.CategoryId = 1;
}
context.Phones.AddRange(records);
context.SaveChanges();
}
}
Try to add the next method on your CategoryConfiguration class:
public void Configure(EntityTypeBuilder<Category> builder)
{
builder
.HasMany(s => s.Products)
.WithOne(t => t.Category)
.HasForeignKey(t => t.CategoryId);
}
I'm not sure but it seems a system does not take into account your foreign key during seeding.

Entity Framework Unable to determine principal end of relationship. Multiple added entities may have same primary key

I'm having some issues with EF.
Migrations go fine, but when I try to run update-database I get the error :
Unable to determine the principal end of the 'LeagueInsight.Models.Image_Passive' relationship. Multiple added entities may have the same primary key.
Here are my models and config:
Passive.cs
public class Passive
{
public long Id { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public string SanitizedDescription { get; set; }
// Navigation
public virtual Image Image { get; set; }
public virtual Champion Champion { get; set; }
}
Image.cs
public class Image
{
public long Id { get; set; }
public string Full { get; set; }
public string Group { get; set; }
public int H { get; set; }
public string Sprite { get; set; }
public int W { get; set; }
public int X { get; set; }
public int Y { get; set; }
// Navigation
public virtual Champion Champion { get; set; }
public virtual ChampionSpell ChampionSpell { get; set; }
public virtual Passive Passive { get; set; }
}
DBContext Configuration Partial:
modelBuilder.Entity<Passive>()
.HasRequired(p => p.Champion)
.WithRequiredPrincipal(c => c.Passive);
modelBuilder.Entity<Info>()
.HasRequired(i => i.Champion)
.WithRequiredPrincipal(c => c.Info);
modelBuilder.Entity<Image>()
.HasOptional(i => i.Champion)
.WithRequired(c => c.Image);
modelBuilder.Entity<Image>()
.HasOptional(i => i.Passive)
.WithRequired(p => p.Image);
base.OnModelCreating(modelBuilder);
It is just supposed to be a 1-1 relationship between the two, an I can't figure out why there would be multiple entities with the same Id here.
Edit: I was asked for the champion class:
public class Champion
{
public int Id { get; set; }
public string Blurb { get; set; }
public string Key { get; set; }
public string Lore { get; set; }
public string Name { get; set; }
public string Partype { get; set; }
public string Title { get; set; }
// Navigation
[InverseProperty("Ally")]
public virtual ICollection<Tip> Allytips { get; set; }
[InverseProperty("Enemy")]
public virtual ICollection<Tip> Enemytips { get; set; }
public virtual Image Image { get; set; }
public virtual Info Info { get; set; }
public virtual Passive Passive { get; set; }
public virtual ICollection<Recommended> Recommended { get; set; }
public virtual ICollection<Skin> Skins { get; set; }
public virtual Stats Stats { get; set; }
public virtual ICollection<ChampionSpell> Spells { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}

Unrecognised method call with NHibernate and Linq when only date part required

I have the following class defined
public class IRM_TEMP_Appointment_archive
{
public virtual String VisitNumber { get; set; }
public virtual String WardCode { get; set; }
public virtual String ClinicCode { get; set; }
public virtual String DoctorCode { get; set; }
public virtual String AppointmentStatusCode { get; set; }
public virtual String PatientNumber { get; set; }
public virtual String EpisodeNumber { get; set; }
public virtual String ConsultantCode { get; set; }
public virtual String ReferrerCode { get; set; }
public virtual String MainSpecialtyCode { get; set; }
public virtual String MessageType { get; set; }
public virtual String EpisodeStatusCode { get; set; }
public virtual String ParentEpisodeType { get; set; }
public virtual String AdmissionType { get; set; }
public virtual String AdmitReason { get; set; }
public virtual String BuildingCode { get; set; }
public virtual String BedNumber { get; set; }
public virtual String CategoryCode { get; set; }
public virtual String ConsultantName { get; set; }
public virtual String DischargeDestination { get; set; }
public virtual String DischargeMethod { get; set; }
public virtual String FacilityCode { get; set; }
public virtual String ReferralReason { get; set; }
public virtual String ReferrerAssigningAuthority { get; set; }
public virtual String ReferrerName { get; set; }
public virtual String currentStatus { get; set; }
public virtual String AppointmentTypeText { get; set; }
public virtual String TCILocation { get; set; }
public virtual Boolean analysed { get; set; }
public virtual DateTime AdmitDateTime { get; set; }
public virtual DateTime StopDateTime { get; set; }
public virtual DateTime ScheduledDateTime { get; set; }
public virtual DateTime AppointmentDateTime { get; set; }
public virtual DateTime DischargeDateTime { get; set; }
public virtual DateTime timeOfInsert { get; set; }
public virtual DateTime timeOfAnalyse { get; set; }
public virtual DateTime TCIDate { get; set; }
public virtual int TempAppointmentKey { get; set; }
I need to find any appointments a patient has on a given date, for this I need to extract just the date
using (ISessionFactory factory = CreateSessionFactory())
{
using (var session = factory.OpenSession())
{
var query = session.QueryOver<IRM_TEMP_Appointment_archive>()
.Where(a => (a.PatientNumber == MRN) && (a.AppointmentDateTime.ToShortDateString() == DateOfAppointment.ToShortDateString()))
.List()
.ToList<IRM_TEMP_Appointment_archive>();
if (query != null)
{
results = query;
}
}
}
However when this code executes I am getting an error
Unrecognised method call in epression a.AppointmentDateTime.ToShortDateString()
I presume that is because it is unable to determine the SQL for ToShortDateString - so how should I code this requirement. Database is SQL Server.
I assume you are looking for something like this (this is using the Linq provider and not QueryOver):
[Test]
public void CanQueryByDate()
{
var x = (from o in db.Orders
where o.OrderDate.Value.Date == new DateTime(1998, 02, 26)
select o).ToList();
Assert.AreEqual(6, x.Count());
}
The unit tests are generally a good place to find basic examples. See here for the nhibernate unit tests for linq that are specific to date time examples:
https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate.Test/Linq/DateTimeTests.cs
The query is split into two, with the date check done in the second half on the result of querying on the patient identifier. So the if statement at the end becomes;
if (query != null)
{
results = (from a in query where a.AppointmentDateTime.Date == DateOfAppointment.Date select a).ToList<IRM_TEMP_Appointment_archive>();
}
Because the later query is not being converted into T-SQL then there is no concern on their being no equivalent of the Date function.

Linq where with nested .Any() in string variable

These are my entities:
public class Subscription : BaseItem
{
public virtual DateTime DateStart { get; set; }
public virtual DateTime? DateEnd { get; set; }
public virtual int Status { get; set; }
public virtual Account Account { get; set; }
public virtual Theater Theater { get; set; }
public virtual Agent Agent { get; set; }
}
public class Account : BaseItem
{
public virtual string LegalName { get; set; }
public virtual string FirstName { get; set; }
public virtual string UrlName { get; set; }
public virtual string Address1 { get; set; }
public virtual string Address2 { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string ZipCode { get; set; }
public virtual string Country { get; set; }
public virtual string Tel { get; set; }
public virtual string Tel2 { get; set; }
public virtual string Fax { get; set; }
public virtual string Mobile { get; set; }
public virtual string Email { get; set; }
public virtual string Website { get; set; }
public virtual int DefaultVatRate { get; set; }
public virtual bool Bankrupt { get; set; }
public virtual string ExternalId { get; set; }
public virtual bool DoNotContact { get; set; }
public virtual bool NotInterested { get; set; }
public virtual Group Group { get; set; }
public virtual IList<Header> Headers { get; set; }
public virtual IList<Note> Notes { get; set; }
public virtual IList<Order> Orders { get; set; }
public virtual IList<Subscription> Subscriptions { get; set; }
}
public class Order : BaseItem
{
public virtual int Number { get; set; }
public virtual DateTime Date { get; set; }
public virtual string Description { get; set; }
public virtual double Amount { get; set; }
public virtual string Attachment { get; set; }
public virtual string AttachmentFilename { get; set; }
public virtual string AttachmentMimetype { get; set; }
public virtual bool? PaidToTheater { get; set; }
public virtual DateTime? DatePaidToTheater { get; set; }
public virtual bool? CashinByTheater { get; set; }
public virtual Account Account { get; set; }
public virtual Theater Theater { get; set; }
public virtual Agent Agent { get; set; }
public virtual IList<Invoice> Invoices { get; set; }
public virtual IList<OrdersAttachment> OrdersAttachments { get; set; }
public virtual IList<OrdersDueDate> OrdersDueDates { get; set; }
public virtual Header Header { get; set; }
}
public class Invoice : BaseItem
{
public virtual int Number { get; set; }
public virtual DateTime Date { get; set; }
public virtual double Amount { get; set; }
public virtual double VatRate { get; set; }
public virtual bool IsDisabled { get; set; }
public virtual bool IsSendMail { get; set; }
public virtual Order Order { get; set; }
public virtual IList<InvoicesDueDate> InvoicesDueDates { get; set; }
public virtual IList<InvoicesPayment> InvoicesPayments { get; set; }
}
I have a method in my program that build a "query" in a string variable.
private string GenerateQuery(FilterSubscription filterSubscription)
{
if (filterSubscription.FilterByOrder)
return "Account.Orders.Any()";
if (filterSubscription.FilterByInvoice)
return "Account.Orders.Any(Invoices.Any())"; //here is my problem
}
This is the call to the method
string query = GenerateQuery(filterSubscription)
var count = Session.Linq<Subscription>().Where(query).Count();
If I need to extract all accounts that have at least one Order is all OK.
But if I need to extract all accounts that have at least one Invoice I don't know how.
If I wrote
var count = Session.Linq<Subscription>().Where(s=>s.Account.Orders.Any(o=>o.Invoices.Any())).Count();
it works but if I use the string variable it doesn't.
It looks like you should return a delegate from GenerateQuery instead - something like:
private Expression<Func<Subscription, bool>> GenerateQuery(FilterSubscription filterSubscription)
{
if (filterSubscription.FilterByOrder)
return s => s.Account.Orders.Any();
if (filterSubscription.FilterByInvoice)
return s => s.Account.Orders.Any(o => o.Invoices.Any());
}
Then just update the query variable to be implicitly typed:
var query = GenerateQuery(filterSubscription);

Code First Entity Framework disallows access to table

I am trying to use code first to generate a database for an asp.net mvc application. the OrderItem class does get generated as OrderItems table in the database but I end up without having any access to it. what can I do to allow the following for example: db.OrderItems.Find(id);
the model is as follows:
namespace CustomerOrders.Models{
public class Customer
{
public virtual int CustomerID { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Company { get; set; }
public virtual string Email { get; set; }
public virtual string EmailCheck { get; set; }
}
public class Order
{
public virtual int OrderID { get; set; }
public virtual int CustomerID { get; set; }
public virtual DateTime OrderDate { get; set; }
public virtual double OrderTotal { get; set; }
public virtual double Tax { get; set; }
public virtual Customer Customer { get; set; }
public virtual List<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public virtual int OrderItemID { get; set; }
public virtual int OrderID { get; set; }
public virtual int ProductID { get; set; }
public virtual double PricePerItem { get; set; }
public virtual double Quantity { get; set; }
public virtual Product Product { get; set; }
public Order Order { get; set; }
}
public class Product
{
public virtual int ProductID { get; set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual double Price { get; set; }
}
}
You probably haven't got the declaration in your dbContext so Open up the dbContext and add this:
public DbSet<OrderItem> OrderItems { get; set; }
you should end up with this:
public class CustomerOrdersDB : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
}

Categories