These are my two classes. I did the mapping and the configuration.
Please instruct me how do I put data into this.
class Actor
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList <Movies> Movie{ get; set; }
}
class Movies
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList <Actor> Actors{ get; set; }
}
I'm not sure what exactly you are asking here, but I'll try to answer what I think you are asking.
In order to map a many-to-many relationship to the database with Fluent NHibernate for the classes in your question would be as simple as
public class ActorMap : ClassMap<Actor>
{
public ActorMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Movies)
.Table("Actors_Movies")
.ParentKeyColumn("ActorId")
.ChildKeyColumn("MovieId")
.LazyLoad()
.Cascade.AllDeleteOrphan();
}
}
public class MovieMap : ClassMap<Movie>
{
public MovieMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(x => x.Actors)
.Table("Actors_Movies")
.ParentKeyColumn("ActorId")
.ChildKeyColumn("MovieId")
.Inverse();
}
}
The junction table in the database is transparent to the relationship in code; consider this an artifact of the impedance mismatch of mapping object oriented code to a relational database.
Now, if you needed to store additional data against the junction table, you would need to expose a type in code to represent the junction table, and add properties to this type for the data you wish to store
public class MovieActorAssociation
{
public MovieActorAssociation(Actor actor, Movie movie)
{
Actor = actor;
Movie = movie;
}
protected MovieActorAssociation()
{
}
public virtual Actor Actor { get; protected set; }
public virtual int Id { get; protected set; }
public virtual Movie Movie { get; protected set; }
public virtual string SomeOtherProperty { get; set; }
public static bool operator ==(MovieActorAssociation left, MovieActorAssociation right)
{
return Equals(left, right);
}
public static bool operator !=(MovieActorAssociation left, MovieActorAssociation right)
{
return !Equals(left, right);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((MovieActorAssociation)obj);
}
public override int GetHashCode()
{
return Id;
}
protected bool Equals(MovieActorAssociation other)
{
return Id == other.Id;
}
}
Now, to map the relationship
public class ActorMap : ClassMap<Actor>
{
public ActorMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
HasMany(x => x.MovieAssociations)
.Cascade.AllDeleteOrphan()
.Inverse()
.KeyColumn("ActorId")
.Not.KeyNullable();
}
}
public class MovieActorAssociationMap : ClassMap<MovieActorAssociation>
{
public MovieActorAssociationMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.SomeOtherProperty);
References(x => x.Movie)
.Not.Nullable()
.Cascade.SaveUpdate()
.Column("MovieId")
.UniqueKey("Movie_Actor");
References(x => x.Actor)
.Not.Nullable()
.Cascade.SaveUpdate()
.Column("ActorId")
.UniqueKey("Movie_Actor");
}
}
public class MovieMap : ClassMap<Movie>
{
public MovieMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
HasMany(x => x.ActorAssociations)
.Cascade.AllDeleteOrphan()
.Inverse()
.KeyColumn("MovieId")
.Not.KeyNullable();
}
}
And to use
var actor = new Actor { Name = "Actor Name" };
var movie = new Movie { Name = "Movie Name" };
var association = new MovieActorAssociation(actor, movie);
actor.MovieAssociations.Add(association);
movie.ActorAssociations.Add(association);
// some way of getting a Session
var session = SessionFactory.GetCurrentSession();
using (var transaction = session.BeginTransaction())
{
session.Save(actor);
transaction.Commit();
}
You may want to consider exposing methods for adding and removing MovieActorAssociation types to ensure association correctness i.e. that an association added to the property collection on an Actor references the Actor instance that owns the collection and not another actor instance. You would want similar logic to this for Movie types too.
Related
I have three classes Product, Stock, StockId. Stock has a composite Id of Token and InternalCode, these two properties are encapsulated in a new class StockID.
My classes definitions are:
public class Producto
{
public virtual long Id { get; set;
public virtual Stock Stock { get; set; }
... Some other (not so important ) properties ...
public Producto()
{
...
}
}
public class Stock
{
public virtual StockID ID { get; set; }
public virtual Producto ProductoStock { get; set; }
... other properties ...
}
public class StockID
{
public virtual string Token { get; set; }
public virtual long CodigoInterno { get; set; }
public override int GetHashCode()
{
int hash = GetType().GetHashCode();
hash = (hash * 31) ^ CodigoInterno.GetHashCode();
hash = (hash * 31) ^ Token.GetHashCode();
return hash;
}
public override bool Equals(object obj)
{
var other = obj as StockID;
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return this.CodigoInterno == other.CodigoInterno &&
this.Token == other.Token;
}
}
And these are the maps:
public class ProductoMap : ClassMap<Producto>
{
public ProductoMap()
{
Id(x => x.Id);
// ... Other Maps and References
References<Stock>( p => p.Stock);
}
}
public class StockMap : ClassMap<Stock>
{
public StockMap()
{
CompositeId( stock => stock.ID)
.KeyProperty(x => x.CodigoInterno)
.KeyProperty(x => x.Token);
// ... Other Maps and References
References(x => x.ProductoStock);
}
}
This is the Exception I get...
Foreign key (FKD33BD86ADE26BE17:Producto [Stock_id])) must have same number of columns as the referenced primary key (Stock [CodigoInterno, Token])
How can I fix this?
You have to map the property that matches your class Producto with your class Stock. It should look like this:
public class StockMap : ClassMap<Stock>
{
public StockMap()
{
CompositeId( stock => stock.ID)
.KeyProperty(x => x.CodigoInterno)
.KeyProperty(x => x.Token);
// ... Other Maps and References
//This is ok since you have (i believe) a column in
//in your table that stores the id from Producto
References(x => x.ProductoStock).Column("idProducto");
//Maybe you have to put the name of the column with
//the id of Producto in your table Stock because
//you could use it later.
}
}
And the ProductMap class:
public class ProductoMap : ClassMap<Producto>
{
public ProductoMap()
{
Id(x => x.Id);
// ... Other Maps and References
//If you have the id from Stock in your Stock class
//this will work.
//References<Stock>( p => p.Stock);
//If you don't have it, this will work:
HasOne<Stock>(x => x.Stock).PropertyRef("Name of the column whit the product id in the Stock table");
}
}
The second choice seem to be better from my point of view. Anyway, if you have a legacy database o something like that is not a really big difference.
I have a one-to-many relationship and cannot get cascade to work, once I set cascade I just get "object references an unsaved transient instance ...".
My mapping looks like this
public class SharedDetailsMapping : ClassMap<SharedDetails>
{
public SharedDetailsMapping()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Foos);
}
}
public class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.SharedDetails).Cascade.SaveUpdate();
}
}
The classes like this
public class Foo
{
public Foo()
{
SharedDetails = new SharedDetails();
SharedDetails.Foos.Add(this);
}
public Foo(SharedDetails sharedDetails)
{
SharedDetails = sharedDetails;
SharedDetails.Foos.Add(this);
}
public virtual Guid Id { get; set; }
public virtual SharedDetails SharedDetails { get; set; }
}
public class SharedDetails
{
public SharedDetails()
{
Foos = new List<Foo>();
}
public virtual Guid Id { get; set; }
public virtual IList<Foo> Foos { get; set; }
}
I then want to create Foos without having to save the SharedDetails first if its a new Foo, like so:
using (var transaction = _session.BeginTransaction())
{
var shared = new SharedDetails();
var fooOne = new Foo(shared);
_session.SaveOrUpdate(fooOne);
var fooTwo = new Foo(shared);
_session.SaveOrUpdate(fooTwo);
transaction.Commit();
}
Cannot figure out what I have done wrong, it works ofcurse if I save the SharedDetails first but that is why I have Cascade setup.
In your SharedDetailsMapping, modify your HasMany to add .Inverse():
public class SharedDetailsMapping : ClassMap<SharedDetails>
{
public SharedDetailsMapping()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Foos)
.Inverse();
}
}
This instructs NHibernate that Foo owns the relationship, which will help it save objects in the relationship in the right order (in this case, the SharedDetails has to get saved first so we have its ID when Foo is saved).
Further info on the purpose/when to use Inverse: NHibernate's inverse - what does it really mean?
The TL;DNR version:
If you have a bidirectional relationship in your classes (HasMany on one side, References on the other), the HasMany should have .Inverse().
I have an issue with mapping, simplified my relationship looks like this.
I have parent class:
public abstract class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
and two subclasses:
public class UploadedFileDocument : DocumentType
{
}
public class ApplicationFormDocument : DocumentType
{
}
mapped like this:
public DocumentTypeMap()
{
Schema("Core");
Id(x => x.Id);
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
public class UploadedFileDocumentMap : SubclassMap<UploadedFileDocument>
{
}
public class ApplicationFormDocumentMap : SubclassMap<ApplicationFormDocument>
{
}
Then I have another entity with a FK to DocumentType, mapped like this:
public FileConversionMap()
{
Schema("Core");
Id(x => x.Id);
References(x => x.Application).Not.Nullable();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
my issue is, when I retrieve rows from the DB like this:
Session.Query<FileConversion>().AsQueryable();
all the rows come back with the DocumentType being of type DocumentType, not of the child type (ie the actual type of that property, ie. when i do .GetType(), either UploadedFileDocument or ApplicationFormDocument)
Apologies if this is just me being dim. But how can I determine which type of DocumentType I have ... is my mapping wrong?
When you look at your generated SQL (adding .ShowSQL() to your .Database method), do you see the Type being entered? You should see something similar to:
INSERT
INTO
"Core_DocumentType"
(Name, Type)
VALUES
(#p0, 'ApplicationFormDocument');
select
last_insert_rowid();
#p0 = 'afd' [Type: String (0)]
Using the mappings you provided, it looks fine and I could return the DocumentType (using SQLite) just fine.
Here's the code I used to reproduce it. I didn't have your FileConversion object, so please verify that it matches what you need.
DocumentType
public class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class DocumentTypeMap : ClassMap<DocumentType>
{
public DocumentTypeMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
}
UploadFileDocument/ApplicationFormDocument
public class UploadedFileDocument : DocumentType
{
public virtual string ContentType { get; set; }
}
public class ApplicationFormDocument : DocumentType
{
}
public class UploadFileDocumentMap :
SubclassMap<UploadedFileDocument>
{
public UploadFileDocumentMap()
{
GenerateMap();
}
void GenerateMap()
{
Map(x => x.ContentType);
}
}
public class ApplicationFormDocumentMap :
SubclassMap<ApplicationFormDocument>
{
}
FileConversion
public class FileConversion
{
public virtual int Id { get; set; }
public virtual DocumentType DocumentType { get; set; }
}
public class FileConversionMap : ClassMap<FileConversion>
{
public FileConversionMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
}
The tests I used (using machine.specifications):
Context
public class when_discriminating_on_subclass
{
static IList<FileConversion> results;
Establish context = () =>
{
using (var session = DataConfiguration.CreateSession())
{
using (var transaction = session.BeginTransaction())
{
var upload = new UploadedFileDocument
{ Name = "uploaded", ContentType = "test" };
var form = new ApplicationFormDocument
{ Name = "afd" };
session.Save(form);
session.Save(upload);
var formConversion =
new FileConversion { DocumentType = form };
var uploadConversion =
new FileConversion { DocumentType = upload };
session.Save(formConversion);
session.Save(uploadConversion);
transaction.Commit();
}
using (var transaction = session.BeginTransaction())
{
results = session.Query<FileConversion>().AsQueryable().ToList();
transaction.Commit();
}
}
};
Specifications
It should_return_two_results = () =>
results.Count.ShouldEqual(2);
It should_contain_one_of_type_uploaded_file = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(UploadedFileDocument))
.ShouldEqual(1);
It should_contain_one_of_type_application_form = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(ApplicationFormDocument))
.ShouldEqual(1);
}
Debugging through the assertions, I can see that the collection comes back with the two types:
Are you casting them back to the base type anywhere in your mappings or classes?
I'm confused by the following NHibernate behaviour:
Domain classes and mappings:
public class Category
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
private IList<Product> _products;
public virtual IList<Product> Products
{
get { return new List<Product>(_products).AsReadOnly(); }
}
public Category()
{
_products = new List<Product>();
}
}
public class Product
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual Category Category { get;set; }
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Schema("dbo");
Table("tProducts");
Id(x => x.ID);
Map(x => x.Name);
References(x => x.Category).Column("CategoryID");
}
}
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Schema("dbo");
Table("tCategories");
Id(x => x.ID);
Map(x => x.Name);
HasMany(x => x.Products)
.KeyColumn("CategoryID")
.Access.CamelCaseField(Prefix.Underscore)
.Inverse()
.Cascade.All();
}
}
Code causing trouble:
Category category;
using (var session = sessionFactory.OpenSession())
{
category = session.Get<Category>(1);
}
using (var session = sessionFactory.OpenSession())
{
var products = category.Products; // exception
}
Why do I get no session exception when I'm trying to get products? I've got a session here! How to avoid this exception (I prefer 2 sessions here, and I want to keep loading lazy)?
Thanks in advance!
Can you re-attach the object to your new session either:
ISession.Update(myDetachedInstance);
or, if the object has not been changed:
ISession.Lock(myDetachedInstance, NHibernate.LockMode.None);
For more info, see: http://intellect.dk/post/Detached-objects-in-nHibernate-and-Lazy-loading.aspx
I got this same error and it was due to the WPF UI holding on to references to objects from the old nhibernate session after I clicked the "refresh" button which called refresh on the data context which disassociated all cached objects in the session.
I needed to make sure "NotifyChange" was being fired to make the UI update (and that the UI was listening to it).
I am using Fluent NHibernate to map entities and am having a problem getting a repository to give a resultset. In the console, the SQL does not show but other repositories do. I have a feeling that it is because of the Mappings but can't tell why. The table name includes an underscore which is one of the only differences between this repo and others. My question is what could cause the sql not to be executed?
Here is my setup.
Entity:
public class Org
{
public virtual int ID { get; set; }
public virtual string IndividualName { get; set; }
public virtual string GroupName { get; set; }
public virtual string AddressLine1 { get; set; }
public virtual string AddressLine2 { get; set; }
}
Mapping:
public class OrgMap : ClassMap<Org>
{
public OrgMap()
{
Table(#"Org_Updates"); // Also tried Table("Org_Updates");
Map(x => x.ID);
Map(x => x.IndividualName);
Map(x => x.GroupName);
Map(x => x.AddressLine1, "PhysicalLocationAddress");
Map(x => x.AddressLine2, "PLAddr2");
Repository:
public class OrgRepository : RepositoryBase<Org>, IOrgRepository
{
public IList<Org>GetTop50()
{
var query = All().AsList();
return query;
}
}
RepositoryBase:
public class OrgRepositoryBase<T> : RepositoryBase<T> where T : class
{
public OrgRepositoryBase()
{
var registry = ServiceLocator.Current.GetInstance<EventListenerRegistry>();
registry.RegisterListenerForType<T>(GetType(), EventType.Save);
registry.RegisterListenerForType<T>(GetType(), EventType.Delete);
}
protected override ISession GetSession()
{
return UnitOfWork.Current.GetSessionFromContext<ISession>(typeof (OrgModel));
}
protected override Type ModelType
{
get { return typeof (OrgModel); }
}
}
}
As I said before, the other repositories that use other entites/mapping work. I can use this repository, exchanging the entity/mapping that it implements and it will work. I'm pretty sure it's because of hte mapping but can't tell which part. I have checked the table name and the column names.
Thanks for the help
I am making an assumption here that your table does have a primary key. If so, should you not map that primary key
public class OrgMap : ClassMap<Org>
{
public OrgMap()
{
Table(#"Org_Updates");
Id(x => x.ID);
Map(x => x.IndividualName);
Map(x => x.GroupName);
Map(x => x.AddressLine1, "PhysicalLocationAddress");
Map(x => x.AddressLine2, "PLAddr2");