Using DbContext to Get Children Count and map to property - entity-framework

I have the following object tree where each plan which is a parent to a child of comments & concerns. On the client i really don't care about the details 90% of time time and thus the comments are lazy loaded when expanded on a per dicipline basis. But I need a little meta data about them for the current feature I'm working on.
[DataContract]
public class Plan
{
[Key]
public int Id { get; set; }
//Ton of properties
}
[DataContract]
public class Comment
{
[Key]
public int Id { get; set; }
[DataMember]
[Required]
public bool IsConcern { get; set; }
[DataMember]
[Required]
public bool IsResolved { get; set; }
[DataMember]
[MaxLength(30)]
public string Discipline { get; set; }
[DataMember]
public int Plan_Id { get; set; }
//Other properties
}
What I would like to do is something like this
[DataContract]
public class Plan
{
[Key]
public int Id { get; set; }
//Ton of properties
public int Discipline1UnresolvedConcernCount? { get; set; }
//... Discipline2.... Discipline 8
}
modelBuilder.Entity<Plan>()
.Ignore(x => x.IsIntegrated)
.Ignore(x => x.Discipline1UnresolvedConcernCount)
.Ignore(x => x.Discipline2UnresolvedConcernCount)
The service
public class PlansController : BaseODataController
{
/// <summary>
/// Look up a Plan record by ID
/// </summary>
/// <param name="key">ID for the Plan</param>
[EnableQuery]
[Authorize]
public SingleResult<Plan> Get([FromODataUri] int key)
{
using (CreateTimedTelemetryTrace($"key:{key}"))
{
IQueryable<Plan> result = DataContext.Plans.Where(bu => bu.Id == key)
//Improve Query to get counts
return SingleResult.Create(result);
}
}
}
Is there a way to do what im wanting to do, and is it clean?

Related

DbSet property of type class returns null

I'm creating an API for an app. The DbContext I have trouble with looks like this:
public class SchoolPlannerDbContext : DbContext
{
public SchoolPlannerDbContext(DbContextOptions<SchoolPlannerDbContext> options) : base(options) { }
public DbSet<Activity> Activities { get; set; }
public DbSet<Room> Rooms { get; set; }
public DbSet<Subject> Subjects { get; set; }
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Group> Groups { get; set; }
}
The Activity class is as follows:
public class Activity
{
public int ID { get; set; }
[Required]
public Teacher Teacher { get; set; }
[Required]
public Room Room { get; set; }
[Required]
public Subject Subject { get; set; }
[Required]
public Group Group { get; set; }
[Required]
public int Slot { get; set; }
[Required]
public int Day { get; set; }
}
All the other properties contain an int ID and a string Name.
My controller looks like this:
public class SqlPlannerData : ISchoolPlannerData
{
private readonly SchoolPlannerDbContext db;
public SqlPlannerData(SchoolPlannerDbContext db)
{
this.db = db;
}
public IEnumerable<Activity> GetActivities()
{
return db.Activities;
}
public IEnumerable<Group> GetGroups()
{
return db.Groups;
}
}
GetGroups() works as intended and returns an IEnumerable with properties set correctly.
My problem is that when I'm trying to access db.Activities, the properties of type, say, Teacher (non-basic types like int) are set to null:
Debugger screenshot.
However, there is a row in the database that looks like this. I.e. the columns exist in the database.
What do I do to make GetActivities() return an IEnumerable with correctly set properties?
Some properties are null because of lazy loading you need to include them
return db.Activities
.Include(i => i.Teacher)
.Include(i => i.Room)
.Include(i => i.Subject)
.Include(i => i.Group)
.ToList()
Each propety Id can be configured by EF5+ as shadows. But I usually prefer to add all Ids explicitely. This way I have much less problem when I am using db context in the project. But is is optional and you can leave it as is
public class Activity
{
public int ID { get; set; }
[Required]
public int? TeacherId { get; set; }
[Required]
public int? RoomId { get; set; }
[Required]
public int? SubjectId { get; set; }
[Required]
public int? GroupId { get; set; }
public virtual Teacher Teacher { get; set; }
public virtual Room Room { get; set; }
public virtual Subject Subject { get; set; }
public virtual Group Group { get; set; }
[Required]
public int Slot { get; set; }
[Required]
public int Day { get; set; }
}
and in order to get list activities you have to add ToList() or ToArray() at least
public IEnumerable<Activity> GetActivities()
{
return db.Activities.ToArray();
}
and by the way, you can' t using not nullabe Id as required becaue it is relevant
[Required]
public int TeacherId { get; set; }
since int by default is 0 and it is a valid value and required will not be working

