How to map a reference of a CompositeId using Fluent NHibernate - c#

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.

Related

NHibernate code mapping bag join two tables

I have the following definition for a Transaction (as in purchase details) object :
public class Transaction : MappingObject
{
public virtual int Id { get; set; }
public virtual IList<TransactionProduct> Products { get; set; }
}
public class TransactionMap : ClassMapping<Transaction>
{
public TransactionMap()
{
Table("TRANSACTIONS_TBL");
Id(x => x.Id, m =>
{
m.Column("ID");
m.Generator(Generators.Identity);
});
Bag(x => x.Products, m =>
{
m.Inverse(true);
m.Table("TRANSACTION_PRODUCTS_TBL");
m.Key(k => k.Column("TRANSACTION_ID"));
m.Lazy(CollectionLazy.NoLazy);
},
relation => relation.OneToMany(mapper => mapper.Class(typeof(TransactionProduct))));
}
}
And TransactionProduct is defined like this :
public class TransactionProduct : MappingObject
{
public virtual int TransactionId { get; set; }
public virtual int ProductId { get; set; }
public virtual int Quantity { get; set; }
public override bool Equals(object obj)
{
var t = obj as TransactionProduct;
if (t == null)
return false;
if (TransactionId == t.TransactionId && ProductId == t.ProductId)
return true;
return false;
}
public override int GetHashCode()
{
return (TransactionId + "|" + ProductId).GetHashCode();
}
}
public class TransactionProductMap : ClassMapping<TransactionProduct>
{
public TransactionProductMap()
{
Table("TRANSACTION_PRODUCTS_TBL");
ComposedId(map =>
{
map.Property(x => x.TransactionId, m => m.Column("TRANSACTION_ID"));
map.Property(x => x.ProductId, m => m.Column("PRODUCT_ID"));
});
Property(x => x.Quantity, m => m.Column("QUANTITY"));
}
}
Now, I want to select a transaction and populate the Products array in a single select (I know I can select the transaction then the products but It's bad practice)
So I'm using this :
using (var session = CommonDAL.GetSession())
{
Transaction transactionAlias = null;
TransactionProduct transactionProductAlias = null;
return session.QueryOver(() => transactionAlias).
JoinAlias(() => transactionAlias.Products, () => transactionProductAlias).
Where(() => transactionAlias.Id == transactionProductAlias.TransactionId).List().ToList();
}
This work's quite well but the problem is that if I have a transaction with 2 products, I get 2 transaction objects with 2 products inside them, same goes for if I have a transaction with 4 products, I get 4 transaction objects with 4 products. The transaction objects are good, but the problem is the duplicates.
I can probably solve it with Distinct() but again, I want best practice
I solved it using .TransformUsing(Transformers.DistinctRootEntity) after the Where(...)

EntiyFramework-Update composite key value in join table with TryGetObjectByKey

I‘m using Entity framework code first ,the following model is a join table with composite key
Model code
public class Schedule
{
public int BabyId { get; set; }
public int VaccineId { get; set; }
public Baby Baby { get; set; }
public Vaccine Vaccine { get; set; }
public DateTime Time { get; set; }
}
Entity framework config code
public sealed class ScheduleConfig : EntityTypeConfiguration<Schedule>
{
public ScheduleConfig()
{
HasKey(q =>
new
{
q.BabyId,
q.VaccineId
});
HasRequired(t => t.Baby)
.WithMany(t => t.Schedules)
.HasForeignKey(t => t.BabyId);
HasRequired(t => t.Vaccine)
.WithMany(t => t.Schedules)
.HasForeignKey(t => t.VaccineId);
ToTable("Schedule", "dbo");
}
}
the following code update tables in database
public bool Update(T t)
{
var entityName = GetEntityName<T>();
object originalItem;
var key = ((IObjectContextAdapter)Context).ObjectContext.CreateEntityKey(entityName, t);
if (((IObjectContextAdapter)Context).ObjectContext.TryGetObjectByKey(key, out originalItem))
{
((IObjectContextAdapter)Context).ObjectContext.ApplyCurrentValues(key.EntitySetName, t);
}
Context.SaveChanges();
return true;
}
The problem is when I change one of key values ,the code inside If block in update method never execute and the table never updates.
My question is should I change my Model or is there any better code that can handle this problem.
Any idea?

NHibernate How do i put data in many to many mapping

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.

NHibernate Envers: Cannot insert duplicate key in object

I'm using Envers to audit tables, but it's creating some audit tables for unknown/absent tables.
It's looks like a Many To Many relation audit table for Many To One relations.
Is this right? If it's, Why?
dbo.HorarioFixo - OK
dbo.HorarioFixo_Auditoria - OK
dbo.HorarioFixo_JanelaHorarioFixo_Auditoria - NOK
dbo.JanelaHorarioFixo - OK
dbo.JanelaHorarioFixo_Auditoria - OK
But when I try to remove/delete and HorarioFixo I'm getting an error.
The error I'm getting:
NHibernate.Exceptions.GenericADOException
could not execute batch command.[SQL: SQL not available]
em NHibernate.Engine.ActionQueue.BeforeTransactionCompletionProcessQueue.BeforeTransactionCompletion()
em NHibernate.Impl.SessionImpl.BeforeTransactionCompletion(ITransaction tx)
em NHibernate.Transaction.AdoTransaction.Commit()
em Foo.Testes.Servicos.TesteCanalDeTransmissaoService.RemoveDependenciasCorretamente() na TesteCanalDeTransmissaoService.cs: line 195
System.Data.SqlClient.SqlException
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
The statement has been terminated.
The statement has been terminated.
This is the SQL duplicated:
exec sp_executesql N'INSERT INTO HorarioFixo_JanelaHorarioFixo_Auditoria (REVTYPE, REV, HorarioFixoId, JanelaHorarioFixoId) VALUES (#p0, #p1, #p2, #p3)',N'#p0 tinyint,#p1 int,#p2 bigint,#p3 bigint',#p0=2,#p1=3,#p2=1,#p3=2 go
All this is a part of the code. If you need something more, leave a comment.
My classes:
public class Entidade
{
protected Entidade();
public virtual long Id { get; set; }
public virtual long Version { get; set; }
public abstract override bool Equals(object obj);
public override int GetHashCode();
}
public class Horario : Entidade
{
protected Horario()
{
}
}
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRemessa { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRetorno { get; set; }
}
public class JanelaHorarioFixo : Entidade
{
public virtual TimeSpan HorarioInicio { get; set; }
public virtual TimeSpan? HorarioLimite { get; set; }
}
My mappings:
public class HorarioMap : ClassMapping<Horario>
{
public HorarioMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
}
}
public class HorarioFixoMap : JoinedSubclassMapping<HorarioFixo>
{
public HorarioFixoMap()
{
Property(x => x.Frequencia);
Bag(x => x.JanelasRemessa, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
Bag(x => x.JanelasRetorno, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
}
}
public class JanelaHorarioFixoMap : ClassMapping<JanelaHorarioFixo>
{
public JanelaHorarioFixoMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
Property(x => x.HorarioInicio, m => m.NotNullable(true));
Property(x => x.HorarioLimite, m => m.NotNullable(false));
}
}
NH and Envers configurations:
var ormHelper = ORMHelperUtils.GetORMHelper();
var mapper = new MyConventionModelMapper();
_config = new Configuration();
mapper.AddMappings(ormHelper.GetMappings());
mapper.AddMapping(typeof(REVINFOMap));
ormHelper.SetupApplicationNeeds(_config);
_config.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
_config.SetProperty(Environment.CurrentSessionContextClass, "call");
if (ormHelper.UseEnvers)
{
var classesDominio = ormHelper.GetDomainTables();
if (classesDominio.Any())
{
var envers = new FluentConfiguration();
envers.Audit(classesDominio);
envers.SetRevisionEntity<REVINFO>(e => e.Id, e => e.Date, new CustomRevisionListener());
_config.SetEnversProperty(ConfigurationKey.AuditTableSuffix, "_Auditoria");
_config.IntegrateWithEnvers(envers);
}
}
I've just changed my class to
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> Janelas { get; set; }
}
And added a property to JanelaHorarioFixo to identify the type. But the table dbo.HorarioFixo_JanelaHorarioFixo_Auditoria is still there, and I don't know why.
If you use unidirectional one-to-many, Envers needs a link table to be able to have correct history.
If you use bidirectional one-to-many, no link table is needed.
See this answer.

