One to One HasRequired configuration not working - c#

I have two sets of objects: Coupon and DiscountScheme.
Each have a connected object of {Type}Action, and identical configurations.
When making a request for Coupon, I do not get anything back,
but the same query for DiscountScheme works as expected
A condensed version of the classes (The full code and sql for the tables can be found here):
public class CouponAction
{
public int Id { get; set; }
public virtual Coupon Coupon { get; set; }
}
public class Coupon
{
public int Id { get; set; }
public virtual CouponAction Action { get; set; }
}
public class DiscountSchemeAction
{
public int Id { get; set; }
public virtual DiscountScheme DiscountScheme { get; set; }
}
public class DiscountScheme
{
public int Id { get; set; }
public virtual DiscountSchemeAction Action { get; set; }
}
The configuration:
public class CouponActionMap : EntityTypeConfiguration<CouponAction>
{
public CouponActionMap()
{
ToTable("CouponAction");
}
}
public class CouponMap : EntityTypeConfiguration<Coupon>
{
public CouponMap()
{
ToTable("Coupon");
HasRequired(c => c.Action);
}
}
public class DiscountSchemeActionMap : EntityTypeConfiguration<DiscountSchemeAction>
{
public DiscountSchemeActionMap()
{
ToTable("DiscountSchemeAction");
}
}
public class DiscountSchemeMap : EntityTypeConfiguration<DiscountScheme>
{
public DiscountSchemeMap()
{
ToTable("DiscountScheme");
HasRequired(ds => ds.Action);
}
}
The query I am trying to make:
using(var context = new Context()/* My database context, using a custom wrapper framework*/)
{
Console.WriteLine(context.Coupons.ToList()); // nothing
Console.WriteLine(context.DiscountSchemes.ToList()); // the contents of the table
}
If I query the actions table, I do get the contents, but again for CouponAction I do not get the connected Coupon, and for DiscountScheme it works as expected.

The issue is with your 1-to-1 relationship. By default EF expects a 1-to-1 to be using the PKs on both tables. By putting a CouponID on your CouponAction you are not setting a 1-to-1 relationship, you are setting a 1-to-many/many-to-1. Nothing stops several CouponAction records from having the same CouponId. You could put a unique constraint on CouponID, but if that were the case then you may as well have the CouponAction's PK to be the CouponID. Hence, this is why I don't advise using "Id" as a PK name, but rather CouponId vs. DiscountId, etc.
If the relationship between coupon and action is truly 1-to-1 then get rid of the CouponId on the Action table, and ensure you're using the same ID value across both tables for the related records. You can test this by changing your mapping to configure EF to use CouponId on the CouponAction as it's PK. Once you do that, you should see your related records coming up.
Alternatively you can establish a many to 1 relationship (HasOne.WithMany()) from Action to Coupon, but no return reference without a CouponActionId on Coupon. Or you can set up a 1-to-many where Coupon contains an ICollection<CouponAction> CouponActions even though you intend to only have one action per coupon. But if it is 1-to-1 then I would highly recommend using the same PK value across both tables.

Related

EF Core - Reuse the same join table entity for several relationships

