good day everyone
i have some trouble with formula property, update and nhibernate
suppose, i have next model:
public class Model
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual State CurrentState { get; protected set; }
public virtual ICollection<State> History { get; set; }
public Model()
{
State = new List<State>();
}
}
public class State
{
public virtual Guid Id { get; set; }
public virtual DateTime Timestamp { get; set; }
public virtual string Description { get; set; }
public virtual string Author { get; set; }
public virtual Model Owner { get; set; }
}
and this is my mapping (NHibernate 4.0.0.4000, using mapping by code (not fluentnhibernate))
public class ModelMapping: ClassMapping<Model>
{
public ModelMapping()
{
Id(x => x.Id, m => m.Generator(Generators.Assigned));
Property(x => x.Name);
ManyToOne(x => x.CurrentState, c =>
{
c.Formula("(select top(1) s.Id from State s where s.Owner = Id order by s.Timestamp desc)");
});
Bag(x => x.History, c =>
{
c.Cascade(Cascade.All);
c.Key(k =>
{
k.ForeignKey("none");
k.Column("ParentId");
});
c.OrderBy(x => x.Timestamp);
});
}
}
public class StateMapping: ClassMapping<State>
{
public StateMapping()
{
Id(x => x.Id);
Property(x => x.Timestamp);
Property(x => x.Description);
Property(x => x.Author);
ManyToOne(x => x.Owner, c => c.Column("ParentId"));
}
}
now, i want retrieve some model entity (by knowing their id),
var model = modelRepository.Get(<someId>);
if i see model.CurrentState i see correct state (model.CurrentState = model.History.Last())
next, i try to add new state and save entity
model.History.Add(new State(){...});
modelRepository.Save(model);
after this content of model.History is ok (i see new state in collection and in database), but value of property CurrentState not updated (and equal to state at retrieving moment).
is there is any way to update formula property after entity was saved?
The only way to get the loaded entity to refresh is to call refresh or reload the entity.
You History looks correct because you actually changed that state locally and sent the update to the server.
Here's a relevant link.
https://weblogs.asp.net/ricardoperes/nhibernate-pitfalls-entity-refresh
Related
I have Payments between Users. A Payment has a FromUser and a ToUser. I'm testing out the entity relationships with the following:
var newPayment = new Payment() {
FromUserId = user1.UserId,
ToUserId = user2.UserId
};
db.Payments.Add(newPayment);
db.SaveChanges();
var tempPaymentId = user1.Payments.First().PaymentId;
newPayment = db.Payments.First(s => s.PaymentId == tempPaymentId);
Assert.AreEqual(newPayment.FromUserId, user1.UserId); // true
Assert.AreEqual(newPayment.ToUserId, user2.UserId); // true
Assert.AreEqual(user1.Payments.Count(), 1); // true
Assert.AreEqual(user2.Payments.Count(), 1); // false
My question is - why does user2 not have any Payments?
Class and fluent config:
public class Payment {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int PaymentId { get; set; }
[ForeignKey("FromUser")]
public int FromUserId { get; set; }
public User FromUser { get; set; }
[ForeignKey("ToUser")]
public int ToUserId { get; set; }
public User ToUser { get; set; }
}
public PaymentConfiguration() {
HasRequired(s => s.FromUser);
HasRequired(s => s.ToUser);
}
public UserConfiguration() {
// One-to-Many
HasMany(s => s.Payments);
}
It can be due to many reasons, but, first things first, you mixed Data Annotations and Fluent API mappings. Why? I am not sure if it works as expected or not, but I am sure that it does not look right. Stick to one (I recommend Fluent API). Secondly, I suppose that, you should have two navigation properties at User for Payments. User.Payments does not mean much, do you want payments that the user paid, or the payments that were paid to user by one navigation property?. And you have to map only from one side. Finally, your models and mappings should be like below:
public class User
{
public int UserId { get; set; }
// Navigation properties
public virtual ICollection<Payment> PaymentsFromUser { get; set; }
public virtual ICollection<Payment> PaymentsToUser { get; set; }
}
public class UserConfiguration
: IEntityTypeConfiguration<User>
{
public UserConfiguration()
{
// Primary key
HasKey(m => m.UserId);
Property(m => m.PaymentId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class Payment
{
public int PaymentId { get; set; }
public int FromUserId { get; set; }
public int ToUserId { get; set; }
// Navigation properties
public virtual User FromUser { get; set; }
public virtual User ToUser { get; set; }
}
public class PaymentConfiguration
: IEntityTypeConfiguration<Payment>
{
public PaymentConfiguration()
{
// Primary key
HasKey(m => m.PaymentId);
Property(m => m.PaymentId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// Relationship mappings
HasRequired(m => m.FromUser)
.WithMany(m => m.PaymentsFromUser)
.HasForeignKey(m => m.FromUserId)
.WillCascadeOnDelete(true);
HasRequired(m => m.ToUser)
.WithMany(m => m.PaymentsToUser)
.HasForeignKey(m => m.ToUserId)
.WillCascadeOnDelete(true);
}
}
Note: virtual keyword is for Lazy loading and can be skipped if you do not need it.
Now it should work as expected. Just remember to test like:
Assert.AreEqual(user1.PaymentsFromUser.Count(), 1);
Assert.AreEqual(user2.PaymentsToUser.Count(), 1);
Please note that, I suppose user1 and user2 both are tracked by EntityFramework, so EF can relate users with payments.
I have a parent/child hierarchy of which I want to insert a new parent into a DbContext and have it automatically persist the child objects. The relationship is a One-to-many, where each parent can at 0 or more columns.
But whenever I call DbContext.Save(parent) I receive a 'Conflicting changes detected. This may happen when trying to insert multiple entities with the same key.'. When I strip the parent of the child columns it saves fines, so I'm assuming this is related to the child objects not having their primary key set. How do I tell the EntityFramework to save my hierarchy properly?
My classes:
public class ExcelTemplate
{
public int Id { get; set; }
public string Name { get; set; }
public int FirstDataRow { get; set; }
public virtual ICollection<TemplateColumn> Columns { get; set; }
public ExcelTemplate()
{
Columns = new List<TemplateColumn>();
}
}
public class TemplateColumn
{
public int Id { get; set; }
public int Index { get; set; }
public int MetricTypeId { get; set; }
public virtual MetricType MetricType { get; set; }
public int TemplateId { get; set; }
public virtual ExcelTemplate Template { get; set; }
}
And the configurations:
public class ExcelTemplateConfiguration : EntityTypeConfiguration<ExcelTemplate>
{
public ExcelTemplateConfiguration()
{
HasKey(t => t.Id)
.Property(t => t.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(t => t.Name)
.IsRequired();
HasMany(t => t.Columns)
.WithRequired(c => c.Template)
.HasForeignKey(c => c.TemplateId);
}
}
public class TemplateColumnConfiguration : EntityTypeConfiguration<TemplateColumn>
{
public TemplateColumnConfiguration()
{
HasKey(c => c.Id)
.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(tpl => tpl.MetricType)
.WithRequiredDependent();
}
}
Do the ExcelTemplate.Id and TemplateColumn.Id have Identity specification? Are you setting the Id property properly?
Are you setting the ExcelTemplate property of the TemplateColumn objects to the ExcelTemplate parent?
Something like:
ExcelTemplate t = new ExcelTemplate();
//init template code...skip
TemplateColumn c = new TemplateColumn();
//init template column code...skip
c.ExcelTemplate = t;//this is the line I am asking if you are doing.
I have discovered the culprit through trial-and-error, because the problem persisted when I only tried to store a TemplateColumn by itself.
The problem was to remove this line of code from the TemplateColumn configuration and let the entity framework map by convention:
HasRequired(tpl => tpl.MetricType)
.WithRequiredDependent();
Any idea why this didn't work?
I am working to map an existing database using Fluent NHibernate and have encountered a problem when it comes to complex many-to-many relationships (additional columns).
I know that many-to-many relationships with additional columns have to be mapped as HasMany rather than HasManyToMany as they are not pure many-to-many relationships. The linking table has to be mapped as a class within itself, which I have done in the example below.
When loading this data from an existing database it loads fine. The project I am working on takes this data and inserts it into an empty database, which is where the problem occurs. I think that when inserting into the new database the CompositeId is trying to insert NULL values for ItemID and ItemGroupID which is not allowed in the database. Changing the database structure is not a viable option at this point, is there a way around this issue?
Thanks, example code below.
Entity Classes
public class Item
{
public virtual long ItemID { get; set; }
public virtual string Name { get; set; }
}
public class ItemGroup
{
public virtual long ItemGroupID { get; set; }
public virtual string Name { get; set; }
public virtual IList<ItemInGroup> ItemsInGroup { get; set; }
}
public class ItemInGroup
{
public virtual Item Item { get; set; }
public virtual ItemGroup ItemGroup { get; set; }
public virtual int? DisplayOrder { get; set; }
}
Mapping Classes
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
Table("Items");
Id(x => x.ItemID).GeneratedBy.Identity();
Map(x => x.Name);
}
}
public class ItemGroupMap : ClassMap<ItemGroup>
{
public ItemGroupMap()
{
Table("ItemGroups");
Id(x => x.ItemGroupID).GeneratedBy.Identity();
Map(x => x.Name);
HasMany(x => x.ItemsInGroup).KeyColumn("ItemGroupID").Cascade.All();
}
}
public class ItemInGroupMap : ClassMap<ItemInGroup>
{
public ItemInGroupMap()
{
Table("ItemsInGroups");
CompositeId().KeyReference(x => x.Item, "ItemID")
.KeyReference(x => x.ItemGroup, "ItemGroupID");
Map(x => x.DisplayOrder);
}
}
assuming DisplayOrder is the only extra column in the link table why not use the List index of IList as order?
public class ItemGroup
{
public virtual long ItemGroupID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Item> Items { get; private set; }
}
public class ItemGroupMap : ClassMap<ItemGroup>
{
public ItemGroupMap()
{
Table("ItemGroups");
Id(x => x.ItemGroupID).GeneratedBy.Identity();
Map(x => x.Name);
HasManyToMany(x => x.ItemsInGroup)
.Table("ItemsInGroups")
.ParentKeyColumn("ItemGroupID")
.ChildKeyColumn("ItemID")
.AsList("DisplayOrder")
.Cascade.All();
}
}
I am trying to figure out how to map the following situation using NHibernate's "sexy" mapping by code system.
Please help as I have been trying to figure this out for a while with no luck! I am using components to represent the composite keys. Below are the tables I am trying to map.
Account
-------
BSB (PK)
AccountNumber (PK)
Name
AccountCard
-----------
BSB (PK, FK)
AccountNumber (PK, FK)
CardNumber (PK, FK)
Card
------------
CardNumber (PK)
Status
Here is my current attempt (which is not working at all!)
Account:
public class Account
{
public virtual AccountKey Key { get; set; }
public virtual float Amount { get; set; }
public ICollection<Card> Cards { get; set; }
}
public class AccountKey
{
public virtual int BSB { get; set; }
public virtual int AccountNumber { get; set; }
//Equality members omitted
}
public class AccountMapping : ClassMapping<Account>
{
public AccountMapping()
{
Table("Accounts");
ComponentAsId(x => x.Key, map =>
{
map.Property(p => p.BSB);
map.Property(p => p.AccountNumber);
});
Property(x => x.Amount);
Bag(x => x.Cards, collectionMapping =>
{
collectionMapping.Table("AccountCard");
collectionMapping.Cascade(Cascade.None);
//How do I map the composite key here?
collectionMapping.Key(???);
},
map => map.ManyToMany(p => p.Column("CardId")));
}
}
Card:
public class Card
{
public virtual CardKey Key { get; set; }
public virtual string Status{ get; set; }
public ICollection<Account> Accounts { get; set; }
}
public class CardKey
{
public virtual int CardId { get; set; }
//Equality members omitted
}
public class CardMapping : ClassMapping<Card>
{
public CardMapping ()
{
Table("Cards");
ComponentAsId(x => x.Key, map =>
{
map.Property(p => p.CardId);
});
Property(x => x.Status);
Bag(x => x.Accounts, collectionMapping =>
{
collectionMapping.Table("AccountCard");
collectionMapping.Cascade(Cascade.None);
collectionMapping.Key(k => k.Column("CardId"));
},
//How do I map the composite key here?
map => map.ManyToMany(p => p.Column(???)));
}
}
Please tell me this is possible!
You were pretty close.
The IKeyMapper that you get in the Action parameter of both the Key and the ManyToMany methods has a Columns method that takes as many parameters as you want, so:
collectionMapping.Key(km => km.Columns(cm => cm.Name("BSB"),
cm => cm.Name("AccountNumber")));
//...
map => map.ManyToMany(p => p.Columns(cm => cm.Name("BSB"),
cm => cm.Name("AccountNumber"))));
I have a many to many relation, and I want to add an intermediate class, which would enable me adding the many to many relations using repository pattern.
What I can't figure out is the mapping.
Here's the structure
public class Product
{
public Product()
{
Categories = new HashSet<Category>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public String Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class PCMap
{
public int product_id { get; set; }
public int category_id { get; set; }
public Product Product { get; set; }
public Category Category { get; set; }
}
And the Mapping
modelBuilder.Entity<Product>()
.HasEntitySetName("PCMap")
.HasMany(p=>p.Categories)
.WithMany(p=>p.Products)
.Map(m=>
{
m.MapLeftKey("product_id");
m.MapRightKey("category_id");
m.ToTable("PCMap");
});
modelBuilder.Entity<PCMap>()
.ToTable("PCMap");
modelBuilder.Entity<PCMap>().HasKey(k => new
{
k.category_id,
k.product_id
});
modelBuilder.Entity<PCMap>()
.HasRequired(p=>p.Product)
.WithMany()
.HasForeignKey(p => p.product_id);
modelBuilder.Entity<PCMap>()
.HasRequired(p => p.Category)
.WithMany()
.HasForeignKey(p => p.category_id);
Here's the error that I get..
How do I fix this ?
The way you've set it up. PCMap is a non entity and is just used to facilitate the M:N join under the hood.
Product p = new Product();
p.Categories ...
Category c = new Category();
c.Products ...
So beacuse you've already defined PC as part of the Product Entity definition here.
.Map(m=>
{
m.MapLeftKey("product_id");
m.MapRightKey("category_id");
m.ToTable("PCMap");
});
I don't believe you need to (or it's possible to) define it again, separately below. Try deleting all this code.
modelBuilder.Entity<PCMap>()
.ToTable("PCMap");
modelBuilder.Entity<PCMap>().HasKey(k => new
{
k.category_id,
k.product_id
});
modelBuilder.Entity<PCMap>()
.HasRequired(p=>p.Product)
.WithMany()
.HasForeignKey(p => p.product_id);
modelBuilder.Entity<PCMap>()
.HasRequired(p => p.Category)
.WithMany()
.HasForeignKey(p => p.category_id);