TreeMenu with self Referencing and nested Menu - c#

I have a need to create a TreeMenu using NHibernate.
Here is what i cameup with:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.AppId);
Map(x => x.Title);
Map(x => x.MaxDepth).Default("3");
Map(x => x.IsActive);
References(x => x.Parent).LazyLoad().Column("ParentId");
HasMany(x => x.ChildrenNodes)
.LazyLoad()
.KeyColumn("ParentId")
.Cascade.All();
Table("Category");
}
}
public class Category : Node
{
public virtual string AppId { get; set; }
public override string Title { get; set; }
public override int MaxDepth { get; set; }
public virtual Category Parent { get; set; }
public virtual IList<Category> ChildrenNodes { get; set; }
public virtual bool IsActive { get; set; }
}
public abstract class Node
{
public virtual long Id { get; set; }
public abstract string Title { get; set; }
public abstract int MaxDepth { get; set; }
}
Here is my testing code:
[Test]
public void CreateTableInDb()
{
using (var db = new FileDatabase(DbFileLocation, true))
{
var categoryMenu = new Category
{
AppId = "1",
MaxDepth = 3,
IsActive = true,
Title = "Services"
};
db.Create(categoryMenu);
categoryMenu.ChildrenNodes = new List<Category>
{
new Category
{
Title = "SubService-Level1",
Parent = categoryMenu,
}
};
db.Update(categoryMenu);
}
}
When i look into the table it's creating i have only one row,
with empty ParentId column.
How can i fix it and what i'm doing wrong?

first you'll missing .Inverse() on the hasmany. As it stands it will save and update the parentId twice
all collections should be readonly. NHibernate will replace them with tracking and lazyloading collections which you are throwing away. Change the code to
public class Category : Node
{
public Category()
{
// best practice so that nobody has to null check the collection
ChildrenNodes = new List<Category>();
}
public virtual IList<Category> ChildrenNodes { get; private set; }
public void Add(Category subcategory)
{
subcategory.Parent = this;
ChildrenNodes.Add(subcategory);
}
}
// in testcode
categoryMenu.Add(new Category { Title = "SubService-Level1" });

Related

EF Core include list of list

I have a very simple model.
the main class, Recipe, contains a list of RecipeItem. Every RecipeItem has a list of RecipeItemComponents.
Using the Entity Framework context, I can do this:
var ret = await _context.Recipes
.Include(x => x.RecipeItems)
.ToListAsync();
This code returns The recipes with the RecipeItems, but for every RecipeItems I have not RecipeItemsComponent list.
It makes sense since I'm not including those, but I'm, not sure no how to do it.
Thanks
Here is my working code sample
Models
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Child1> Child1s { get; set; }
}
public class Child1
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public virtual List<Child2> Child2s { get; set; }
}
public class Child2
{
public int Id { get; set; }
public string Name { get; set; }
public int Child1Id { get; set; }
public Child1 Child1 { get; set; }
}
In the DB context class
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options)
: base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().HasMany(x => x.Child1s).WithOne(x => x.Parent).HasForeignKey(x => x.ParentId);
modelBuilder.Entity<Child1>().HasMany(x => x.Child2s).WithOne(x => x.Child1).HasForeignKey(x => x.Child1Id);
this.InitialData(modelBuilder);
base.OnModelCreating(modelBuilder);
}
protected void InitialData(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().HasData(new Parent[]
{
new Parent
{
Id = 1,
Name = "Parent 1",
}
});
modelBuilder.Entity<Child1>().HasData(new Child1[]
{
new Child1
{
Id = 1,
Name = "Child 1 1",
ParentId = 1,
}
});
modelBuilder.Entity<Child2>().HasData(new Child2[]
{
new Child2
{
Id = 1,
Name = "Child 2 1",
Child1Id = 1
}
});
}
public DbSet<Parent> Parent { get; set; }
public DbSet<Child1> Child1s { get; set; }
public DbSet<Child2> Child2s { get; set; }
}
Controller
public class ParentsController : Controller
{
private readonly TestDbContext _context;
public ParentsController(TestDbContext context)
{
_context = context;
} public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var parent = await _context.Parent
.Include(x=>x.Child1s).ThenInclude(x=>x.Child2s)
.FirstOrDefaultAsync(m => m.Id == id);
if (parent == null)
{
return NotFound();
}
return View(parent);
}
}
Here is the output
You can't use the strongly typed extension methods to include everything. In some cases you need to use a string:
.Include("RecipeItems.RecipeItemsComponents")
For those curious, the documentation for this overload is here:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.include?view=efcore-2.1#Microsoft_EntityFrameworkCore_EntityFrameworkQueryableExtensions_Include__1_System_Linq_IQueryable___0__System_String_

