I'm trying to do a pretty simple thing in Entity Framework.
I have a product that has zero or more parameters and these parameters will be mapped to their own tables. However, I'm unable to get this to work. I've been trying to get the mappings right and then use the migrations to see what the database is supposed to look like. I know that this is very simple in NHibernate, but I'm forced against my will to use Entity Framework v6.
Background
These are my entities:
namespace Entities
{
public class EntityState
{
public int Id { get; set; }
}
public class ProductState : EntityState
{
public virtual ICollection<ProductParameterState> Parameters { get; set; }
}
public abstract class ProductParameterState : EntityState
{
}
public class ColorParameterState : ProductParameterState
{
public virtual string Color { get; set; }
}
public class SizeParameterState : ProductParameterState
{
public virtual int Size { get; set; }
}
}
I would like to store this in the following schema:
How to do this?
My attempts
Table-per-class
I tried mapping using TPC:
namespace Mappings
{
public class ProductMap : EntityTypeConfiguration<ProductState>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Name);
HasMany(x => x.Parameters);
}
}
public class ColorParameterMap : EntityTypeConfiguration<ColorParameterState>
{
public ColorParameterMap()
{
HasKey(x => x.Id);
Property(x => x.Color);
Map(x =>
{
x.ToTable("ColorParameters");
x.MapInheritedProperties();
});
}
}
public class SizeParameterMap : EntityTypeConfiguration<SizeParameterState>
{
public SizeParameterMap()
{
HasKey(x => x.Id);
Property(x => x.Size);
Map(x =>
{
x.ToTable("SizeParameters");
x.MapInheritedProperties();
});
}
}
}
But this gives the error The association 'ProductState_Parameters' between entity types 'ProductState' and 'ProductParameterState' is invalid. In a TPC hierarchy independent associations are only allowed on the most derived types..
Don't use an inheritence strategy
So I tried to remove the MapInheritedProperties, but then it wants to create an additional, and unwanted, table:
CreateTable(
"dbo.ProductParameterStates",
c => new
{
Id = c.Int(nullable: false, identity: true),
ProductState_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.ProductStates", t => t.ProductState_Id)
.Index(t => t.ProductState_Id);
I don't want this. I am able to get rid of this one by removing the Parameters property in Product, but then I'm not able to use the Parameters of a Product.
Am I asking for too much or is it possible?
You can use TPC, but the relationship must be bidirectional with explicit FK defined (which I guess is the opposite of "independent association" mentioned in the error message).
Add inverse navigation property and FK property to your base entity:
public abstract class ProductParameterState : EntityState
{
public int ProductId { get; set; }
public ProductState Product { get; set; }
}
and use the same entity configurations as in your first attempt, except for the ProductMap where you either remove the following
HasMany(x => x.Parameters);
or change it to
HasMany(e => e.Parameters)
.WithRequired(e => e.Product)
.HasForeignKey(e => e.ProductId);
Related
I received this error from Entity Framework this morning:
A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.
The error refers to a very simple One-To-Many relationship between User and Address
public class User
{
public Guid Id { get; set; }
public virtual IList<Address> Addresses { get; set; }
}
public class Address
{
public Guid Id { get; set; }
public User User { get; set; }
}
I'm using Fluent API to configure the relationship between entities, but in this case the relationship seems to simple that at first I didn't specify any particular rule and I let EF "deduce" the relationship:
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
HasKey(a => a.Id);
Property(a => a.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
}
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(u => u.Id);
Property(u => u.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
}
}
But after receiving the error I tried to specify the following rule in the AddressConfiguration:
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
HasKey(a => a.Id);
Property(a => a.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
HasOptional(x => x.User).WithMany(); // I tried adding this
}
}
After doing that, I tried generating a new automatic migration, and this is what I obtained:
public partial class AddressFixMigration : DbMigration
{
public override void Up()
{
DropForeignKey("dbo.Addresses", "User_Id", "dbo.Users");
AddColumn("dbo.Addresses", "User_Id1", c => c.Guid());
CreateIndex("dbo.Addresses", "User_Id1");
AddForeignKey("dbo.Addresses", "User_Id1", "dbo.Users", "Id");
}
public override void Down()
{
DropForeignKey("dbo.Addresses", "User_Id1", "dbo.Users");
DropIndex("dbo.Addresses", new[] { "User_Id1" });
DropColumn("dbo.Addresses", "User_Id1");
AddForeignKey("dbo.Addresses", "User_Id", "dbo.Users", "Id");
}
}
I found this very odd. The Db seemed ok even before, with the Addresses Table having a Foreign Key "User_Id", but after specifying the one-to-many relationship in the Configuration file EF wants to create a different Foreign Key. Why??
I also tried to specify HasOptional(x => x.User).WithMany().Map(x => x.MapKey("User_Id"));, but in that case when I try to create the automatic migration I receive the following error:
User_Id: Name: Each property name in a type must be unique. Property name 'User_Id' is already defined.
It seems like something is clearly wrong with my DB, but I can't see what and why.
There are other techniques, but I prefer to put the FK in explicitly:
public class Address
{
public Guid Id { get; set; }
public Int? User_Id { get; set; }
public User User { get; set; }
}
Then use this fluent code:
HasOptional(x => x.User).WithMany(x => x.Addresses).HasForeignKey(x => x.User_Id);
I realized that I needed to change my Configuration file as it follows:
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
HasKey(a => a.Id);
Property(a => a.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
HasOptional(x => x.User).WithMany(x => x.Addresses).Map(x => x.MapKey("User_Id"));
}
}
.WithMany() (without parameter) is for when there is no navigation property on the other side of the relationship, while in this case I needed to spacify .WithMany(x => x.Addresses) because User actually contains a list of Addresses.
I have a "Group" class and a "GroupSummaryLevel" class, codes are given below. There is a one-to-one relation between these entities in DB. I need the "GroupSummaryLevel" as a property in Groups class. It is supposed to be a very simple join like
(SELECT g.Id FROM GroupSummaryLevel g WHERE g.AcctGroup = GroupID)
Unfortunately, I wasn't able to figure this out how to do with NHibernate. The many answers I saw here is no help to me. I would appreaciate any inputs from the more experienced NHibernate users out there. Thanks in advance.
public class Group : DomainEntity
{
public virtual string GroupId { get; set; }
public virtual string GroupName { get; set; }
public virtual GroupSummaryLevel GroupSummaryLevel { get; set; }
}
public class GroupSummaryLevel : DomainEntity
{
public virtual int Id { get; set; }
public virtual string AcctGroup { get; set; }
public virtual GroupSummaryLevel Parent { get; set; }
public virtual IList<GroupSummaryLevel> Children { get; set; }
public GroupSummaryLevel()
{
Children = new List<GroupSummaryLevel>();
}
}
The mapping I have done did not work so far. My mapping codes are as below:
public GroupMap()
{
Table("Groups");
LazyLoad();
Id(x => x.GroupId).GeneratedBy.Assigned().Column("GroupID").CustomType<TrimmedString>();
Map(x => x.GroupName).Column("GroupName").CustomType<TrimmedString>().Not.Nullable();
HasOne(x => x.GroupSummaryLevel).Cascade.None().ForeignKey("AcctGroup");
}
public GroupSummaryLevelMap()
{
Table("GroupSummaryLevel");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AcctGroup).Column("AcctGroup").CustomType<TrimmedString>().Not.Nullable();
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
}
Note: I also need to do a self-join for GroupSummaryLevel, and no success with that either. Any recommendations for that will also be appreciated :)
I would say, that your one-to-one is not driven by primary/foreign keys, but by property-ref. So the Group should map the summary by saying something like this:
...if you want to find related SummaryLevel, pass my <id> into column mapped as AcctGroup
public GroupMap()
{
...
HasOne(x => x.GroupSummaryLevel)
.Cascade.None()
//.ForeignKey("AcctGroup")
.PropertyRef(gsl => gsl.AcctGroup)
;
}
public GroupSummaryLevelMap()
{
...
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
References(x => x.Parent, "AcctGroup");
}
NOTEs for completeness, as discussed in comments:
In this scenario, when the "child" has reference to parent - it really calls for one-to-many/.HasMany() mapping.
The down side is, that child is represented as a colleciton of children: IList<GroupSummaryLevel>. It is not as straighforward to use, but we can create some virtual property, returning the .FirstOrDefault(). The benefit we get - is lazy loading (not in place with one-to-one).
I have a class:
public class classParty
{
private int _arrivedCount;
public int PartyID {get; private set;}
public DateTime PartyDate {get; private set;}
public int ArrivedCount
{
get
{
return _arrivedCount;
}
set
{
_arrivedCount = value;
}
}
}
I can map the PartyId and the PartyDate but I don't have a column for ArrivedCount (it's a moment in time count, it doesn't persist).
How do I tell EF 4.1 to stop looking for a column named "ArrivedCount"? It's not in the table. It's not going to be in the table. It's simply a property of the object and that's all.
Thanks in advance.
EDIT:
Here's the Fluent API configuration for classParty.
public class PartyConfiguration : EntityTypeConfiguration<classParty>
{
public PartyConfiguration()
: base()
{
HasKey(p => p.PartyID);
Property(p => p.PartyID)
.HasColumnName("PartyID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired();
Property(p => p.PartyDate)
.HasColumnName("PartyDate")
.IsRequired();
ToTable("Party");
}
}
With data annotations:
[NotMapped]
public int ArrivedCount
//...
Or using Fluent API:
modelBuilder.Entity<classParty>()
.Ignore(c => c.ArrivedCount);
modelBuilder.Entity<classParty>().Ignore(x => x.ArrivedCount);
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");
I am new to NHibernate and I am having trouble mapping the following relationships within this class.
public class Category : IAuditable
{
public virtual int Id { get; set; }
public virtual string Name{ get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> SubCategories { get; set; }
public Category()
{
this.Name = string.Empty;
this.SubCategories = new List<Category>();
}
}
Class Maps (although, these are practically guesses)
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.ParentCategory)
.Nullable()
.Not.LazyLoad();
HasMany(x => x.SubCategories)
.Cascade.All();
}
}
Each Category may have a parent category, some Categories have many subCategories, etc, etc
I can get the Category to Save correctly (correct subcategories and parent category fk exist in the database) but when loading, it returns itself as the parent category.
I am using Fluent for the class mapping, but if someone could point me in the right direction for just plain NHibernate that would work as well.
By convention, Fluent NHibernate will look at "Category_Id" as foreign key column. It won't figure out your "ParentCategoryId" column. Unless you rename your self-referencing column to "Category_Id", you have to clarify the column name for both parent and child relationships.
For category without parent (absolute-parent category), whose reference column is null, it's reasonable to return itself as parent or null depending on how NHibernate handles it since you choose eager loading.
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.ParentCategory)
.Column("ParentCategoryId") // Many-To-One : parent
.Nullable()
.Not.LazyLoad();
HasMany(x => x.SubCategories)
.Cascade.All().Inverse().KeyColumn("ParentCategoryId"); //One-To-Many : chidren
}
}
Ok so on the HasMany(x=>x.SubCategories) you need to add Inverse() to the call chain and also give it the column name which I'm assuming is "ParentCategoryId" given the mapping of the parent category, of course this depends on your conventions too.
If you were to post your table stucture I can give you a complete solution.