I want to reuse the same many-to-many relationship table (FileInEntity) for several other objects (Course, Lecture, Game), since they all can have files. Since we have to manually create the many-to-many relationships by creating a join entity, I want to reuse the join entity for the objects (Course, Lecture, Game).
If we look at the table structure, I would like to have the following:
Course: Id, ...
Lecture: Id, ...
Game: Id, ...
FileInEntity: EntityId (this can be either Course.Id, Lecture.Id or Game.Id), FileId
File: Id, ...
(File is base class type with two derived types: Image and Audio)
When I try this approach in .NET Core, I receive the following error message:
Entity type 'FileInEntities' is in shadow-state. A valid model requires
all entity types to have corresponding CLR type.
Is this even possible?
This is my setup:
ModelBase.cs
public class ModelBase
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}
Course.cs
[Table("Courses")]
public class Course : ModelBase
{
private ICollection<FileInEntity> IconsInCourse { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => IconsInCourse.Select(e => e.File).FirstOrDefault();
}
Lecture.cs
// Same as Course.cs
Game.cs
// Same as Course.cs
FileInEntity.cs
[Table("FilesInEntities")]
public class FileInEntity
{
public Guid FileId { get; set; }
public Guid EntityId { get; set; }
public virtual ModelBase Entity { get; set; }
public virtual File File { get; set; }
}
File.cs
[Table("Files")]
public class File : ModelBase
{
// This is the property for which the error occured
private ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
}
FilesInEntitiesMap.cs (Relationship Configuration)
builder.HasOne(p => p.Entity)
.WithMany()
.HasForeignKey(k => k.EntityId);
builder.HasOne(p => p.File)
.WithMany()
.HasForeignKey(k => k.FileId);
FileMap.cs
// This is the key to which the error references to
builder.HasMany("FileInEntities")
.WithOne("Entity")
.HasForeignKey("EntityId");
You won't be able to use the base class ModelBase as the object in the mapping class because c# wont know the actual type coming back from the db. You can look at table per hierarchy inheritance, but I'm still not sure you would be able to use that in a mapping table either. Here is a good article
If your Course.cs, Lecture.cs, and Game.cs are the same and the only difference is type, you could combine them into one class and add an enum property to set the type.
public enum EntityType{
Game = 1,
Lecture = 2,
Course = 3
}
public class MyEntity : ModelBase{
private ICollection<FileInEntity> Icons { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => Icons.Select(e => e.File).FirstOrDefault();
public EntityType EntityType {get;set;} //course, lecture, or game
}
When you care about the type just use a where clause.
Be sure to use Fluent Api in DbContext's OnModelCreating to determine One to One relationship for this tables. (and be sure again correct reference properties are selected)
Missing parts of your codes.
public class ModelBase
{
[Key]// add for primary key
//set none always for primary keys (because guid has no auto increment)
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
}
[Table("Files")]
public class File : ModelBase
{
//make it public
public ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
[NotMapped] //set not mapped
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
//do it same changes for `Lacture.cs`, `Game.cs` and `Course.cs`...
}

My database entity has a generic base BaseEntity<key>, but how to map Id value of base entity to database entity?

How can I interchangeably use entity base Id or database column Id
public partial class Orders : EntityBase<int>
{
protected Orders() { }
public int Orderid { get; set; }
public override int Id { get => base.Id; set => base.Id = Orderid; }
}
Entitybase:
public abstract class EntityBase<T> : IEntity<T>
{
public virtual T Id { get; set; }
}
Question: Can I map this Id of EntityBase and db column's primary key(for eg: order entity's key -> orderId) to sync values ( In app code, either user set Id of base or orderId of entity both should contain same value and also when retrieved also these values, at a given time, should return same value. Is there any way to achieve above synch feature via fluent API in the OnModelCreating() method?
P.S: If you have not understood the question, please say which part does not have clarification, instead of using authority :)
Basically If I understand your question correctly, you want to have a base class with an Id, but in SQL you've named your Id Column differently. You want to Alias your updates so that when you set they Id, they also set the DB Id.
This is more of an architecture, thing, if you really needed to you could write tool that helps you scaffold automatically, but that seems buggy and more complex.
Instead I'll try to fix this problem by modifying your architecture.
A simple way to fix this problem, is just to work with Id's
public partial class Orders : EntityBase<int>
{
protected Orders() { }
[Column("Orderid")]
public int Id { get; set; }
}
You can map a column directly to the Id property, and avoid the whole issue all together.
If you're set on having both OrderId and Id
public partial class Orders : EntityBase<int>
{
protected Orders() { }
public int Orderid { get; set; }
public override int Id { get => Orderid; set => Orderid = value; }
}
but using that architecure is generally a bad idea, It's messy since your EntityBase Is Generic an seems error prone.
If i understand right you need that your Orders.Orderid is an autoincremental-PrimaryKey for your DB table right?
Supposing you're using SQLite (if not probably is similar but not the same words) you can do something like that:
public partial class Orders : EntityBase<int> {
[PrimaryKey, AutoIncrement]
public int Orderid { get; set; }
protected Orders() { }
public override int Id { get => base.Id; set => base.Id = Orderid; }
}
When you create/update your table using the orders "model" SQLite should do the rest.
L-

Insert entity with related entity in TPH architecture

I need to insert an entity (Picture) that holds a related entity (Ad) based on TPH architecture:
Picture model:
public class Picture
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
}
public class AdPicture : Picture
{
public Ad Ad { get; set; }
}
Ad model:
public class Ad
{
// Primary properties
public int Id { get; set; }
public string Title { get; set; }
}
public class AdCar : Ad
{
public int? CubicCapacity { get; set; }
public int? Power { get; set; }
}
I want to insert a new Picture in the AdCar, and I tried:
AdPicture picture = new AdPicture()
{
Ad = _adRepository.GetById(adId),
Filename = newFileName
};
_pictureService.CreateAdPicture(picture);
CreateAdPicture:
public void CreateAdPicture(AdPicture adPicture)
{
_adPictureRepository.Add(adPicture);
_adPictureRepository.Save();
}
But Entity Framework says
*Invalid column name 'AdCar_Id'.*
When I check the SQL command text, I can see
insert [dbo].[Pictures]([Name], [Filename], [URL], [Rank], [Discriminator], [PictureType_Id], [AdCar_Id], [Ad_Id])
values (null, #0, null, #1, #2, #3, #4, null)
It's putting AdCar_Id and Ad_Id, why? How can I insert the Picture related with the AdCar?
First up: You may need to define the key for the subclass explicitly to be Ad_Id as it is assuming the AdCar_Id (or vice-versa).
Something like this in your DbContext:
modelBuilder.Entity<AdPicture>()
.HasRequired(o => o.Ad)
.WithMany().Map(f => f.MapKey("Ad_Id"));
Second: Also, just to check - all of these classes have completely separate tables (TPH) and not tables with just the additional properties specified (TPH) I.e. an AdPicture does not have data in both a Picture and AdPicture table?
Having not done TPH (only TPT) I am unsure of if Entity Framework handles the subclass-ing differently, but I suspect it must. Someone else can hopefully answer that better if you are missing something there.
I found the problem:
I was defining the Navigation property "public IList Pictures { get; set; }" in the Ad Model that was creating an aditional field in the SQL Command "Ad_Id".
As soon as I removed it, the problem was solved and all the theory I had about TPH came togheter again :)
Thanks for your feedback.

