I have this mapping:
public sealed class EntityMap : ClassMap<Entity>
{
public EntityMap ()
{
...
Component(entity => entity.StateHistory,
m => m.HasMany<HistoryItem<EntityState>>
(Reveal.Property<EntityStateHistory>("Items"))
.Table("EntityStateHistory")
.KeyColumn("IDEntity")
.Component
( m2 =>
{
m2.Map(esh => esh.Item, "State")
.CustomType(typeof(EntityState)).Not.Nullable();
m2.Map(esh=> esh.Date, "TransitionDate").Not.Nullable();
}
)
.Cascade.AllDeleteOrphan());
...
}
}
I want to make a query where i get a specific date (TransitionDate) in an EntityStateHistory entry.
If it was possible it would be something like this:
"select e from Entity e where e.StateHistory.Items.Date = :date"
but i can't do this, i don't know how i can access an History record date, knowing that History is a component that has itself a collection of components, and i need to access one of the properties of those components in the collection.
The object model is something like this:
public class Entity
{
private int ID {get; set;}
etc
...
public virtual EntityStateHistory StateHistory{ get; private set; }
}
public class EntityStateHistory: History<EntityState>
{
//some wraped properties and methods
public IList<HistoryItem<EntityState>> StateRecords
{
get { return base.Items;}
}
public bool ContainsStateRecord(EstadoOT state)
{
return base.Items.Count(i => i.Item.Equals(state)) > 0;
}
etc ...
}
public class History<T>
{
protected virtual IList<HistoryItem<T>> Items { get; private set; }
public History()
{
Items = new List<HistoryItem<T>>();
}
protected virtual HistoryItem<T> AddHistoryItem(DateTime data, T item)
{
...
}
}
public class ItemHistory<T>
{
#region NHibernate
private int ID { get; set; }
#endregion
public virtual DateTime Date { get; private set; }
public virtual T Item { get; private set; }
...
}
I know i will probably have to change the mapping for entity, and create a map for EntityStateHistory, but i would like to avoid that, because that means one more table. The way i have it is the most canonical mapping because has no need to map HistoryItem or EntityStateHistory, that means i only use one table to map EntityStateHistory:
Table EntitiStateHistory:
-IDEntity
-TransitionDate
-State
So is it possible with the current mapping to query the database for a Entity that has a specific history record date?
Thanks
It was so easy...
The problem was in the path:
"select e from Entity e where e.StateHistory.Items.Date = :date"
if i do this:
"select e from Entity e join e.StateHistory.Items i where i.Date = :date"
it works
Related
I have following relationships:
public class Order : IAggregateRoot
{
public Guid OrderId { get; private set; }
public string Title { get; private set; }
private ICollection<OrderProduct> _orderProducts;
private Order()
{
_orderProducts = new List<OrderProduct>();
}
}
public class OrderProduct
{
public Guid OrderProductId { get; private set; }
public int Quantity { get; private set; }
public Order Order { get; private set; }
public Product Product { get; private set; }
public bool Purchased { get; private set; }
private OrderProduct()
{
}
}
internal class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
private readonly string orderProducts = "_orderProducts";
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("Orders");
builder.HasKey(b => b.OrderId);
builder.OwnsMany<OrderProduct>(orderProducts, x =>
{
x.ToTable("OrderProducts");
x.HasKey(y => y.OrderProductId);
x.WithOwner(y => y.Order);
x.HasOne(x => x.Product)
.WithMany();
});
}
}
Order is aggregate root, owns collection of OrderProduct entities. Based on https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities owned types are included by default(without using the Include method).
My question is, how can I include Product for each OrderProduct when I load Order from db by following method:
public async Task<App.Domain.Orders.Order> GetOrder(Guid orderId)
{
return await _appDbContext.Orders
.SingleOrDefaultAsync(x => x.OrderId == orderId);
}
Is it possible in such a configuration of types and without exposing the collection of OrderProduct from Order?
My goal was to avoid using hard-coded strings for eager load, solution which I found is as follows:
internal class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.OwnsMany<OrderProduct>(x => x.OrderProducts, x =>
{
x.WithOwner(y => y.Order);
x.HasOne(x => x.Product)
.WithMany();
x.OwnedEntityType.FindNavigation(nameof(OrderProduct.Product)).SetIsEagerLoaded(true);
}
);
builder.Metadata.FindNavigation(nameof(Order.OrderProducts))
.SetPropertyAccessMode(PropertyAccessMode.Field);
}
}
public class Order : IAggregateRoot
{
public Guid Id { get; private set; }
public string Title { get; private set; }
private List<OrderProduct> _orderProducts;
public IReadOnlyCollection<OrderProduct> OrderProducts => _orderProducts.AsReadOnly();
private Order()
{
_orderProducts = new List<OrderProduct>();
}
}
public class OrderRepository : IOrderRepository
{
public async Task<Order> GetOrder(Guid orderId)
{
return await _shoppingAppContext.Orders
.SingleOrDefaultAsync(x => x.OrderId == orderId);
}
}
Thanks that Product is eager loaded by default. What do you think about it?
The reason why you can't get Product for each of the OrderProduct in the Order object is that you are making OrderProduct as an owned type of Order while Product is not an owned type of OrderProduct.
That's why you can get OrderProduct without .Include() method on Order object and can't get Product without .Include() method on OrderProduct.
I'm a complete noob to Fluent NHibernate, and I'm using the Query Object Pattern based on a recommendation. Which I'm also new to. I'll try to keep the code samples concise and helpful.
User class:
public class User {
public Guid ID { get; set; }
public string Name { get; set; }
}
Visibility:
public enum VisibilityType {
Anybody,
OwnersOnly,
Nobody
}
Car class:
public class Car {
public Guid ID { get; set; }
public VisibilityType Visibility { get; set; }
public ICollection<User> Owners { get; set; }
}
So I need to write a conditional restriction method for the query object. Return all cars that have VisibilityType.Public, but if a car has Visibility property value of VisibilityType.OwnersOnly, restrict the return to users who belong to that group.
Here's the current restriction method that I have working, but without the condition:
public class CarQueryObject
{
private User user { get; set; }
private const string OwnersProperty = "Owners";
private const string OwnersIDProperty = "Owners.ID";
public CarQueryObject RestrictToOwners()
{
// How do I add a conditional criteria here? Only restrict by owner
// if the QueryObject has VisibilityType.OwnersOnly? Note that it should
// *NOT* restrict VisibilityType.Anybody
CreateOwnersAlias();
Criteria.Add(Restrictions.Eq(OwnersIDProperty, user.Id));
return this;
}
public CarQueryObject JoinFetchOwned()
{
Criteria.SetFetchMode(OwnersProperty, FetchMode.Join);
return this;
}
public void CreateOwnersAlias()
{
Criteria.CreateAlias(OwnersProperty, OwnersProperty, JoinType.LeftOuterJoin);
JoinFetchOwned();
}
}
?_?
an idea to get shown cars
var carsShown = session.CreateCriteria<Car>()
.JoinAlias("Owner", "owner")
.Add(Expressions.Or(
Expression.Eq("Visibility", Visibility.Anybody),
Expression.Eq("Visibility", Visibility.OwnersOnly) && Expression.Eq("owner.Id", currentUser.Id)
))
.List<Car>();
I have the following database tables defined:
Club: Id, Name
Member: Id, Name
ClubMember: ClubId, MemberId
I have the following entity Classes defined:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Member> Members { get; set; }
}
public class Member() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Club> Clubs { get; set; }
}
I have the following overrides defined:
public class MemberOverride : IAutoMappingOverride<Member>
{
public void Override(AutoMapping<Member> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Clubs)
.ParentKeyColumn("MemberId")
.ChildKeyColumn("ClubId")
.Cascade.All()
.Table("ClubMembers");
}
}
public class ClubOverride : IAutoMappingOverride<Club>
{
public void Override(AutoMapping<Club> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Members)
.ParentKeyColumn("ClubId")
.ChildKeyColumn("MemberId")
.Inverse()
.Table("ClubMembers");
}
}
I can see from my overrides that the Inverse on the ClubOverride means you cannot do the following
session.Save(club.Members.Add(member));
but this works:
session.Save(member.Clubs.Add(club);
But it doesn't make logical sense. I want to be able to save either the club with members or member with clubs.
Am I trying to do something impossible with FluentNhibernate?
TIA
Yes, you're right, that's not possible. But it's not a question of FluentNhibernate, NHibernate works like that.
Only one side is the owner of the relation and on charge of adding elements.
From official documentation:
Changes made only to the inverse end of the association are not persisted. This means that NHibernate has two representations in memory for every bidirectional association, one link from A to B and another link from B to A. This is easier to understand if you think about the .NET object model and how we create a many-to-many relationship in C#:
You can create add or remove methods on your entities that will help accomplish this:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
private IList<Member> members;
public virtual IEnumerable<Member> Members { get { return members.Select(x => x); } }
public Club() {
members = new List<Member>();
}
public virtual void AddMember(Member member){
if (members.Contains(member))
return;
members.Add(user);
member.AddClub(this);
}
public virtual void RemoveMember(Member member){
if (!members.Contains(member))
return;
members.Remove(member);
member.RemoveClub(this);
}
}
Using NHibernate, is there a quick way to map the following class:
public class Office
{
public virtual Guid Id { get; set; }
public virtual IList<DateTime> Holidays { get; set; }
}
...to the tables:
table Office { Guid Id }
table OfficeHolidays { Guid OfficeId, DateTime Holiday }
Quick? I think so. Create an OfficeHoliday class and map it as one-to-many from Office, maping the collection as a private member in Office. Then expose the Holidays property and methods to maintain it.
public class Office
{
private IList<OfficeHoliday> _officeHolidays;
public virtual Guid Id { get; set; }
public IEnumerable<DateTime> Holidays
{
get { return _officeHolidays.Select(x => x.Holiday); }
}
public void AddHoliday(DateTime holiday)
{
// should check if it already exists first...
var newHoliday = new OfficeHoliday(this, holiday.Date);
_officeHolidays.Add(newHoliday);
}
public void RemoveHoliday(DateTime holiday)
{
var removeHoliday = _officeHolidays.FirstOrDefault(x => x.Holiday == holiday.Date);
if (removeHoliday != null)
{
_officeHolidays.Remove(removeHoliday);
}
}
}
I've got a basic tree structure that is stored in a single table. Let's say this is my model:
public class TreeNode {
public virtual Guid Id { get; private set; }
public virtual string Name { get; private set; }
public virtual IEnumerable<TreeNode> Contents { get; private set; }
}
and the table:
TREE_NODES
PK_NODE Guid
FK_NODE_PARENT Guid
NODE_NAME Varchar
I want the following implementation where the return value is a TreeNode with the full eagerly loaded tree of its children and their children, etc.
public class Tree {
ISessionFactory _sessions;
public TreeNode GetBy(Guid id) {
using(var s = _sessions.OpenSession())
return s.Linq<TreeNode>().Single(n => n.Id == id);
}
}
How would I do this mapping?
I doubt you can optimize it - there is no recurtion in basic SQL. You can optimize it using server-side procedures (server specific - some servers, like MySQL does not support them) but it still be doubtful as you get non-recursive components.
Probably the best way is to walk down the tree in loading function and force the evaluation. Something like:
public class TreeNode {
public virtual Guid Id { get; private set; }
public virtual string Name { get; private set; }
public virtual IEnumerable<TreeNode> Contents { get; private set; }
}
public class Tree {
ISessionFactory _sessions;
public TreeNode GetBy(Guid id) {
using(var s = _sessions.OpenSession()) {
return LoadSubTree(s.Linq<TreeNode>().Single(n => n.Id == id));
}
}
private LoadSubTree(TreeNode node) {
foreach(var n in node.Contents)
LoadSubTree(n);
}
}
PS. Tree is probably not the best place for ISessionFactory.