I have these classes:
public class Document
{
public Document()
{
Descriptions = new List<Descriptions>();
}
public virtual int Id { get; set; }
public virtual IList<DocumentDescription> Descriptions { get; set; }
}
public class DocumentDescription
{
public virtual int DocumentId { get; set; }
public virtual int LanguageId { get; set; }
}
and mappings:
public DocumentMap()
{
Id(x => x.Id);
HasMany(x => x.Descriptions).KeyColumn("DocumentId");
}
public DocumentDescriptionMap()
{
CompositeId()
.KeyProperty(x => x.DocumentId)
.KeyProperty(x => x.LanguageId);
}
my query is:
var query = Session.QueryOver<Document>().Where(x => x.Id.IsIn(documentIds)).List();
I need a query over solution to restrict DocumentDescriptions by few languages, which I will get run-time. I don't want to get all DocumentDescriptions for one Document (only few). Is it possible to set filter/limitation for a child collection?
So, I find out how to add additional join clause to my query:
DocumentDescription dd = null;
ICriterion criterion = Restrictions.On<DocumentDescription>(x => x.LanguageId).IsIn(languageIds.ToArray());
var query = Session.QueryOver<Document>().Where(x => x.Id.IsIn(documentIds));
query.Left.JoinQueryOver(x => x.Descriptions, () => dd, criterion);
SQL:
SELECT * FROM tDocument
LEFT OUTER JOIN tDocumentDescription ON tDocumentDescription.DocumentId = tDocument.Id AND tDocumentDescription.LanguageId IN (#languageIds)
WHERE tDocument.Id IN (#documentIds)
Related
I'm trying to migrate from using TPT (a table per subclass) to TPH (One table for all subclasses).
This is my starting point for TPT:
entities:
[Serializable]
public abstract class VeganItem<TVeganItemEstablishment> : DomainEntity<int>
{
[Required]
public string Name { get; set; }
[Required]
public string CompanyName { get; set; }
[Required]
public string Description { get; set; }
public string Image { get; set; }
[Required]
public int IsNotVeganCount { get; set; } = 0;
[Required]
public int IsVeganCount { get; set; } = 0;
[Required]
public int RatingsCount { get; set; } = 0;
[Required]
public int Rating { get; set; }
[Required]
public List<Option> Tags { get; set; }
[PropertyName("veganItemEstablishments", Ignore = true)]
public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}
[ElasticsearchType(RelationName = "groceryitem", IdProperty = "Id")]
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}
[ElasticsearchType(RelationName = "menuitem", IdProperty = "Id")]
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}
OnModelCreating:
modelBuilder.Entity<GroceryItem>(gi =>
{
gi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
gi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
gi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
gi.HasKey(e => e.Id);
gi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
gi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
gi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
modelBuilder.Entity<MenuItem>(mi =>
{
mi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
mi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
mi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
mi.HasKey(e => e.Id);
mi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
mi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
mi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
public DbSet<GroceryItem> GroceryItems { get; set; }
public DbSet<MenuItem> MenuItems { get; set; }
So I want just one table called VeganItems. What is it that is actually causing there to be 2 tables - GroceryItems and MenuItems? As I have tried a couple of things and they didn't work. EF Core uses TPH by default so I'm unsure why it is using TPT. I'm wondering if it is because my base entity is a generic type.
EF Core uses TPH by default so I'm unsure why it is using TPT. I'm wondering if it is because my base entity is a generic type.
Generic type is one of the problems. The other is that it is not a base entity, but just base class. In order to be considered entity, there must be either DbSet<T>, or ModelBuilder.Entity<T>() call, or applied IEntityTypeConfiguration<T>, or some discorevered entity navigation property (either collection or reference) referring to it - see Including types in the model.
You don't have any of these, so the model is not even TPT (common table containing common properties + single table per each derived entity containing specific properties), but some sort of a TPC (Table-Per-Class, not currently supported by EF Core), where there is no common table - all the data is in concrete tables for each derived entity.
So, in order to use TPT you need to fix both issues. Generic class cannot be used as entity type because its type is not enough to identify it (each generic instantiation is different type, typeof(Foo<Bar>) != typeof(Foo<Baz>)).
Start by extracting the non generic part which will serve as base entity (removed non EF Core annotations for clarity):
// Base class (code/data reuse only, not an entity)
public abstract class DomainEntity<TId>
{
public TId Id { get; set; }
}
// Base entity
public abstract class VeganItem : DomainEntity<int>
{
[Required]
public string Name { get; set; }
[Required]
public string CompanyName { get; set; }
[Required]
public string Description { get; set; }
public string Image { get; set; }
[Required]
public int IsNotVeganCount { get; set; } = 0;
[Required]
public int IsVeganCount { get; set; } = 0;
[Required]
public int RatingsCount { get; set; } = 0;
[Required]
public int Rating { get; set; }
[Required]
public List<Option> Tags { get; set; }
}
// Base class (code/data reuse only, not an entity)
public abstract class VeganItem<TVeganItemEstablishment> : VeganItem
{
public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}
// Derived entity
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}
// Derived entity
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}
Then (optionally) add DbSet for it
public DbSet<VeganItem> VeganItems { get; set; }
Finally (mandatory) move the fluent configuration of the base entity members to its own block, and keep in derived only the configuration of the specific members of the derive type:
// Configure base entity
modelBuilder.Entity<VeganItem>(vi =>
{
vi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
vi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
vi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
vi.HasKey(e => e.Id);
vi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
vi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
vi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
// Configure derived entities
modelBuilder.Entity<GroceryItem>(gi =>
{
});
modelBuilder.Entity<MenuItem>(mi =>
{
});
I have the following parent entity Department which contains a collection of child entities Sections
public class Department
{
private Iesi.Collections.Generic.ISet<Section> _sections;
public Department()
{
_sections = new HashedSet<Section>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ICollection<Section> Sections
{
get { return _sections; }
}
public virtual int Version { get; set; }
}
public partial class Section
{
public Section()
{
_employees = new HashedSet<Employee>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual Department Department { get; protected set; }
public virtual int Version { get; set; }
}
I would like to transform (flatten) it to the following DTO
public class SectionViewModel
{
public string DepartmentName { get; set; }
public string SectionName { get; set; }
}
Using the following code.
SectionModel sectionModel = null;
Section sections = null;
var result = _session.QueryOver<Department>().Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property<Department>(d => sections.Department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property<Department>(s => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I am however getting the following exception: could not resolve property: Department.Name of: Domain.Section
I have even tried the following LINQ expression
var result = (from d in _session.Query<Department>()
join s in _session.Query<Section>()
on d.Id equals s.Department.Id into ds
from sm in ds.DefaultIfEmpty()
select new SectionModel
{
DepartmentName = d.Name,
SectionName = sm.Name ?? null
}).ToList();
Mappings
public class DepartmentMap : ClassMapping<Department>
{
public DepartmentMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
Set(x => x.Sections,
m =>
{
m.Access(Accessor.Field);
m.Inverse(true);
m.BatchSize(20);
m.Key(k => { k.Column("DeptId"); k.NotNullable(true); });
m.Table("Section");
m.Cascade( Cascade.All | Cascade.DeleteOrphans);
},
ce => ce.OneToMany());
}
}
public class SectionMap : ClassMapping<Section>
{
public SectionMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
ManyToOne(x => x.Department,
m =>
{
m.Column("DeptId");
m.NotNullable(true);
});
}
}
But this throws a method or operation is not implemented.
Seeking guidance on what I am doing wrong or missing.
NHibernate doesn't know how to access a child property's child through the parent entity. A useful thing to remember about QueryOver is that it gets translated directly into SQL. You couldn't write the following SQL:
select [Section].[Department].[Name]
right? Therefore you can't do the same thing in QueryOver. I would create an alias for the Department entity you start on and use that in your projection list:
Department department;
Section sections;
var result = _session.QueryOver<Department>(() => department)
.Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property(() => department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property(() => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I noticed in your comment you'd like an order by clause. Let me know if you need help with that and I can probably come up with it.
Hope that helps!
This may be now fixed in 3.3.3. Look for
New Feature
[NH-2986] - Add ability to include collections into projections
Not sure but if this is your problem specifically but if you are not using 3.3.3 then upgrade and check it out.
Aslo check out the JIRA
Have you tried a linq query like
from d in Departments
from s in d.Sections
select new SectionModel
{
DepartmentName = d.Name,
SectionName = s == null ? String.Empty : s.Name
}
I'm trying to query on a simple data structure in nhibernate and MSSQL
dbo.Projects : Id(int, not null)
dbo.Finances : Id(int, not null), ProjectId(int,not null), foreign key references to dbo.projects
I want to get all the records in projects table that are not present in finances table where the finances table has a foreign key reference ProjectId.
I am migrating to (Fluent) Nhibernate 3 from EntityFramework?
//So far I have got here:
public IQueryable<ProjectModel> GetProjectsNotPresentInFinance()
{
var factory = Fluently.Configure()
.Database(MsSqlConfiguration
.MsSql2008
.ConnectionString(m_connectionString))
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<ProjectMap>()
).BuildSessionFactory();
using (var session = factory.OpenSession())
{
var allprojects = session.QueryOver<ProjectModel>();
var projectsToReturn = allprojects.List<ProjectModel>().AsQueryable();
//--- Something like : all the records not in finances table ---------
// .Where( proj => !db.Finances.Where(fin => fin.ProjectId == proj.Id).Any())
// .Select(project => new ProjectModel
// {
// Id=project.Id,
// ProjectName = project.ProjectName,
// });
return projectsToReturn;
}
}
public class FinanceModel
{
public virtual int Id { get; set; }
public virtual int ProjectId { get; set; }
}
public class ProjectModel
{
public virtual int Id { get; set; }
public virtual string ProjectName { get; set; }
}
public class ProjectMap:ClassMap<ProjectModel>
{
public ProjectMap() {
Table("Projects");
Id(x => x.Id);
Map(x => x.ProjectName);
}
}
public class FinanceMap : ClassMap<FinanceModel>
{
public FinanceMap()
{
Table("Finances");
Id(x => x.Id);
References(x => x.ProjectModel);
}
}
//-------------------------------------------------------
//This is an Equivalent working code Using EntityFramework :
public IQueryable<ProjectModel> GetProjectsNotPresentInFinance() {
IQueryable<ProjectModel> projectList = db.Projects
.Where( proj => !db.Finances.Where(fin => fin.ProjectId == proj.Id).Any())
.Select(project => new ProjectModel
{
Id=project.Id,
ProjectName = project.ProjectName,
});
return projectList;
}
//-------------------------------------------------------
On second thought, you may try this without changing anything to your mapping, using a subquery :
var notOrphanProjectIdsSubquery = QueryOver.Of<FinanceModel>()
.Select(x => x.ProjectId);
var orphanProjects = session.QueryOver<ProjectModel>()
.WithSubquery
.WhereProperty(x=>x.Id)
.NotIn(notOrphanProjectIdsSubquery)
.List();
----------------------- Initial answer
Assuming you have a mapped Finances Property in your Project class, and according to https://stackoverflow.com/a/14980450/1236044, it should be something like :
var orphanProjects = session.QueryOver<ProjectModel>()
.WhereRestrictionOn(x => x.Finances).IsEmpty()
.List();
I must confess I am not proficient with FluentNH. I guess the classes and mappings should be something like this, hoping I'm not setting you on the wrong track...
public class FinanceModel
{
public virtual int Id { get; set; }
public virtual int ProjectId { get; set; }
public virtual ProjectModel Project{get;set;}
}
public class ProjectModel
{
public virtual int Id { get; set; }
public virtual string ProjectName { get; set; }
public virtual IList<FinanceModel> Finances { get; set; }
}
public class ProjectMap:ClassMap<ProjectModel>
{
public ProjectMap() {
Table("Projects");
Id(x => x.Id);
Map(x => x.ProjectName);
HasMany(x => x.Finances);
}
}
public class FinanceMap : ClassMap<FinanceModel>
{
public FinanceMap()
{
Table("Finances");
Id(x => x.Id);
References(x => x.Project);
}
}
I'm trying to figure out how to use CompositeId to map another class. Here's a test case:
The tables:
TestParent:
TestParentId (PK)
FavoriteColor
TestChild:
TestParentId (PK)
ChildName (PK)
Age
The classes in C#:
public class TestParent
{
public TestParent()
{
TestChildList = new List<TestChild>();
}
public virtual int TestParentId { get; set; }
public virtual string FavoriteColor { get; set; }
public virtual IList<TestChild> TestChildList { get; set; }
}
public class TestChild
{
public virtual TestParent Parent { get; set; }
public virtual string ChildName { get; set; }
public virtual int Age { get; set; }
public override int GetHashCode()
{
return Parent.GetHashCode() ^ ChildName.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is TestChild)
{
var toCompare = obj as TestChild;
return this.GetHashCode() != toCompare.GetHashCode();
}
return false;
}
}
The Fluent NHibernate maps:
public class TestParentMap : ClassMap<TestParent>
{
public TestParentMap()
{
Table("TestParent");
Id(x => x.TestParentId).Column("TestParentId").GeneratedBy.Native();
Map(x => x.FavoriteColor);
HasMany(x => x.TestChildList).KeyColumn("TestParentId").Inverse().Cascade.None();
}
}
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId"); /** breaks insert **/
}
}
When I try to add a new record, I get this error:
System.ArgumentOutOfRangeException :
Index was out of range. Must be
non-negative and less than the size of
the collection. Parameter name: index
I know this error is due to the TestParentId column being mapped in the CompositeId and References calls. However, removing the References call causes another error when querying TestChild based on the TestParentId.
Here's the code that does the queries:
var session = _sessionBuilder.GetSession();
using (var tx = session.BeginTransaction())
{
// create parent
var p = new TestParent() { FavoriteColor = "Red" };
session.Save(p);
// creat child
var c = new TestChild()
{
ChildName = "First child",
Parent = p,
Age = 4
};
session.Save(c); // breaks with References call in TestChildMap
tx.Commit();
}
// breaks without the References call in TestChildMap
var children = _sessionBuilder.GetSession().CreateCriteria<TestChild>()
.CreateAlias("Parent", "p")
.Add(Restrictions.Eq("p.TestParentId", 1))
.List<TestChild>();
Any ideas on how to create a composite key for this scenario?
I found a better solution that will allow querying and inserting. The key is updating the map for TestChild to not insert records. The new map is:
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId")
.Not.Insert(); // will avoid "Index was out of range" error on insert
}
}
Any reason you can't modify your query to just be
_sessionBuilder.GetSession().CreateCriteria<TestChild>()
.Add(Restrictions.Eq("Parent.TestParentId", 1))
.List<TestChild>()
Then get rid of the reference?
i'm trying to remove an item from a one to many list and have it persist in the database. Here are the entities i have defined:
public class SpecialOffer
{
public virtual int SpecialOfferID { get; set; }
public virtual string Title { get; set; }
public virtual IList<SpecialOfferType> Types { get; private set; }
public SpecialOffer()
{
Types = new List<SpecialOfferType>();
}
}
public class SpecialOfferType
{
public virtual SpecialOffer SpecialOffer { get; set; }
public virtual Type Type { get; set; }
public virtual int MinDaysRemaining { get; set; }
#region composite id requirements
public override bool Equals(object obj)
{
if (obj == null || !(obj is SpecialOfferType))
return false;
var t = (SpecialOfferType)obj;
return SpecialOffer.SpecialOfferID == t.SpecialOffer.SpecialOfferID && Type.TypeID == t.Type.TypeID;
}
public override int GetHashCode()
{
return (SpecialOffer.SpecialOfferID + "|" + Type.TypeID).GetHashCode();
}
#endregion
}
public class Type
{
public virtual int TypeID { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
}
With the following fluent mappings:
public class SpecialOfferMap : ClassMap<SpecialOffer>
{
public SpecialOfferMap()
{
Table("SpecialOffers");
Id(x => x.SpecialOfferID);
Map(x => x.Title);
HasMany(x => x.Types)
.KeyColumn("SpecialOfferID")
.Inverse()
.Cascade.All();
}
}
public class SpecialOfferTypeMap : ClassMap<SpecialOfferType>
{
public SpecialOfferTypeMap()
{
Table("SpecialOfferTypes");
CompositeId()
.KeyReference(x => x.SpecialOffer, "SpecialOfferID")
.KeyReference(x => x.Type, "TypeID");
Map(x => x.MinDaysRemaining);
}
}
public class TypeMap : ClassMap<Type>
{
public TypeMap()
{
Table("Types");
Id(x => x.TypeID);
Map(x => x.Title);
Map(x => x.Price);
}
}
The problem i have is that if i remove an item from the SpecialOffer.Types collection it successfully removes it from the list but when i try to save the session the change is not persisted in the database. I'm assuming this is something to do with the composite id on the join table since i have been able to do this successfully in the past with a standard id.
I'd appreciate it if someone could show me what i'm doing wrong. Thanks
I think you have to 1) Change the cascade setting on SpecialOffer.Types to Cascade.AllDeleteOrphan() and 2) set SpecialOfferType.SpecialOffer = null when you remove it from the collection. Since the collection is the inverse side of the relationship, the many-to-one reference to SpecialOffer on SpecialOfferType has to be set to null to make it an orphan, then Cascade.AllDeleteOrphan will cause it to be deleted.