How to use the auto created table by the DbContext (OnModelCreating(DbModelBuilder))

I Created a Many to Many Relationship between two tables
The table MoviesHashTags was auto created by the DbContext.
I want to be able to send a query to MoviesHashTags table with Linq
The problem is that I don't have any model of it and the DbContext not giving me any option to use it. for example something like that :
_db.MoviesHashTags.Select(i =>i.Id).Where(i => i.HashTagId==3)
Is there a way to do this without _db.Database.ExecuteSqlCommand("QUERY")
Example:
I want to take all the Id's that have HashTagId=3
TABLE MoviesHashTags:
-------------------------------------------------------------------------
TABLE Movies:
[DataContract]
public class Movie
{
[DataMember]
public long Id { get; set; }
*
*
*
[DataMember]
public ICollection<HashTag> HashTagsCollection { get; set; }
public Movie()
{
HashTagsCollection = new HashSet<HashTag>();
}
TABLE HashTag:
[DataContract]
public class HashTag
{
[DataMember]
public long HashTagId { get; set; }
*
*
*
[DataMember]
public ICollection<Movie> MoviesCollection { get; set; }
public HashTag()
{
MoviesCollection = new HashSet<Movie>();
}
DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Movie>().
HasMany(c => c.HashTagsCollection).
WithMany(p => p.MoviesCollection).
Map(
m =>
{
m.MapLeftKey("Id");
m.MapRightKey("HashTagId");
m.ToTable("MoviesHashTags");
});
}
ANSWER: Thanks to #DigitalD
db.Movies.Where(m => m.HashTagsCollection.Any(h => h.HashTagId == hashTag.HashTagId)).Select(m => m.Id);
Linq should be able to handle the following:
var HashTag = _db.HashTags.Find(3); //or whatever method you need to get a single hashtag
var Movies = _db.Movies.Where(m => m.HashTagsCollection.Contains(HashTag))
.Select(m => m.Id);

Categories