Fluent NHibernate: How to perform INNER JOIN?

I was wondering if someone could help me. I want to perform SQL INNER JOIN operation by using NHibernate. First of all let me introduce you to the structure of my database.
I have the following parameters into my C# method: int documentId, int userId, int folderId. My main goal is to get RoleDocumentValueEntity. By using USER_ID and FOLDER_ID I can get the unique FolderUserRoleEntity. I am looking for the ROLE_ID. I want to join ROLE_DOCUMENT_VALUE and FOLDER_USER_ROLES tables by ROLE_ID where DOCUMENT_ID is equal to specific value which I have as a parameter of C# method.
My entities:
public class RoleDocumentValueEntity
{
public virtual int Id { get; set; }
public virtual RoleEntity Role { get; set; }
public virtual DocumentEntity Document { get; set; }
public virtual string Text { get; set; }
}
public class RoleEntity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<UserRoleEntity> UserRoles { get; set; }
public virtual IList<FolderUserRoleEntity> FolderUserRoles { get; set; }
}
public class FolderEntity
{
public virtual int Id { get; set; }
public virtual UserEntity User { get; set; }
public virtual IList<DocumentEntity> Documents {get; set;}
}
public class UserEntity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class UserRoleEntity
{
public virtual int Id { get; set; }
public virtual int UserId { get; set; }
public virtual RoleEntity Role { get; set; }
}
public class FolderUserRoleEntity
{
public virtual int Id { get; set; }
public virtual int UserId { get; set; }
public virtual RoleEntity Role { get; set; }
public virtual FolderEntity Folder { get; set; }
}
Mappings:
public class RoleDocumentValueMap : BaseDatabaseMap<RoleDocumentValueEntity>
{
public RoleDocumentValueMap()
{
this.Table("ROLE_DOCUMENT_VALUE");
Id(x => x.Id, "ROLE_DOCUMENT_VALUE_ID");
// Relationships
References(x => x.Role)
.Column("ROLE_ID")
.Cascade.None();
References(x => x.Document)
.Column("DOCUMENT_ID")
.Cascade.None();
}
}
public class RoleMap : BaseDatabaseMap<RoleEntity>
{
public RoleMap()
{
this.Table("ROLES");
Id(x => x.Id, "ROLE_ID");
HasMany(x => x.UserRoles)
.KeyColumn("ROLE_ID")
.Cascade.All()
.Inverse();
HasMany(x => x.FolderUserRoles)
.KeyColumn("ROLE_ID")
.Cascade.All()
.Inverse();
}
}
public class UserRoleMap : BaseDatabaseMap<UserRoleEntity>
{
public UserRoleMap()
{
this.Table("USER_ROLES");
this.Id(x => x.Id, "USER_ROLE_ID");
Map(x => x.UserId).Column("USER_ID");
References(x => x.Role).Column("ROLE_ID").Cascade.None().Fetch.Join();
}
}
public class FolderUserRoleMap : BaseDatabaseMap<FolderUserRoleEntity>
{
public FolderUserRoleMap()
{
this.Table("FOLDER_USER_ROLES");
Id(x => x.Id, "FOLDER_USER_ROLE_ID");
Map(x => x.UserId).Column("USER_ID");
References(x => x.Folder).Column("FOLDER_ID").Cascade.None().Fetch.Join();
References(x => x.Role).Column("ROLE_ID").Cascade.None().Fetch.Join().Not.LazyLoad();
}
}
public class FolderMap : BaseDatabaseMap<FolderEntity>
{
public FolderMap()
{
this.Table("FOLDERS");
Id(x => x.Id, "FOLDER_ID");
HasMany(x => x.Documents)
.Cascade
.AllDeleteOrphan()
.Inverse()
.KeyColumn("FOLDER_ID");
}
}
public class DocumentMap : BaseDatabaseMap<DocumentEntity>
{
public DocumentMap()
{
this.Table("DOCUMENTS");
Id(x => x.Id, "DOCUMENT_ID");
// Relationships
References(x => x.Folder)
.Column("FOLDER_ID")
.Cascade.None();
}
}
INNER JOIN
I manually created SQL query which I want to get by using NHibernate.
SELECT ROLE_DOCUMENT_VALUE.*
FROM ROLE_DOCUMENT_VALUE
INNER JOIN FOLDER_USER_ROLES ON FOLDER_USER_ROLES.ROLE_ID = ROLE_DOCUMENT_VALUE.ROLE_ID
AND FOLDER_USER_ROLES.FOLDER_ID = ?
AND FOLDER_USER_ROLES.USER_ID = ?
AND ROLE_DOCUMENT_VALUE.DOCUMENT_ID = ?;
It looks like my NHibernate criteria should look like this one:
var result = this.Session.CreateCriteria<RoleDocumentValueEntity>()
.CreateCriteria("FolderUserRoles", "fr")
.Add(Restrictions.Eq("fr.UserId", userId))
// etc
.List();
But this criteria cannot be performed because RoleDocumentValueEntity doesn't have such property as FolderUserRoles.
Please, could you provide me what is the best practice in this case? How can I create the SQL query which I want by using NHibernate?
you could use linq support for Nhibernate to achieve this easily,
from doc in Session.Query<RoleDocumentValueEntity>()
join role in Session.Query<FolderUserRoleEntity>()
on doc.Role equals role.Role

