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.
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.
When I insert my objects, they recognize they are one-to-many and the foreign key is correctly placed in the many side table.
When I retrieve my objects, they do not recognize the one-to-many on the one side table so I cannot access the ICollection of the many side objects. Specifically a Null Reference Exception is thrown when trying to access the collection/
In the explanation below, Incident is the one side and Disturbance is the many side. An Incident is associated with many Disturbances, but a Disturbance is a part of only one Incident.
Disclaimer: due to some project constraints and some modules being built on top of other modules we are using Entity Framework in our DAL and have models cross cutting Business/Data. This may factor into the issue. I'm aware this isn't ideal, but this is where we are at and I haven't seen anything that explicitly says you cannot use EF like this.
I have an Incident defined like this:
public class Incident
{
public Incident()
{
}
public Incident(List<Disturbance> sortedDisturbances)
{
StartTime = sortedDisturbances[0].StartTime;
Disturbances = new List<Disturbance>(sortedDisturbances);
}
[Key]
public int IncidentID { get; set; }
public virtual ICollection<Disturbance> Disturbances { get; set; }
[Column(TypeName="datetime2")]
public DateTime? StartTime { get; set; }
}
I had to add a parameterless constructor to deal with errors resulting from Entity Framework trying to use a parameterless constructor in certain areas.
I have a Disturbance defined like this :
public class Disturbance : IComparable<Disturbance>
{
[Key]
public int DisturbanceID { get; set; }
[Column(TypeName = "datetime2")]
public DateTime StartTime { get; set; }
[Column(TypeName = "datetime2")]
public DateTime EndTime { get; set; }
public int CompareTo(Disturbance other)
{
if (this.StartTime < other.StartTime)
return 1;
if (this.StartTime > other.StartTime)
return -1;
return 0;
}
}
I haven't read anything that said implementing an interface would break anything in Entity Framework so I did it.
This is how I add an Incident:
Business Layer:
private void MakeIncident(List<Disturbance> DisturbancesToAggregate)
{
Incident incidentToInsert = new Incident(DisturbancesToAggregate);
_iDAL.InsertIncident(incidentToInsert);
}
Data Layer:
public void InsertIncident(Incident incidentToInsert)
{
using (var context = new InternalContext())
{
context.Incident.Add(incidentToInsert);
context.SaveChanges();
}
}
The problem is that when I access my Incidents:
public IEnumerable<DomainModel.Disturbance> GetProcessedDisturbances()
{
List<DomainModel.Disturbance> processedDisturbances = new List<DomainModel.Disturbance>();
using(var context = new InternalContext())
{
foreach(var i in context.Incident)
{
foreach(var d in i.Disturbances)
{
processedDisturbances.Add(d);
}
}
}
return processedDisturbances;
}
The i.Disturbances Collection causes a Null Reference Exception. Is there something I need to call to force the context to get the Disturbances? Am I doing something blatantly wrong?
My ideas (I don't like any of them and don't want to do any of them):
1. Explicitly put the IncidentID on the Disturbance table (not even sure if this would work)
2. Force a lookup table by adding an ICollection of Incidents to Disturbances (its not a many-to-many relationship and I think this would prevent me from being able to clear all Disturbances from an Incident)
3. Explicitly define the relationship when the model is created. (I don't like the idea of having to do this, plus I think EF is half way there because it is inserting correctly.
Its happening because of lazy loading in EF. We need to Eagerly loading the data. To know more about them, please refer the link below.
https://msdn.microsoft.com/en-in/data/jj574232.aspx
After quite some digging and confusing error messages, I've arrived at this sample (which I believe to be the smallest example reproducing the issue). I can almost certainly conclude that the issue appears due to the entity linked to the one that I'm returning.
[OperationContract]
[WebGet(UriTemplate = "Stations")]
List<Station> GetStations();
public List<Station> GetStations()
{
List<Station> stations = new List<Station>();
using (Context context = new Context())
foreach (Station station in context.Stations.Include(element => element.Records))
//stations.Add(station.Copy());
stations.Add(station);
return stations;
}
It works if I activate the line with Copy() (which creates a new instance of a station and copies over all the properties except for the records, which it creates by itself). However, when I just add the stations without creating a copy (regardless of whether I keep the records, nullify them or set en empty list), it doesn't roll well.
Since I used Include(), the objects being disposed isn't the issue anymore. The error message I get says in the console like so.
http://localhost:25760/MyService.svc/Stations net::ERR_CONNECTION_RESET
Googling that gave me a lot of references to Apache (I'm running it on IIS), PHP (I'm building it on .NET) and security issues with certificates (I'm not using any and the other calls work well).
So my suspicion is either that the error message is misleading coming from a confused computer or that I'm missing something in my setup. The autogenerated classes reflect the foreign key I added to the tables and look like this.
alter table Records
add constraint FkStationId
foreign key (StationId)
references Stations (Id)
public partial class Record
{
public System.Guid Id { get; set; }
public Nullable<System.Guid> StationId { get; set; }
...
public virtual Station Station { get; set; }
}
public partial class Station
{
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Station() { this.Records = new HashSet<Record>(); }
public System.Guid Id { get; set; }
...
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Record> Records { get; set; }
}
I see nothing that leads me to any idea on how to trouble-shoot it. This error shouldn't happen. On the other hand - Fukushima shouldn't happen neither. But it did.
Finally I got this running.
Turn off proxy creation.
Turn off lazy loading.
Make virtual property ignorable to serialization.
Load in the linked entity explicitly.
The first two items are done in the constructor of the context.
public partial class Context : DbContext
{
public Context() : base("name=ContextConnection")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
...
}
The third is done by attributing one of the virtual properties that lead to circular dependency as not valid for serialization.
public partial class Station
{
...
[IgnoreDataMemeber]
public virtual ICollection<Record> Records { get; set; }
}
The last one is including or omitting the navigational property to be (or not to be displayed). In this case, it made sense to jam in the information about stations into each record. The stations can be presented without the records, though.
public List<Station> GetStations()
{
using (Context context = new Context())
return context.Stations
.ToList();
}
public List<Record> GetRecords()
{
using (Context context = new Context())
return context.Records
.Include(record => record.Station)
.ToList();
}
Having said that, there will be dragons. This approach leads to a lot of work as it requires to manually re-edit the auto-generated files each time they're re-created. So i went with Code First, instead.
I am using Entity Framework 4.3 Code First, and I have problem with updating many-to-many relationships.
I defined the following classes:
public abstract class Entity
{
[Column(Order = 0)]
[Key]
public int Id { get; set; }
[Timestamp]
[Column(Order = 1)]
public byte[] Version { get; set; }
}
public class Video : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public TimeSpan Length { get; set; }
public virtual ICollection<Coworker> Coworkers { get; set; }
}
public class Coworker : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
When the database is created, the schema look right:
There is a Videos, Coworkers and VideoCoworkers table too, without
I use repository pattern in an N-Tier application to access database, my Insert and Update method looks like this:
public T Insert(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
session.Context.Set<T>().Add(entity);
}
}
public T Update(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<T>().Attach(entity);
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
When I update an entity, I create the entity object from a DTO, that's why use DbSet.Attach instead of selecting it and updating the properties one-by-one.
When I initialize the database, I add some test data:
Create 3 Coworkers, where I set first and last name. (A, B, C)
Create 3 Videos, where I set title, description and length, and also set some coworkers. First video has A,B, second has B,C and third has A,C.
When I list the Videos from code, I can see that Video.Coworkers collection is filled with good values, and when I query the link table (VideoCoworkers) in SQL Server Management Studio, it also looks good.
My problem is
when I update for example the title of the Video, it works. But when I try to delete from Video2 the existing coworkers (B and C), and try to add coworker A, then the relationship is not updated. It also does not work when I only try to add new coworker, or only try to delete one. I create the entity which is used as the parameter of the Update() method by creating a new Video entity with a new collection of Coworkers (which are selected from the database with Find() method by Id).
What is the correct way to update many-to-many relationships?
But when I try to delete from Video2 the existing coworkers (B and C),
and try to add coworker A, then the relationship is not updated.
Without using a generic repository the correct procedure would be:
using (var session = new DatabaseSession())
{
video2 = session.Context.Set<Video>().Include(v => v.Coworkers)
.Single(v => v.Id == video2Id);
coworkerA = new Coworker { Id = coworkerAId };
session.Context.Set<Coworker>().Attach(coworkerA);
video2.Coworkers.Clear();
video2.Coworkers.Add(coworkerA)
session.Context.SaveChanges();
}
The essential part is that you must load or attach the entity in its original state, change the entity, i.e. remove and add children, and then save the changes. EF's change detection will create the necessary INSERT and DELETE statements for the link table entries. The simple procedure to set the state to Modified you are trying in your generic Update method is suited only for updating scalar properties - like changing the video title - but won't work for updating relationships between entities.
For solve this problem:
attach the entity to context
load the collection(the collection is not loaded, because )
change the state of entity to modified
save changes
So your code for update should be like this:
public Video Update(Video entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<Video>().Attach(entity);
session.Context.Entry(entity).Collection(p => p.Coworkers).Load();
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
Please refer here to see how to save master detail in asp.net mvc with database first. Hopefully it will give you the idea about the code first. You may also have a look at knokout.js example
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".