Does anyone know why I'm getting this warning from NHibernate/NH Profiler?

"Disabled lazy properies fetching for fully_qualified_type_name beacuse it does not support lazy at the entity level".
This warning was reported by NH Profiler, and as a result, I'm experiencing the dreaded SELECT N + 1 side affect. So if 2200 Subgroup entities are returned, an additional query is being executed to retrieve each InvoicePreference entity (2201 queries total). Something about that relationship seems to be causing the issue.
Here are the entities in question and their respective mappings.
Entity 1
public class Subgroup : Entity
{
public virtual string GroupNumber { get; set; }
public virtual string RUSNumber { get; set; }
public virtual string REANumber { get; set; }
public virtual string CustomerType { get; set; }
public virtual string Name { get; set; }
public virtual IList<IndividualEmployment> Employees { get; set; }
public virtual IList<BenefitsAdministrator> Administrators { get; set; }
public virtual InvoicePreference InvoicePreference { get; set; }
}
Entity 2
public class InvoicePreference : IEntity
{
public virtual Guid Id { get; set; }
public virtual Guid SubgroupId { get; set; }
public virtual bool PaperlessNotifications { get; set; }
}
Mapping 1
public static AutoPersistenceModel ConfigureSubGroup(this AutoPersistenceModel
autoPersistenceModel)
{
return autoPersistenceModel.Override<Subgroup>(map =>
{
map.Table("SubgroupV");
map.Id(s => s.Id).Column(SubGroupPrimaryKeyColumn);
map.Map(s => s.CustomerType, "BAS_Customer_Type");
map.Map(s => s.RUSNumber, "BAS_RUS_Number");
map.Map(s => s.GroupNumber, "BAS_Group_Number");
map.Map(s => s.REANumber, "BAS_REA_Number");
map.HasMany(s => s.Administrators).KeyColumn(SubGroupPrimaryKeyColumn);
map.HasMany(s => s.Employees).KeyColumn(SubGroupPrimaryKeyColumn);
map.HasOne(s => s.InvoicePreference).PropertyRef(i => i.SubgroupId);
});
}
Mapping 2
public static AutoPersistenceModel ConfigureInvoicePreference(this AutoPersistenceModel autoPersistenceModel)
{
return autoPersistenceModel.Override<InvoicePreference>(map =>
{
map.Table("SubgroupInvoicePreference");
map.Schema(RetirementStatementsSchemaName);
});
}
InvoicePreference is referenced as hasone. Since it is lazyloaded by default NHibernate will create a proxy to populate the property InvoicePreference and to do that it needs the identity from InvoicePreference which is not present in the Subgroup. Therefor it has to query for it using the property in the propertyref.
To remedy that do .Not.LazyLoad() and/or .Fetch.Join()
I guess that there is some reason why NH disabled lazy loading "on entity level", which I understand as not creating proxies. There may be several reasons for that. Did you get another warning before? I don't really understand why it disabled "lazy properies", which means that some properties are lazy loaded. This is a feature that is used in the mapping explicitly, but I can't see something like this in your mapping definitions.
To overcome the N+1, you may use Fetch.Join. I had bad experience with that, because the queries get really large. In a complex model, you could hit some database server limits (like max. number of columns of a query). It is mostly better to use batch size, which reduces the number of queries notably. Take a look at my answer to "Nhinerbate lazy loading of reference entity".

How can I loosely couple database columns in a .NET application?