Configuration of two Entites with EntityTypeConfiguration

I have the two following Entitites with the corresponding EntityTypeConfigurations
public class Master
{
public int Id { get; set; }
public int RequestId { get; set; }
public string ClientNo { get; set; }
public string SomeValue { get; set; }
public ICollection<Child> Childs { get; set; }
}
public class MasterConfig : EntityTypeConfiguration<Master>
{
public MasterConfig()
{
ToTable("Master", "MySchema");
HasKey(k => k.Id);
HasMany(m => m.Childs)...
// Connect Master.RequestId to Child.RequestId
// and Master.ClientNo to Child.ClientNo
}
}
public class Child
{
public int Id { get; set; }
public int RequestId { get; set; }
public string ClientNo { get; set; }
public string SomeOtherValue { get; set; }
}
public class ChildConfig : EntityTypeConfiguration<Child>
{
public ChildConfig()
{
ToTable("Child", "MySchema");
HasKey(k => k.Id);
}
}
I would like to configure it that way that when I do
myAppContext.Masters.Include(m => m.Childs).First(m => m.Id == 4);
It would load all the Master with ID 4 and the Corresponding maching Childs.
Somehow I can't make it work.
Relationships are based on FK to PK. In your scenario RequestId is not primary key either of Master or Child. You should have Request model, which has RequsetId as PK and should have navigation properties to Master, and Child model should not directly bound to Request, it should bound to Master. Your models should look like this:
public class Request
{
public int Id { get; set; }
public string ClientNo { get; set; }
public virtual ICollection<Master> MasterCollection { get; set; }
}
public class RequestMap : EntityTypeConfiguration<Request>
{
HasKey(m => m.Id);
}
public class Master
{
// Id is same as RequestId
public int Id { get; set; }
public int RequestId { get; set; }
public string SomeValue { get; set; }
public virtual Request Request { get; set; }
public virtual ICollection<Child> Childs { get; set; }
}
public class MasterConfig : EntityTypeConfiguration<Master>
{
public MasterConfig()
{
ToTable("Master", "MySchema");
HasKey(k => k.Id);
// Map Request and Master
HasRequired(m => m.Request)
.WithMany(m => m.MasterCollection)
.HasForeignKey(m => m.RequestId);
}
}
public class Child
{
public int Id { get; set; }
public string SomeOtherValue { get; set; }
public int MasterId { get; set; }
public virtual Master Master { get; set; }
}
public class ChildConfig : EntityTypeConfiguration<Child>
{
public ChildConfig()
{
ToTable("Child", "MySchema");
HasKey(k => k.Id);
HasRequired(m => m.Master)
.WithMany(m => m.Childs)
.HasForeignKey(m => m.MasterId);
}
}
By changing your models to suit this, now you can load your Master and related Childs as you want to:
Master master = myAppContext.Masters
.Include(m => m.Childs)
.First(m => m.Id == 4);
If you want to load Request data according to Master then:
Master master = myAppContext.Masters
.Include(m => m.Request)
.Include(m => m.Childs)
.First(m => m.Id == 4);
You can also load Master and Request details for any child:
Child child = myAppContext.Childs
.Include(m => m.Master.Request)
.First(m => m.Id == 2);