Referenced object is not loaded from database

This the table structure I have:
#region Tables
public class WorkoutProfile
{
public WorkoutProfile()
{
WorkoutExercises = new List<WorkoutExercise>();
}
[Key]
public int ProfileId { get; set; }
public string Name { get; set; }
public int Sets { get; set; }
public int RestAfterSetInSeconds { get; set; }
public virtual User User { get; set; }
public virtual ICollection<WorkoutExercise> WorkoutExercises { get; set; }
}
public class WorkoutExercise
{
[Key]
public int WorkoutId { get; set; }
public virtual Exercise Exercise { get; set; }
public int Order { get; set; }
public int WorkoutTimeInSeconds { get; set; }
public int RestAfterInSeconds { get; set; }
}
public class Exercise
{
[Key]
public long ExerciseId { get; set; }
public string Title { get; set; }
public string Visualisation { get; set; }
public bool IsDefault { get; set; } // Is exersice should be included when user first registers
}
public class User
{
[Key]
public long UserId { get; set; }
public string Email { get; set; }
public DateTime Registered { get; set; }
}
#endregion Tables
In the repository class I run the following linq query:
return context
.WorkoutProfiles.Include(w => w.WorkoutExercises)
.Where(q => q.User.UserId == userId && q.ProfileId == profileId)
.FirstOrDefault();
and I receive the good and old "Object reference not set to an instance of an object". When examining the result, see that Exercises property in WorkoutExercises is null.
This is how the database is created using code first approach:
So, the question is: why Exercises not included in WorkoutExercises object? Do I need to include it somehow? I am using .NET Core 2
The simple answer would be no lazy loading in EFCore. Not Released yet but if you want to dabble with alpha code, its in the repository. Based on your classes there are no collections for exercises in WorkoutExcercise.
Then you need to ThenInclude(w => w.Exercises) following your Include clause since EFCore doesn't do lazy loading.
I found a solution following this post
Altered my code as following:
var top = context
.Set<WorkoutProfile>()
.Where(q => q.ProfileId == profileId && q.User.UserId == userId)
.Include(q => q.WorkoutExercises)
.SingleOrDefault();
context
.Entry(top)
.Collection(e => e.WorkoutExercises)
.Query()
.OfType<WorkoutExercise>()
.Include(e => e.Exercise)
.Load();
And it worked

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection. - for chiled entity

Following is my entities.
public class Expense
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ExpenseId { get; set; }
[Required]
public int CategoryId{ get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
public virtual List<EditedExpense> EditedExpenses { get; set; }
}
public class EditedExpense
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int EditedExpenseId { get; set; }
[Required]
public int CategoryId{ get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
public int ExpenseId { get; set; }
}
public class Category
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int CategoryId{ get; set; }
public string Title
public virtual List<Expense> Expenses { get; set; }
public virtual List<EditedExpense> EditedExpenses { get; set; }
}
I have used this code to get all Expanses and EditedExpanses
var expenses = db.Expenses.Include(exp => exp.EditedExpenses).Include(exp => exp.Category);
return View(expenses.ToList());
Now I want to go through all Expanses and thier EditedExpanse using two foreach loop like following, but it casts an exception when it tries to get the Category of the EditedExpense:
foreach(exp in expansesList)
{
foreach(editedExp in exp.EditedExpanses)
{
<text>#editedExp.Category.Title</text>
}
}
var expenses = db.Expenses
.Include(exp => exp.EditedExpenses)
.Include(exp => exp.Category);
This includes the Category property of Expense, not the one of EditExpense.
Use:
var expenses = db.Expenses
.Include(exp => exp.EditedExpenses.Select(z => z.Category));
EDIT:
Just to clarify about the exception you had: you currently are using lazy loading, so you can't lazy load navigation properties once your EF context has been disposed. As a side note, I always recommend deactivating lazy loading. See https://stackoverflow.com/a/21379510/870604

