I have an existing database where I have four identical and unrelated tables.
I want to use the same POCO class to describe all four without having to create duplicates of the same class.
This is what my context looks like so far:
class StatsContext : DbContext
{
// [MagicTableAttribute( "map_ratings_vsh" )] -- does something like this exist?
public DbSet<MapRatings> MapRatingsVSH { get; set; }
public DbSet<MapRatings> MapRatingsJump { get; set; }
// 2 more tables using same class
}
class MapRatings
{
public string SteamID { get; set; }
public string Map { get; set; }
public int Rating { get; set; }
[Column( "rated" )]
public DateTime Time { get; set; }
}
My problem is that the existing tables are named "map_ratings_vsh" and "map_ratings_jump", and I cannot use the data annotations TableAttribute because it can only be used on the class.
Is there some other way--maybe the fluent api--to describe my schema?
One way I've found to solve this is to use inheritance.
[Table("map_ratings_vsh")]
public class MapRatingsVSH : MapRatingsBase {}
[Table("map_ratings_jump")]
public class MapRatingsJump : MapRatingsBase {}
public class MapRatingsBase
{
public string SteamID { get; set; }
public string Map { get; set; }
public int Rating { get; set; }
[Column( "rated" )]
public DateTime Time { get; set; }
}
Then you can have your DbContext look like:
public class StatsContext : DbContext
{
public DbSet<MapRatingsVSH> MapRatingsVSH { get; set; }
public DbSet<MapRatingsJump> MapRatingsJump { get; set; }
}
EF shouldn't have any problem understanding that these are two different tables even though the implementation will be in the same place (MapRatingsBase)
You can use the fluent api to map some properties to one table and other properties to another table like this:
modelBuilder.Entity<TestResult>()
.Map(m =>
{
m.Properties(t => new { /* map_ratings_vsh columns */ });
m.ToTable("map_ratings_vsh");
})
.Map(m =>
{
m.Properties(t => new { /* map_ratings_jump columns */ });
m.ToTable("map_ratings_jump");
});
The (hacky) way I've handled this in the past is to return data using a stored procedure or view, and then provide a "AddEntity" or "SaveEntity" method on the DbContext implementation that persists the entity in question
Related
In EF Core 3.1.15 I manage a model with a generic. I would like to store the entities in the same table basis Table-Per-Hierarchy approach (TPH pattern). Below is the model abstracted. The resulting database creates 1 table for Part and descendants with a discriminator (as expected), but instead of 1 table for BaseComputer and descendants it creates a separate table for Computers and a separate table for Laptops (not expected).
namespace EFGetStarted
{
public class BloggingContext : DbContext
{
public DbSet<Computer> Computers { get; set; }
public DbSet<Laptop> Laptops { get; set; }
public DbSet<Part> Parts { get; set; }
}
public abstract class BaseComputer<T> where T : Part
{
[Key]
public int Id { get; set; }
public List<T> Parts { get; set; }
}
public class Computer : BaseComputer<Part>
{
public string ComputerSpecificProperty { get; set; }
}
public class Laptop : BaseComputer<LaptopPart>
{
public string LaptopSpecificProperty { get; set; }
}
public class Part
{
[Key]
public int Id { get; set; }
public string PartName { get; set; }
}
public class LaptopPart : Part
{
public string LaptopSpecificPartProperty { get; set; }
}
}
I tried explicitly specifying the entity as TPH:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseComputer<Part>>()
.HasDiscriminator()
.HasValue<Computer>("Computer")
.HasValue<Laptop>("Laptop");
}
But this fails with the following message:
The entity type 'Laptop' cannot inherit from 'BaseComputer' because 'Laptop' is not a descendant of 'BaseComputer'.
Questions: Is it possible for me to design this model in a TPH pattern? If not, is it because "Laptop is not a descendant of BaseComputer<Part>"? And if that's the case, why is not a considered a descendant and what should I change in the class to make it a descendant?
Is there a better way to accomplish this end-goal of having easily-queryable (and Include-able) cross-sections of a related many-to-many entity stored in the same table?
I started off without implementing TPH in the join table, but that makes consuming one type or another in queries more involved, afaict.
// table Related: [Id]
public class Related
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
public List<RelatedOtherOne> RelatedOtherOnes { get; set; } = new List<RelatedOtherOne>();
public List<RelatedOtherTwo> RelatedOtherTwos { get; set; } = new List<RelatedOtherTwo>();
}
// table RelatedOther: [RelatedId, OtherId, Type]
public abstract class RelatedOther
{
public Guid RelatedId { get; set; }
public Guid OtherId { get; set; }
public Related Related { get; set; }
public Other Other { get; set; }
public abstract RelatedOtherType Type { get; }
}
public class RelatedOtherOne : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.One;
// should be unnecessary, 'Other' should be correct type
public OtherOne OtherOne { get; set; }
}
public class RelatedOtherTwo : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.Two;
// should be unnecessary, 'Other' should be correct type
public OtherTwo OtherTwo { get; set; }
}
public enum RelatedOtherType : int
{
One = 1,
Two = 2
}
// table Other: [Id, OneProp, TwoProp]
public abstract class Other
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
}
public class OtherOne : Other
{
public string OneProp { get; set; }
}
public class OtherTwo : Other
{
public string TwoProp { get; set; }
}
TPH is mapped like this
M2M is mapped like this + discriminator in HasKey()
This gets even more complicated (if not impossible?) when the 'Related' entity evolves into a TPH strategy like the 'Other'.
I have no easy solution but as I stumbled across the same problem I thought I'll share what I have so far.
I found out that I usually need to load all or many types of the relations to the classes of a TPH structure.
So I use the base many-to-many class to load the related objects. Thus this class cannot be abstract:
public class Event2Location
{
[Required]
public Event Event { get; set; }
public int EventId { get; set; }
[Required]
public Location Location { get; set; }
public int LocationId { get; set; }
public byte EntityType { get; set; }
}
The derived class only adds some properties for easier access:
public class Event2Country : Event2Location
{
[NotMapped]
public Country Country
{
get { return base.Location as Country; }
set { base.Location = value; }
}
[NotMapped]
public int CountryId
{
get { return base.LocationId; }
set { base.LocationId = value; }
}
}
In the Event class I have:
public virtual ICollection<Event2Location> Event2Locations { get; set; }
[NotMapped]
public virtual ICollection<Event2Country> Event2Countries => Event2Locations?.OfType<Event2Country>().ToList();
// I should probably add some caching here if accessed more often
[NotMapped]
public virtual ICollection<Event2City> Event2Cities => Event2Locations?.OfType<Event2City>().ToList();
So when I load the joined tables I can use
.Include(e => e.Event2Locations).ThenInclude(j => j.Location)
And I can access the relations of a specific type as needed with the NotMapped Collections.
I still use the derived Event2... classes to add a new relationship.
As you see I have added a column EntityType to the many-to-many class which I use as TPH discriminator. With this column I can also declare which types of Relations/entities I want to load if I do not want to load all.
modelBuilder.Entity<Event2Location>()
.HasDiscriminator<byte>("EntityType")
.HasValue<Event2Location>(0)
.HasValue<Event2Country>(1)
This is surely far from perfect but I finally gave up on optimizing that. First EFCore has to become more mature. Second I want to see how I actually use these structures.
PS: Actually my Location TPH structure has parent-child-relationships within it. Here I did not create a TPH structure for the relation class (as you said - not possible or at least not reasonable). I added ParentType and ChildType. Thus I can determine which relations I actually want to load. Then I fetch the related Locations of the types I need manually on the client side from the result.
I am using Entity Framework 6 Code First and I'm configuring the mapping of my domain model with Fluent API. I don't see how to create a navigation properties for a Table which is a little tricky.
I have several objects which can make noise, I would like to record that noise in a NoiseRecord Table.
I need some kind of conditional mapping, something like that :
modelBuilder.Entity<NoiseRecord>().HasRequired(n=>n.Origine.OrigineType()=="Car").WithMany(c=>c.NoiseRecords);
That would be the mapping of the Car Navigation Property to avoid that, for example, it includes record related to Planes.
Here is my code
public interface INoisy
{
int ID {get; set;}
string OriginType()
...
//And other useful things not related to persistence
}
public class Car : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords { get; set; }
string OrigineType()
{
return "Car";
}
}
public class Plane : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords {get; set;}
string OrigineType()
{
return "Plane";
}
}
And a couple of other classes implement INoisy also.
Below is the NoiseRecord Table.
public class NoiseRecord
{
public int RecordID {get; set;}
public INoisy NoiseOrigine {get; set;}
public double NoiseMagnitude {get; set;}
}
I'm looking for a way to achieve that with Fluent API.
Thank you !
First of all, it is not possible to use interfaces as navigation properties. But you could use an abstract base class for your noise origins
public abstract class NoiseOrigin
{
public NoiseOrigin()
{
this.NoiseRecords = new Collection<NoiseRecord>();
}
public int Id { get; set; }
public ICollection<NoiseRecord> NoiseRecords { get; set; }
}
public class Car : NoiseOrigin {}
public class Plane : NoiseOrigin { }
public class NoiseRecord
{
public int Id { get; set; }
public int OriginId { get; set; }
public NoiseOrigin Origin { get; set; }
public double NoiseMagnitude { get; set; }
}
Your fluent API mapping whould look like this
public class NoiseModelContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Car>().Map(p => p.Requires("Type").HasValue("Car"));
modelBuilder.Entity<Plane>().Map(p => p.Requires("Type").HasValue("Plane"));
}
public DbSet<NoiseOrigin> NoiseOrigins { get; set; }
public DbSet<NoiseRecord> NoiseRecords { get; set; }
}
To get all car noise records your query will look like
using (var db = new NoiseModelContext()) {
var records = db.NoiseRecords.Where(p => p.Origin is Car);
// or like this - the result is the same.
var records2 = db.NoiseOrigins.OfType<Car>().SelectMany(p => p.NoiseRecords);
}
Im working on EF Code first, I like abstraction! so want have ItemCat entity like this:
public abstract class EntityBase
{
public int Id { get; set; }
}
public abstract class TreeBase : EntityBase
{
public int? ParentId { get; set; }
public string Name { get; set; }
public virtual TreeBase Parent { get; set; }
public virtual ICollection<TreeBase> Children { get; set; }
}
public abstract class CatBase : TreeBase
{
public string ImageUrl { get; set; }
public string Description { get; set; }
public int OrderId { get; set; }
}
public class ItemCat : CatBase
{
public stringName { get; set; }
// other fields...
public virtual ICollection<Item> Items { get; set; }
}
My map startgey is table per type.TPT
All base classes for ItemCat are decoreated by abstract keyword. but in Migration i get TreeBases Table in Db, really why? i'm wonder because it's abstract. does my mapping need define any configuration explicitly? im using EF 6
Edit also EF in Migration create Discriminator column for TreeBase table and when i insert Record it has ItemCat value.
Edit
protected override void OnModelCreating(DbModelBuilder mb)
{
//Item
mb.Configurations.Add(new TreeBaseConfig());
mb.Configurations.Add(new CatConfig());
}
public class TreeBaseConfig:EntityTypeConfiguration<TreeBase>
{
public TreeBaseConfig()
{
HasMany(rs => rs.Children).WithOptional(rs => rs.Parent).HasForeignKey(rs => rs.ParentId).WillCascadeOnDelete(false);
}
}
public class CatConfig : EntityTypeConfiguration<CatBase>
{
public CatConfig()
{
//properties
Property(rs => rs.Name).IsUnicode();
Property(rs => rs.ImageUrl).IsUnicode();
Property(rs => rs.Description).IsUnicode();
}
}
Edit
I added ItemCatConfig Class:
public ItemCatConfig()
{
//map
Map(m => { m.ToTable("ItemCats"); m.MapInheritedProperties(); });
}
but get:
The type 'ItemCat' cannot be mapped as defined because it maps
inherited properties from types that use entity splitting or another
form of inheritance. Either choose a different inheritance mapping
strategy so as to not map inherited properties, or change all types in
the hierarchy to map inherited properties and to not use splitting.
You have used TPH (Table Per Hierarchy) when you don't do any ToTable() mapping, and TPC (Table Per Concrete Type) when doing both ToTable() and MapInheritedProperties(). If you want to use TPT (Table Per Type) do only the ToTable() mapping and leave the call to MapInheritedProperties() off, like so:
ToTable("ItemCats");
I'm currently working on an application that works with dynamic questionnaire forms.
forms that are defined in a database like so: Forms > Sections > Controls > Questions.
Each Question can have many business rules such as: required, minlength, maxlength etc etc.
Each BusinessRule is the rule for exactly one Question. However, there are complex business rules that require fetching the value of another question. Therefore each Business Rule can have many Linked Questions to fetch a required value from.
I'm using code first for the first time and have the following defining classes and mapping for this relationship:
public class Question
{
public int Id { get; set; }
public string Name { get; set; }
public int ValueTypeId { get; set; }
public int ParentQuestionId { get; set; }
public virtual ValueType ValueType { get; set; }
public virtual ICollection<Question> ChildQuestions { get; set; }
public virtual ICollection<BusinessRule> BusinessRules { get; set; }
public virtual ICollection<BusinessRule> LinkedBusinessRules { get; set; }
}
public class BusinessRule
{
public int Id { get; set; }
public string ValidationMessage { get; set; }
public string ConditionValue { get; set; }
public int QuestionId { get; set; }
public int BusinessRuleTypeId { get; set; }
public virtual BusinessRuleType BusinessRuleType { get; set; }
public virtual Question Question { get; set; }
public virtual ICollection<Question> LinkedQuestions { get; set; }
}
internal class QuestionConfigruation : EntityTypeConfiguration<Question>
{
public QuestionConfigruation()
{
this.HasMany<Question>(q => q.ChildQuestions)
.WithOptional()
.HasForeignKey(q => q.ParentQuestionId);
this.HasMany(q => q.BusinessRules)
.WithRequired(br => br.Question)
.WillCascadeOnDelete(false);
}
}
internal class BusinessRuleConfiguration : EntityTypeConfiguration<BusinessRule>
{
public BusinessRuleConfiguration()
{
this.HasMany(b => b.LinkedQuestions)
.WithMany(q => q.LinkedBusinessRules)
.Map(map =>
{
map.ToTable("BusinessRuleLinkedQuestions");
map.MapLeftKey("LinkedQuestionId");
map.MapRightKey("BusinessRuleId");
});
}
}
This results in the following being produced in the database:
Finally, my questions:
[Resolved]
Why is the many to many join table ignoring the mappings for the table name and keys i've specified in the BusinessRuleConfiguration?
[/Resolved]
When I try to insert a test form using a custom initialiser by doing this:
var companyName = new Question
{
Name = "CompanyName",
ValueTypeId = _typeRepository.GetValueType(ValueTypes.String).Id,
BusinessRules = new List<BusinessRule>{
new BusinessRule
{
BusinessRuleTypeId = _typeRepository.GetBusinessRuleType(BusinessRuleTypes.required).Id,
ValidationMessage = "Company name is required.",
}
}
};
var form = new Form
{
Sections = new List<Section>
{
new Section(){
Controls = new List<Control>
{
new Control{
ControlTypeId = _typeRepository.GetControlType(ControlTypes.Textbox).Id
Questions = new List<Question>
{
companyName,
}
}
}
}
}
}
I get the following error:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
I've spent far too long on this now trying resolve it, so help from someone with more experience with EF and Code First would be greatly appreciated. I'm starting to regret the choice of code first, but i don't know if it is code first thats the problem or my understanding of it.
Thanks!
before we get into the entity framework things I believe that your models does not reflect your business problem correctly, and as a result the invalid relationships are causing the errors.
Can you please provide more information regarding the business problem around LinkedBusinessRules
BusinessRules
Based on what you have mentioned I see your question/businessrule classes looking more like this.
Things to note,
Instead of having 'one question has on to many business rule AND that business rule has many questions'
Its now 'one question Has one to many business rules'.
public class Question
{
public int Id { get; set; }
public string Name { get; set; }
public int ValueTypeId { get; set; }
public int ParentQuestionId { get; set; }
public virtual ValueType ValueType { get; set; }
public virtual ICollection<BusinessRule> BusinessRules { get; set; }
}
public class BusinessRule
{
public int Id { get; set; }
public string ValidationMessage { get; set; }
public string ConditionValue { get; set; }
public int BusinessRuleTypeId { get; set; }
public virtual string BusinessRuleType { get; set; }
}
If you still have problems and have good db design skills, use a tool such as http://salardbcodegenerator.codeplex.com/
Then you can create your db first and generate classes based on that.
Resolved!
First issue was because I had forgotten to add the BusinessRule configuration as point out by #Slauma and #Abhijit.
The second problem of saving a new form was actually to do with a number of problems that i didnt detail in the question.