Fluent NHibernate one-to-one mapping not working (again.....)

The more messages I read about it the more I get confused! Maybe I simply understand something wrong... I have two entities: Computer and OperatingSystem, both have a few dozens properties.
The relation I like to map is one-to-one, one computer has one OS. To shorten I only show relevant properties:
public abstract class EntityBase
{
public virtual Guid Id { get; set; }
public virtual DateTime CDate { get; set; }
public virtual string CUser { get; set; }
protected EntityBase()
{
Initialize();
}
private void Initialize()
{
CDate = DateTime.Now;
var wi = System.Security.Principal.WindowsIdentity.GetCurrent();
if (wi != null)
CUser = wi.Name;
}
}
public class ComputerDb : EntityBase
{
public virtual string ComputerType { get; set; }
public virtual string Domain { get; set; }
public virtual string Name { get; set; }
/* ...... */
public virtual OsDb Os { get; set; }
public virtual void AddOs(OsDb os)
{
this.Os = os;
}
public class ComputerDbMap : ClassMap<ComputerDb>
{
public ComputerDbMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.ComputerType).Nullable().Length(16);
Map(x => x.Domain).Nullable().Length(64);
Map(x => x.Name).Not.Nullable().Length(64);
/* .... */
HasOne(x => x.Os).Cascade.All().PropertyRef(x => x.Computer);
}
}
public class OsDb : EntityBase
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual bool? PartOfCluster { get; set; }
/* ... */
public virtual ComputerDb Computer { get; set; }
}
public class OsDbMap : ClassMap<OsDb>
{
public OsDbMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Nullable().Length(128).Index("Idx_OsName");
Map(x => x.Description).Nullable().Length(128);
Map(x => x.PartOfCluster).Nullable();
/* .... */
References(x => x.Computer, "Computer_Id").Unique();
}
}
In the program I do:
var computer = getComputerDb(ps);
var os = getOsDb(ps);
computer.AddOs(os);
var o = new Operations();
o.AddComputerToDb(computer);
where
public void AddComputerToDb(ComputerDb cpDb)
{
using (var session = _sessionManager.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(cpDb);
transaction.Commit();
}
catch (Exception e)
{
Log.Error($"Exception in AddComputerToDb(). Exception: {e}");
transaction.Rollback();
throw;
}
finally
{
session.Close();
}
}
}
Here the result of the select on the tblComputerDb table on SQLServer:
And here the result from the tblOsDb table:
Problem: Computer_Id is NULL. If I understand things right, it should contain the GUID of the computer record 053DFFDF-0A88-4270-8405-A5EE00FFD388. I do not understand, why it cannot fill this Id from the computer entity. Modelling as ONE-TO-MANY works just fine, but is NOT what I want...
What am I doing wrong?

