Below is my base class for Domain entities:
public interface IBaseEntity
{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
public class BaseEntity : IBaseEntity
{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
}
public class ExternalSystem : BaseEntity
{
public string Name { get; set; }
public string ConnectionUrl { get; set; }
public ICollection<ExternalSystemRules> ExternalSystemRules { get; set; }
}
public ExternalSystemRules : BaseEntity
{
public string RuleName { get; set; }
public string ConfiguredBy { get; set; }
public int ExternalSystemId { get; set; }
public ExternalSystem ExternalSystem { get; set; }
public ICollection<TaskSchedular> TaskSchedulars { get; set; }
}
public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystem>
{
public void Configure(EntityTypeBuilder<ExternalSystem> builder)
{
builder.ToTable("ExternalSystem");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemId");
builder.HasKey(e=>e.Id);
}
}
public class ExternalSystemRulesConfiguration : IEntityTypeConfiguration<ExternalSystemRules>
{
public void Configure(EntityTypeBuilder<ExternalSystemRules> builder)
{
builder.ToTable("ExternalSystemRules");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemRuleId");
builder.HasKey(e=>e.Id);
builder.HasOne(d=>d.ExternalSystem)
.WithMany(p=>p.ExternalSystemRules)
.HasForeignKey(p=>p.ExternalSystemId)
.HasConstraintName("FK_ExternalSystemRules_ExternalSystemId");
builder.Navigation(p=>p.ExternalSystem)
.IsRequired()
.AutoInclude();
}
}
public class MyDatabaseContext : DbContext
{
private readonly IConfiguration _configuration;
public MyDatabaseContext(IConfiguration configuration)
{
_configuration = configuration;
Database.EnsureCreated();
}
public DbSet<ExternalSystem> ExternalSystem {get; set; }
public DbSet<ExternalSystemRules> ExternalSystemRule {get; set; }
public void Save()
{
this.SaveChanges();
}
}
I already had existing database created so I created all this domain models and configuration based on existing database tables and relationships.
Now when I am trying to get list of ExternalSystems like below :
var myDatabaseContext = new MyDatabaseContext();
var externalSystems = myDatabaseContext.ExternalSystem.ToList();
This returns the list of ExternalSystems but my "ExternalSystemRules" navigation property is null. All the other related child entities are null as well.
Now, I don't want to explicitly keep on using .Include() to load related entities. I want to use default feature of entity framework core of eager loading other related entities when querying parent entity.
What might be the problem here?
Database table:
ExternalSystem:
ExternalSystemId(PK) Name ConnectionUrl
ExternalSystemRules:
ExternalSystemRuleId(PK) RuleName ConfiguredBy ExternalSystemId(F.K)
This isn't a problem with your configuration, but how you query your data. You should manually include the relational record / navigation properties, you are looking for:
var externalSystems = myDatabaseContext
.ExternalSystem
.Include(es => es.ExternalSystemRules)
.ToList();
I would advise against using AutoInclude, though (*). While it might not seem to make a difference, when you have tens or hundreds of objects to query, performance will degrade very rapidly, once your dataset grows. Everytime you query a set with AutoInclude enabled, you will get all its navigation properties with it, whether you need it, or not. In addition, this will also apply, to all entity-types derived from this entity. If you decide to use it anyway, you can disable it for single queries by using .IgnoreAutoIncludes().
A problem common to both approaches (Include(), as well as AutoInclude()) are hierachies. They will work for very simple models, but once you try to map a hierachy - think of something like a tree, where a Rule can have sub-rules of the same type, you might run into problems with self-referencing loops and need to manually project.
There is a nice article explaining the problem here: https://khalidabuhakmeh.com/ef-core-and-aspnet-core-cycle-issue-and-solution
A better way of querying your data, would be to use "view models", as to avoid returing unused or sensitive data to your clients.
public class ExternalSytemVm
{
public int Id {get; set;}
public IEnumerable<ExternalSystemRulesVm> Rules {get; set;}
/* ...*/
}
public class ExternalSytemRulesVm
{
public int Id {get; set;}
public string Name {get; set;}
/* ...*/
}
var externalSystems = myDatabaseContext
.ExternalSystem
.Select(es => new ExternalSystemVm {
Id = es.Id
Rules = es.ExternalSystemRules.Select(esr => {
/* ... */
})
})
.ToList();
(*) If you are absolutely sure, that you will always need all of your properties, with all of the navigation properties in each and every query in your application, this might be fine.
Documentation: https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager
Add the AutoInclude() to your Fluent API configuration as follows (similar to what you already have for ExternalSystemRules):
public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystem>
{
public void Configure(EntityTypeBuilder<ExternalSystem> builder)
{
builder.ToTable("ExternalSystem");
builder.Property(e=>e.Id).HasColumnName("ExternalSystemId");
builder.HasKey(e=>e.Id);
builder.Navigation(e => e.ExternalSystemRules)
.AutoInclude();
}
}
Related
I'm having some trouble to get into EF Core relationship.
I didn't know how to search it properly, so I've not found what I need, but I got somewhere.
I have these two classes:
Expense:
public class Expense : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public List<ExpenseType> Types { get; set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
ExepenseType:
public class ExpenseType : Entity
{
#region properties
public string Name { get; private set; }
public string Description { get; private set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseTypeIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
During the ToListAsync in ExpenseType, the EF adds the column "expenseId" to the query, but this column does not exist.
My database has three tables, one for each class, and one for the relationship.
(Expense, ExpenseType and Expense_ExpenseType)
By looking for the solution here on StackOverflow I found that I should have a class for the third table.
Here it is:
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them.
So ExpenseType hasn't any Expense.
I'm not sure what I should do now.
Should I Map using optionsBuilder? How?
Should I ReWrite the database?
If you want to create Many-to-Many relationship, you have several options how to do it:
Create additional class how you described. In this case EF will create table and you can get access to get values only from this table.
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
You may don't create class and just describe in the context relationship. Where you will describe everything and EF will create by yourself this table. But from the app you will not see this table. You have to use this variant if you don't want to extend table with additional fields.
modelBuilder
.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
For this relationship you can read more here
But in your case you don't need to use Many-to-Many. That's why if you don't want to add propertie ExpanseTypeId or ExpenseId in your model you can describe it like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Expense>()
.HasMany<ExpenseType>(o => o.Types) //It is your list of expense types.
.WithOne() //Here you can add model for expense. To have an option go back from expense type to expense
.HasForeignKey("ForeignKey");//This key EF will create for you in DB but not in you app model
}
What do you want to use you have to decide. If you have an idea that expense has a lot of expensetypes and each expense type has a lot of expenses. You have to use Many-To-Many how I described.
I think that your main question is "My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them."
so you can do that by creating a nullable foreign key ExpenseTypeId in Expanse class and HashSet of Expanse in ExpeseType class.
Like this:
public class ExpenseType : Entity
{
public ICollection<Expanse> Expanses {get; set;} = new HashSet<Expanse>()
}
public class Expense : Entity
{
public int? ExpanseTypeId {get; set;}
public ExpanseType ExpanseType {get; set;}
}
I have 2 entities and everything works fine except NHibernate won't load the FieldGroupItems property in the second entity on an object.
I suspect it's because there is a circular dependency between the 2 entities.
I really need both ChildGroups and FieldGroupItems. If I remove ChildGroups than FieldGroupItems is loaded fine.
Is there a way to have what I want. The only way I can think of is to use Guid collections instead of object collections to store only the Ids and fetch data manually from code.
Any help is appreciated.
public class FieldGroupItemInstance : TenantBaseEntity
{
public virtual Guid ItemId { get; set; } //ID from the database to update actual object later
public virtual bool IsTemporaryId { get; set; } //true if field group is new (doesn't exist in system)
public virtual IList<QuestionnaireInstanceField> Fields { get; set; }
public virtual IList<QuestionnaireFieldGroupInstance> ChildGroups { get; set; }
public FieldGroupItemInstance()
{
Fields = new List<QuestionnaireInstanceField>();
ChildGroups = new List<QuestionnaireFieldGroupInstance>();
}
}
public class QuestionnaireFieldGroupInstance : TenantBaseEntity
{
public virtual Guid FieldGroupTemplateId { get; set; }
public virtual IList<FieldGroupItemInstance> FieldGroupItems { get; set; } //Each repeated group of instances
//public virtual FieldGroupItemInstance Parent { get; set; }
public QuestionnaireFieldGroupInstance()
{
FieldGroupItems = new List<FieldGroupItemInstance>();
}
}
If you are using fluentnhibernate to map your entities, this should work. Just pay attention to the Cascade options.
public class FieldGroupItemInstanceMap()
{
public FieldGroupItemInstanceMap()
{
Table("FieldGroupItemInstance");
HasManyToMany(x => x.ChildGroups)
.Table("FieldGroupItemInstance_QuestionnaireFieldGroupInstance")
.ParentKeyColumn("IdFieldGroupItemInstance")
.ChildKeyColumn("IdQuestionnaireFieldGroupInstance")
.Cascade.None();
}
}
public class QuestionnaireFieldGroupInstanceMap()
{
public QuestionnaireFieldGroupInstanceMap()
{
Table("QuestionnaireFieldGroupInstance");
HasManyToMany(x => x.FieldGroupItems)
.Table("FieldGroupItemInstance_QuestionnaireFieldGroupInstance")
.ParentKeyColumn("IdQuestionnaireFieldGroupInstance")
.ChildKeyColumn("IdFieldGroupItemInstance")
.Cascade.None();
}
}
I was using EF6's fluent mapping like this:
public SomeClass
{
public int SomeID { get; set; }
}
public SomeClassMap : EntityTypeConfiguration<SomeClass>
{
public SomeClassMap()
{
ToTable("SomeTable");
HasKey(c => c.SomeID);
}
}
And building the configuration from the assembly of the first requested type (model):
public class MyContext : DbContext
{
private Assembly _assembly;
public MyContext(string connectionName, Type type)
{
//checks
Database.Connection.ConnectionString = ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;
_assembly = Assembly.GetAssembly(type);
}
public override void OnModelCreating(DbModelBuilder modelBuilder)
{
//conventions
//Not the ideal solution, still looking for something better
modelBuilder.Configurations.AddFromAssembly(_assembly);
}
}
Now I want to make a generic Data project, independent of the models, so I'd like to map via annotations and simply call the generic methods in my Data project.
I've mapped the class:
[Table("SomeTable")]
public SomeClass
{
[Key]
public int SomeID { get; set; }
}
Now how do I pass this to the Data project so it can build the model configuration?
Edit This might be relevant, since my Data project is generic, I don't have the DbSet<Entity> variables in it, instead I'm calling the context.Set<Entity> and using the functions from there.
You can create a DbContext by the constructor that takes a DbCompiledModel.
You can build the compiled model separately. It's up to you where you want to implement that responsibility, but the model classes should be in scope.
Here's an example:
Some classes:
[Table("Company")]
class Company
{
[Key]
public int CompanyID { get; set; }
public string Name { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
[Table("Location")]
class Location
{
[Key]
public int LocationID { get; set; }
public string Name { get; set; }
[ForeignKey("Company")]
public int CompanyID { get; set; }
public virtual Company Company { get; set; }
}
And the creation + usage of a DbContext:
// Create a model + register types to it.
var mb = new DbModelBuilder();
mb.Entity<Company>();
mb.Entity<Location>();
// Or:
//mb.RegisterEntityType(typeof(Company));
//mb.RegisterEntityType(typeof(Location));
// Build and compile the model
var connString = #"server=myServer;database=theDataBase;Integrated Security=SSPI;MultipleActiveResultSets=True";
var dbModel = mb.Build(new SqlConnection(connString));
var compiledModel = dbModel.Compile();
// Create a DbContext using the compiled model.
var db = new DbContext(connString, compiledModel);
Database.SetInitializer<DbContext>(null); // Prevent creation of migration table
// Ready to go!
var companies = db.Set<Company>().Include(c => c.Locations).ToList();
You can make this more efficient by storing and reusing cached DbCompiledModels.
I'm creating a EF5 entity model with the designer (VS2012), and used the EF5 DbContext generator as code generation item.
My model contains an entity deriving from another (not abstract).
So let's say the base entity is called BaseEntity, and the derived entity is DerivedEntity.
Now I see in the generated context class, that there is no
Public DbSet<DerivedEntity> DerivedEntities { get; set; }
defined.
Only
Public DbSet<BaseEntity> BaseEntities { get; set; }
is defined.
Is this normal ? And if yes, how do I query the derived entities in linq ?
I'm used to query like this:
using(var ctx = new EntityContainer)
{
var q = from e in ctx.DerivedEntities <-- but this is now not possible since it doesn't exist
select e;
return q.ToList();
}
Thanks for replying.
EDIT:
As requested, generated classes posted:
public partial class Scheduling
{
public int Id { get; set; }
public string Subject { get; set; }
public System.DateTime BeginDate { get; set; }
public System.DateTime EndDate { get; set; }
}
public partial class TeamScheduling : Scheduling
{
public int TeamId { get; set; }
public Nullable<int> AssignmentId { get; set; }
public virtual Team Team { get; set; }
public virtual Assignment Assignment { get; set; }
}
public partial class EntityContainer : DbContext
{
public EntityContainer()
: base("name=EntityContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Team> Teams { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<ProductType> ProductTypes { get; set; }
public DbSet<AssignmentPreference> AssignmentPreferences { get; set; }
public DbSet<Scheduling> Schedulings { get; set; }
}
As you see, the EntityContainer class does not contain
public DbSet<TeamScheduling> TeamSchedulings { get; set; }
This is expected when you use inheritance the way you have. context.Schedulings contains both Scheduling objects and TeamScheduling objects. You can get the TeamScheduling objects only by asking for context.Schedulings.OfType<TeamScheduling>(). Note that you cannot meaningfully use context.Schedulings.OfType<Scheduling>() to get the others: that will also include the TeamScheduling objects.
You could alternatively try context.Set<TeamScheduling>(), but I'm not entirely sure that will work.
If your intention is to have two tables come up, say a parent Scheduling entity as well as a child TeamScheduling entity that has a foreign key back to the Scheduling entity, consider using a Table-per-Type (TPT) mapping as discussed here.
In essence, you should modify your "OnModelCreating" method to have the following code:
modelBuilder.Entity<TeamScheduling>().ToTable("TeamScheduling");
This explicitly tells EF that you want to have the TeamScheduling subclass to be represented as its own table. Querying it via LINQ would be simple as you would be able to do something like the following:
var teamScheds = context.Set<TeamScheduling>().Where(s => s.Id == 1).FirstOrDefault();
I was curious if it is possible to map an intermediate table through a containing object.
public class Subscriber : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
private ChannelList _subscribedList { get; set; }
public int NumSubscribedChannels { get { return _subscribedList.Count(); } }
}
public class HelpChannel : IEntity
{
[Key]
public int Id { get; set; }
public string name { get; set; }
public string category { get; set; }
public int group { get; set; }
}
I need to have a subscriber table, channel table and an intermediate table to link a subscriber to his/her channels.
Is it possible to map the list that is within the ChannelList object to the Subscriber Model?
I figured that's probably not possible and that I would need to just have a private List for EF to map. But I wasn't sure if EF will do that for private variables. Will it?
I'm hoping that is does because if it has to be public to maintain the encapsulation.
You can map private properties in EF code-first. Here is a nice description how to do it. In your case it is about the mapping of Subscriber._subscribedList. What you can't do is this (in the context's override of OnModelCreating):
modelBuilder.Entity<Subscriber>().HasMany(x => x._subscribedList);
It won't compile, because _subscribedList is private.
What you can do is create a nested mapping class in Subscriber:
public class Subscriber : IEntity
{
...
private ICollection<HelpChannel> _subscribedList { get; set; } // ICollection!
public class SubscriberMapper : EntityTypeConfiguration<Subscriber>
{
public SubscriberMapper()
{
HasMany(s => s._subscribedList);
}
}
}
and in OnModelCreating:
modelBuilder.Configurations.Add(new Subscriber.SubscriberMapping());
You may want to make _subscribedList protected virtual, to allow lazy loading. But it is even possible to do eager loading with Include:
context.Subscribers.Include("_subscribedList");