migrating old ado.net web app to EF

consider this query
Select (some properties from all 3 Tables)
From PageWidgets pw LEFT JOIN PageWidgetDefinition pwd
On pwd.ID = pw.WidgetID
LEFT JOIN PageWidgetSkin pws
ON pws.ID = pw.SkinInstanceID
LEFT JOIN PageWidgetSkinRows pwsr
On pwsr.SkinID = pws.ID Where pw.PageID = *x*
Order By (some properties)
in old implementation, it reads widgets on a page & their skin & i have a function looping through rows returned & make a pagewidget by its skin & its widget instance.
each widget has three row for its skin, and finally we receive a List that has everything it needs to operate
i have these classes in EF
public partial class Widget: BaseEntity {
public int ID { get; set; }
public int PageTemplateID { get; set; }
public PageTemplate PageTemplate { get; set; }
public int WidgetDefinitionID { get; set; }
public WidgetDefinition WidgetDefinition { get; set; }
public int WidgetSkinID { get; set; }
public WidgetSkin WidgetSkin { get; set; }
//other properties omitted
}
public partial class WidgetDefinition: BaseEntity {
public int ID { get; set; }
public string Title { get; set; }
//other properties omitted
public virtual ICollection<Widget> Widgets { get; set; }
}
public partial class WidgetSkin: BaseEntity {
public int ID { get; set; }
public string Name { get; set; }
//other properties omitted
public virtual ICollection<Widget> Widgets { get; set; }
public virtual ICollection<WidgetSkinRow> WidgetSkinRows { get; set; }
}
public partial class WidgetSkinRow: BaseEntity {
public int ID { get; set; }
public int WidgetSkinID { get; set; }
public virtual WidgetSkin WidgetSkin { get; set; }
}
do i need an extra bussiness layer doing the same thing?
using EF, I want to have only one trip to DB.
You can use "eager loading" method to do this.
Your query will then look something like this:
using (var entities = new WidgetEntities() )
{
var query = from w in entities.Widgets.Include("WidgetDefinition").Include("WidgetDefinition.Widgets").Include("WidgetSkins").Include("WidgetSkins.WidgetSkinRows")
where w.Page = *x*
order by w.someproperty
select w;
Widget myWidget = query.First();
}

EF code first error "The specified index already exists. [ IX_Id ]" for object tree

Using EF code first 4.3 I'm trying to model an object tree with a required-required relationships and a required-optional relationships.
Here is a simple representation of those classes
public class Top
{
public int Id { get; set; }
public virtual Middle Middle { get; set; }
}
public class Middle
{
public int Id { get; set; }
public virtual Child Child { get; set; }
}
public class Child
{
public int Id { get; set; }
}
Here is the OnModelCreating code
modelBuilder.Entity<Top>().HasRequired(t => t.Middle).WithRequiredPrincipal().WillCascadeOnDelete();
modelBuilder.Entity<Middle>().HasRequired(t => t.Child).WithOptional().WillCascadeOnDelete();
This produces the error "The specified index already exists. [ IX_Id ]" on SQLCE
After checking the db schema, both model binder fluent API configuration lines create an index IX_Id on the table Middles.
Does anyone know how to work around that problem?
Is there a way to set the index name?
Thank you!
Pascal
use this code ;
public class Top
{
public int Id { get; set; }
public int MiddleId { get; set; }
[ForeignKey("MiddleId")]
public virtual Middle Middle { get; set; }
}
public class Middle
{
public int Id { get; set; }
public int ChildId { get; set; }
[ForeignKey("ChildId")]
public virtual Child Child { get; set; }
}
public class Child
{
public int Id { get; set; }
}