Fluent Nhibernate Get Distinct parents having child count greater than n

Below are my entities. One ContractBill can have multiple SubscriberInvoices.
public class ContractBill
{
public virtual Int32 ContractID { get; set; }
public virtual Int32 CustomerID { get; set; }
public virtual string ContractStatus { get; set; }
public virtual Customer Customer { get; set; }
public virtual IList<SubscriberInvoice> InvoiceList { get; set; }
public ContractBill() { }
}
public class ContractBillMap : ClassMap<ContractBill>
{
public ContractBillMap()
{
Table("GT_CONTRACT");
Id(x => x.ContractID, "GF_CONT_ID").GeneratedBy.Increment();
Map(x => x.CustomerID, "GF_CUST_ID");
Map(x => x.ContractStatus, "GF_CONTRACT_STATUS");
HasMany(x => x.InvoiceList).KeyColumn("GF_CONT_ID").Not.LazyLoad();
}
}
public class SubscriberInvoice : Object
{
virtual public String InvoiceID { get; set; }
virtual public Int32 ContractID { get; set; }
virtual public Int32 CustomerID { get; set; }
virtual public String BillPeriod { get; set; }
virtual public Double Amount { get; set; }
virtual public Double MinimumAmount { get; set; }
virtual public DateTime PayByDate { get; set; }
virtual public String IsPaid { get; set; }
virtual public Double AmountPaid { get; set; }
public SubscriberInvoice()
{
}
}
public class SubscriberInvoiceMap : ClassMap<SubscriberInvoice>
{
public SubscriberInvoiceMap()
{
Table("GT_BILL");
CompositeId()
.KeyProperty(x => x.InvoiceID, "GF_BILL_ID")
.KeyProperty(x => x.ContractID, "GF_CONT_ID")
.KeyProperty(x => x.BillPeriod, "GF_BILL_PERIOD")
Map(x => x.Amount, "GF_AMOUNT");
Map(x => x.MinimumAmount, "GF_MINIMUM_AMOUNT");
Map(x => x.PayByDate, "GF_PAY_BY_DATE");
Map(x => x.IsPaid, "GF_PAID");
Map(x => x.AmountPaid, "GF_AMOUNT_PAID");
}
}
//Get distinct BillContract having more than two non-paid Subscriber Invoices.
ICriteria criteria = session.CreateCriteria(typeof(ContractBill), "cb1");
string aliasName = "invoice";
string prefix = aliasName + ".";
criteria = criteria.CreateAlias("InvoiceList", aliasName);
criteria = criteria.Add(Expression.Eq(prefix + "IsPaid", "N"));
// below detached query will give the count of non paid invoice list
DetachedCriteria crit = DetachedCriteria.For(typeof(ContractBill))
.CreateAlias("InvoiceList", "InvoiceList")
.SetProjection(Projections.ProjectionList()
.Add(Projections.RowCount()))
.Add(Property.ForName("ContractID").EqProperty("cb1.ContractID"));
//Non paid invoice Count = 2
criteria = criteria.Add(Restrictions.Gt(Projections.SubQuery(crit), 2));
//Not giving proper reasult when being used with paging
//criteria = Criteria.SetResultTransformer(Transformers.DistinctRootEntity);
criteria =criteria.SetFirstResult(0).SetMaxResults(10);
//Tried with below detached query, but not getting distinct record
DetachedCriteria crit1 = DetachedCriteria.For(typeof(ContractBill))
.Add(Restrictions.EqProperty ("ContractID", "cb1.ContractID"))
.CreateCriteria("InvoiceList")
.SetProjection(Projections.Distinct(Projections.Id()));
criteria = criteria.Add(Subqueries.PropertyIn("cb1.ContractID", crit1));
IList<ContractBill> matchingObjects = criteria.List<ContractBill>();
Above code is not giving distinct result. Please suggest how to get the distinct ContractBill records.
For displaying records with paging, I need to get the total record count for above criteria also.

Categories

Resources