I have two versions of an almost identical database. Below I have created an Example table to demonstrate the basic differences, namely the ID column has changed from an Integer Identity to a GUID and various properties have been updated, in the Example archived has been replaced with readOnly and hidden:
Legacy version:
CREATE TABLE Example
(
--Data Identity (maps to DbId in the example code)
Id int IDENTITY PRIMARY KEY,
--Example columns
SomeValue varchar(50),
AnotherValue int,
--Data Properties
Archived bit
)
New version:
CREATE TABLE Example
(
--Data Identity (maps to DbId in the example code)
Id uniqueidentifier PRIMARY KEY,
--Example columns
SomeValue varchar(50),
AnotherValue int,
--Data Properties
ReadOnly bit,
Hidden bit
)
I need to be able to use an O/R mapper such as NHibernate to connect to one or other of these database versions. I would like to be able to tell the application which version to use through settings in a configuration file.
My initial plan was to create a common interface for the business logic and use an IoC container such as Unity to swap between the relevant concrete classes in the configuration file.
Below is an example of the code I created to test this theory:
public interface IDataIdentity
{
object Id { get; }
}
public class LegacyDataIdentity : IDataIdentity
{
public virtual long DbId { get; set; }
public object Id
{
get { return DbId; }
}
}
public class NewDataIdentity : IDataIdentity
{
public virtual Guid DbId { get; set; }
public object Id
{
get { return DbId; }
}
}
public interface IDataProperties
{
bool ReadOnly { get; set; }
bool Hidden { get; set; }
}
public class LegacyDataProperties : IDataProperties
{
public virtual bool Archived { get; set; }
public bool ReadOnly
{
get { return Archived; }
set { Archived = value; }
}
public bool Hidden
{
get { return Archived; }
set { Archived = value; }
}
}
public class NewDataProperties : IDataProperties
{
public virtual bool ReadOnly { get; set; }
public virtual bool Hidden { get; set; }
}
public class DataItem
{
public DataItem(IDataIdentity dataIdentity, IDataProperties dataProperties)
{
DataIdentity = dataIdentity;
DataProperties = dataProperties;
}
public IDataIdentity DataIdentity { get; set; }
public IDataProperties DataProperties { get; set; }
}
public class Example : DataItem
{
public Example(IDataIdentity dataIdentity, IDataProperties dataProperties)
: base(dataIdentity, dataProperties)
{
}
public virtual string SomeValue { get; set; }
public virtual int AnotherValue { get; set; }
}
Can anyone advise if this is possible (specifically with Unity and NHibernate) and if so how to create the relevant NHibernate mapping files?
Alternatively, can anyone suggest any solution to the problem using any other methods or other IoC and O/R mapping tools (commercial or open source)?
Many thanks,
Paul
Why not abstract your data provider, implementing 2 versions (one with nhibernate mappings for the legacy data item, and one for the new data item).
To paraphrase your code slightly (simplified for clarity):
public abstract class AbstractData
{
public abstract string SomeValue { get; set; }
public abstract bool ReadOnly { get; set; }
//etc.
}
public interface IDataProvider
{
AbstractData Get(object id);
}
public class LegacyData : AbstractData
{
// Implement AbstractData, and
public virtual long Id { get { return m_Id; } set { m_Id = value; };
private long m_Id;
}
public class LegacyDataNHibernateProvider : IDataProvider
{
public LegacyDataProvider()
{
// Set up fluent nhibernate mapping
}
public AbstractData Get(object id)
{
// Interpret id as legacy identifier, retrieve LegacyData item, and return
}
};
// Same again for new data provider
This way, you aren't tied to nhibernate (or a database, for that matter), and you can specify concrete classes with correctly typed identifiers (which nhibernate can handle). This is the approach I'm taking, where I currently have to map against an existing database's SPs, but will later migrate to a new ORM-based system.
Maybe I'm not understanding your question correctly, but it sounds to me like you need to implement something like the "Factory Pattern".
I recently used the factory pattern to code (c#) to two data layers when the company I work for was switching from JDE to SAP. I was able to flip a config switch to switch between the two data layers, and the GUI wouldn't know any different.
Here's a couple links I found:
http://msdn.microsoft.com/en-us/library/ms954600.aspx
http://www.allapplabs.com/java_design_patterns/factory_pattern.htm
http://en.wikipedia.org/wiki/Abstract_factory_pattern
As far as NHibernate goes, I'm not familiar with it... sorry. Hope this helps.
Hard to recommend without knowing the full picture, but.... You can create an abstruction in the SPs by having SP return identical dataset for both table structures.
Another abstruction I am thinking of, is you can actually specify different hybernate mapping files, and initialize Hibernate with a different file depending on what database you connect to.

Categories