I can't get my head around why this isn't working..
I have a relatively clean entity model consisting of POCOs created with DDD in mind (while probably not following most rules even loosely).
I am using Fluent NHibernate to do the mapping. I am also using SchemaExport to create the database schema, with minimum input from me on how to do it. NHibernate is free to choose the best way.
I have two entities with Many-to-many relationships with each other (non-interesting code removed); MediaItem and Tag; MediaItems can have many tags, Tags can be applied to many MediaItems, and I want collections on both sides so I can easily get at stuff.
(A bit of a formatting issue below, sorry)
MediaItem:
public class MediaItem
{
private IList<Tag> _tags;
public virtual long Id { get; set; }
public virtual string Title { get; set; }
public virtual IEnumerable<Tag> Tags { get { return _tags; } }
public MediaItem()
{
_tags = new List<Tag>();
}
public virtual void AddTag(Tag newTag)
{
_tags.Add(newTag);
newTag.AddMediaItem(this);
}
}
Tag:
public class Tag
{
private IList<MediaItem> _mediaItems;
public virtual long Id { get; set; }
public virtual string TagName { get; set; }
public virtual IEnumerable<MediaItem> MediaItems { get { return _mediaItems; } }
public Tag()
{
_mediaItems = new List<MediaItem>();
}
protected internal virtual void AddMediaItem(MediaItem newItem)
{
_mediaItems.Add(newItem);
}
}
I have tried to be smart about only exposing the collections as IEnumerable, and only allowing adding items through the methods. I also hear that only one side of the relationship should be responsible for this - thus the contrived AddMediaItem() on Tag.
The MediaItemMap looks like this:
public class MediaItemMap : ClassMap<MediaItem>
{
public MediaItemMap()
{
Table("MediaItem");
Id(mi => mi.Id);
Map(mi => mi.Title);
HasManyToMany<Tag>(mi => mi.Tags)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
The Tag mapping looks like this:
public class TagMap : ClassMap<Tag>
{
public TagMap()
{
Table("Tag");
Id(t => t.Id);
Map(t => t.TagName);
HasManyToMany<MediaItem>(mi => mi.MediaItems)
.Access.CamelCaseField(Prefix.Underscore)
.Inverse();
}
}
Now I have some test code that drops the database schema, recreates it (since I am shotgun debugging my brains out here), and then runs the following simple code:
Tag t = new Tag { TagName = "TestTag" };
MediaItem mi = new MediaItem { Title = "TestMediaItem" };
mi.AddTag(t);
var session = _sessionFactory.OpenSession();
session.Save(mi);
Yep, this is test code, it will never live past the problem in this post.
The MediaItem is saved, and so is the Tag. However, the association between them is not. NHibernate does create the association table "MediaItemsToTags", but it doesn't attempt to insert anything into it.
When creating the ISessionFactory, I specify ShowSQL() - so I can see all the DDL sent to the SQL server. I can see the insert statement for both the MediaItem and the Tag tables, but there is no insert for MediaItemsToTags.
I have experimented with many different versions of this, but I can't seem to crack it. Cascading is one possible problem, I've tried with Cascade.All() on both sides, Inverse() on both sides etc., but no dice.
Can anyone tell me what is the correct way to map this to get NHibernate to actually store the association whenever I store my MediaItem?
Thanks!
You need to define the many-to-many table and parent and child key columns:
public class MediaItemMap : ClassMap<MediaItem>
{
public MediaItemMap()
{
Table("MediaItem");
Id(mi => mi.Id);
Map(mi => mi.Title);
HasManyToMany<Tag>(mi => mi.Tags)
.Table("MediaItemsToTags").ParentKeyColumn("Id").ChildKeyColumn("Id")
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
The syntax is identical in TagMap because both key columns are named "Id".
Related
So I created a new simple project just to help a friend.
So I made a class Customer which has a list of Stuff
So far so good, now with the mapping and storing the relationsship. I went to map in accordance with fluent nhibernate class maps and ended up with the following
public class CustomerMap : ClassMap<Customer> {
Id(p => p.Id).GenerateBy.Guid();
HasMany(p => p.Stuff).Access.CamelCaseField().KeyColumn("Id").Inverse().Cascade.AllDeleteOrphan();
}
public class StuffMap : ClassMap<Stuff> {
Id(p => p.Id).GeneratedBy.Guid();
Reference(p => p.Customer).Column("CustomerId).Not.Nullable();
}
and my classes
public class Customer {
private ISet<Stuff> stuff = new HashSet<Stuff>()
public virtual IEnumerable<Stuff> Stuff => stuff;
public void AddStuff(Stuff newstuff) {
newstuff.Customer = this;
stuff.Add(stuff);
}
}
public class Stuff {
public virtual Customer Customer { get; set; }
}
All this works good and when I create a new Customer and add one of more Stuff elements into the collection using the method AddStuff and commits the transaction it gets correctly written to the database.
However now the strange begins, when I make a test like the following
[TestMethod]
public void TestStuffAndCustomer() {
var customer = session.Add(new Customer());
customer.AddStuff(new Stuff());
session.Flush();
var customer = session.Query<Customer>().Single();
customer.Stuff.Should().HaveCount(1);
}
The assertion of the collection fails with reason that the count of the collection is 0. However if I debug the test and check the collection it contains one element. The assertion fails regardless however.
So what is wrong with this setup?
I think you add new Customer and Stuff to Customer on session, but without saving them you flush the session.
I was trying to create a generic method to update an Entity and all it's collection properties from a detached object. For example:
public class Parent{
public int Id { get; set; }
public string ParentProperty { get; set; }
public List<Child> Children1 { get; set; }
public List<Child> Children2 { get; set; }
}
public class Child{
public int Id { get; set; }
public string ChildProperty { get; set; }
}
So, my first intention was to use something like this:
Repository<Parent>.Update(parentObj);
It would be perfect have a magic inside this method that update Parent properties and compare the list of Children of the parentObj to the current values in database and add/update/remove them accordingly, but it's too complex to my knowledge about EF/Reflection/Generic... and so I tried a second more easier way like this:
Repository<Parent>.Update(parentObj, parent => parent.Children1
parent => parent.Children2);
This method would be a little harder to use, but yet acceptable. But how I think the second parameter had to be params Expression<Func<TEntity, ICollection<TRelatedEntity>>>[] relatedEntities I had problems to specify multiple TRelatedEntity. So my try was to 3rd step with no success yet...
Now I tried to call a method to update Parent and a sequence of methods to update Childreen, like this:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And the code:
public virtual void Update(TEntity entityToUpdate)
{
context.Entry(entityToUpdate).State = EntityState.Modified;
}
public virtual void UpdateChild<TRelatedEntity>(TEntity entityToUpdate, Func<TEntity, object> keySelector, Expression<Func<TEntity, ICollection<TRelatedEntity>>> relatedEntitySelector) where TRelatedEntity: class
{
var entityInDb = dbSet.Find(keySelector.Invoke(entityToUpdate));
var current = relatedEntitySelector.Compile().Invoke(entityToUpdate);
var original = relatedEntitySelector.Compile().Invoke(entityInDb);
foreach (var created in current.Except(original))
{
context.Set<TRelatedEntity>().Add(created);
}
foreach (var removed in original.Except(current))
{
context.Set<TRelatedEntity>().Remove(removed);
}
foreach (var updated in current.Intersect(original))
{
context.Entry(updated).State = EntityState.Modified;
}
context.Entry(entityInDb).State = EntityState.Detached;
}
First problem was to get original values, because when I call dbSet.Find the entity is already in context (context.Entry(entityToUpdate).State = EntityState.Modified;).
So I tried to change order calling first Child:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And now I have the error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
In summary, it would be very nice the first way, but I would be satisfied with the second/third too.
Thanks very much
Edit 1
Please, I need a native solution or using Automapper (which we already use in the project), because my customer don't like external dependencies and if we need to adapt something to the project, like working with Attached objects to update their related entities, so GraphDiff mencioned in the comments doesn't fit our needs (and VS 2015 RC crashed when I tried to install the package for tests)
Have you considered getting the object from the DB and using AutoMapper to modify all the property values?
I mean:
var obj = GetObjectFromDB(...);
AutoMapObj(obj, modifiedObj);
SaveInDb();
I have two POCO classes, each containing a collection of the other. I am attempting to configure a many-to-many relationship between these classes. However, when I load the context, EF does not populate any of the collections of each object. How can this be done automatically?
public class POCO1
{
public int ID { get; set; }
public virtual ICollection<POCO2> POCO2s { get; set; }
}
public class PCOO2
{
public int ID { get; set; }
public virtual ICollection<POCO1> POCO1s { get; set; }
}
public class POCOContext : DbContext
{
public DbSet<POCO1> POCO1s { get; set; }
public DbSet<POCO2> POCO2s { get; set; }
modelBuilder.Entity<POCO1>()
.HasKey(p => p.ID);
modelBuilder.Entity<POCO1>()
.HasMany(p => p.POCO2s).WithMany(p => p.POCO1s);
modelBuilder.Entity<POCO2>()
.HasKey(p => p.ID);
modelBuilder.Entity<POCO2>()
.HasMany(p => p.POCO1s).WithMany(p => p.POCO2s);
}
public class Test
{
static void Main(string[] args)
{
using (var context = new POCOContext())
{
context.Database.Initialize(true);
var p1 = context.POCO1s.Create();
var p2 = context.POCO2s.Create();
p1.POCO2s.Add(p2);
p2.POCO1s.Add(p1);
context.saveChanges();
}
// reload context from db
using (var context = new POCOContext())
{
context.POCO1s.ToList()[0].POCO2s.Count(); // == 0, but should be 1
context.POCO2s.ToList()[0].POCO1s.Count(); // == 0, but should be 1
}
}
}
Edit to include more info: from what I have read it should not be necessary to include annotations or even specify keys using Fluent API, and using these conventions EF 5.0 should configure the relations correctly. Indeed this appears to be the case, because the database tables are constructed correctly using code first (there is a POCO1 table, a POCO2 table, and an intersection table, all of which are populated correctly during testing). The problem is when reading the data back out, it does not seem to populate the many to many relationships. Other posts on SO and various tutorials and MSDN documentation suggest that using the proper conventions, lazy loading, and virtual collections should work, but that is not the behavior I'm seeing.
In my case the problem was due to disabled proxy creation on the context (ie context.Configuration.ProxyCreationEnabled = false;) in my actual code. The "automatic collection population" feature described in the original question is known as "lazy loading" and it requires proxy creation to be enabled. This MSDN article has more information.
Also, as mentioned in the original question, this feature works by convention without specifying keys or relationships (using annotations or fluent API).
I am using this article to make a one-to-one relationship between my two objects - Site and WebOptions. Site is already present. a record in WebOptions may or may not be present. When it is, my mappings work fine. When it is not, my system blows up trying to create a new record.
Here is my site class (the important bits)
public class Site : CoreObjectBase
{
public virtual int SiteId { get; set; }
public virtual WebOptions WebOptions { get; set; }
}
And here is my web options class (important parts again)
public class WebOptions : CoreObjectBase
{
public virtual int WebOptionsId { get; set; }
private int SiteId { get; set; }
private Site Site { get; set; }
}
And the mapping for Site is
HasOne<WebOptions>(x => x.WebOptions)
.Cascade.All();
And the mapping for WebOptions is
Id(Reveal.Property<WebOptions>("SiteId")).GeneratedBy.Foreign("Site");
HasOne<Site>(Reveal.Member<WebOptions, Site>("Site"))
.Constrained()
.ForeignKey();
In the data, the table behind Site has no foreighn key field to WebOptions, but the table behind WebOptions contains the SiteId. In my code, I am already getting the site and use site.WebOptions.SomeSetting and would like to keep it that way.
My problem is this. If I deviate from this mapping at all, my model breaks and no weboptions are returned while several records are saved into the weboptions table (duplicates). But, when I try to save a new WebOptions object, I get
Batch update returned unexpected row count from update; actual row
count: 0; expected: 1
I have a repository class with 2 save methods:
public sealed class Repository<T> : IRepository<T> where T : CoreObjectBase
{
public void SaveWithDependence<K>(T entity, K dependant) where K : CoreObjectBase
{
entity.Validate();
dependant.Validate();
using (ITransaction tx = Session.BeginTransaction())
{
Session.SaveOrUpdate(entity);
Session.SaveOrUpdate(dependant);
tx.Commit();
}
}
public void Save(T entity)
{
entity.Validate();
using (ITransaction tx = Session.BeginTransaction())
{
Session.SaveOrUpdate(entity);
tx.Commit();
}
}
}
When no WebOptions is found, I am doing this when making a new one:
var options = site.WebOptions;
if (options == null)
{
options = new WebOptions(site);
site.WebOptions = options;
}
And the constructor looks like this to set the private variables
public WebOptions(Site site)
{
Site = site;
SiteId = site.SiteId;
}
And then to save, I have tried to following:
siteRepository.Save(site);
and
siteRepository.SaveWithDependence(site, options);
and
optionsRepository.Save(options);
and
optionsRepository.SaveWithDependence<Site>(options, site);
All of them return the above error. My session declaration looks like this
sessionFactory =
Fluently.Configure().Database(
FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2005.DefaultSchema("dbo")
.ConnectionString(c => c
.FromConnectionStringWithKey("MyDatabase"))
.AdoNetBatchSize(20))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<SessionManager>())
.ExposeConfiguration(x => x.SetProperty("current_session_context_class", "managed_web"))
.BuildSessionFactory();
I really need to be able to save a new WebOptions record if one doesn't exist, but I can't seem to get it to work with my one-to-one relationship.
Wow, I spent all that time putting that together and then in playing around with it, I removed one line of code - just to see what would happen.
In the constructor for WebOptions I removed this single line:
SiteId = site.SiteId;
for a constructor that looks like this:
public WebOptions(Site site)
{
Site = site;
}
Then, I save only my WebOptions object like this:
optionsRepository.Save(options);
My best guess is that since I am using an ID field for the 'SiteId' property in fluent, fluent doesn't allow me to manually set that value. Setting the private property Site in addition to setting the Site.WebOptions property, must set up the one-to-one relationship for fluent/nhibernate to deduce what value to place into the SiteId field.
Further inspection of the article posted above shows that this is the way it has to be done. I just happened to miss this very important piece of information:
The public constructor, taking a Client parameter, is the one you will use in your code whenever you want to assign a client some alimentary habits, such as: AlimentaryHabits = new AlimentaryHabits(this);. The protected constructor is used internally by NHibernate, and must be present. You can completely ignore it.
I am going to leave this post and answer in the event someone else has this issue and I can save them a little bit of time and frustration.
i am working on a legacy enterprise database that uses multiple columns to retrieve a part number the sql we currently use checks for a part number and if the part number is null then checks for the child part number what would be the best way of combining this logic into a single ef model property (eg Product.PartNumber )
I would like this to be invisible as possible so that only PartNumber is visible externally to the data api
If you are using code-first approach the only way is to do a projection in custom linq-to-entities query:
var products = context.Products
.Select(p => new SomeNonMappedType
{
Id = p.Id,
// All other fields
PartNumber = p.PartNumber ?? p.Child.PartNumber // I hope this will work
});
If you are using EDMX you can use DefiningQuery or QueryView but whole your new entity will be readonly. More about those in another answer.
in theory couldnt you do something like this
public class Part
{
public string PartNumber
{
get
{
return this.PartId ?? this.ChildPartId;
}
}
internal string PartId { get; set; }
internal string ChildPartId { get; set; }
}
public class PartsContext : DbContext
{
public DbSet<Part> Parts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Part>().Ignore(e => e.PartNumber);
base.OnModelCreating(modelBuilder);
}
}
or is this still considered bad