In Entity Framework Code First, when I declare entities I have to use DbSet<> type of properties for that. For example:
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
Recently I've met DbSet<> declared as virtual.
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
What is the difference? What EF functionalities is enabled?
public class AppContext : DbContext
{
public AppContext()
{
Configuration.LazyLoadingEnabled = true;
}
public virtual DbSet<AccountType> AccountTypes { get; set; }
}
public class AccountType
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<AccountCode> AccountCodes { get; set; }
}
public class AccountCode
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid AccountTypeId { get; set; }
public virtual AccountType AccountType { get; set; }
}
The virtual keyword on the navigation properties are used to enable lazy loading mechanism, but the LazyLoadingEnabled property of the configuration must be enabled.
The virtual keyword on AccountType::AccountCodes navigation property will load all account codes the moment there is a programmatically access to that property while the db context are still alive.
using (var context = new AppContext())
{
var accountType = context.AccountTypes.FirstOrDefault();
var accountCodes = accountType.AccountCodes;
}
While the virtual keyword on the derived DbContext class (virtual DbSet<>) is used for testing purpose (mocking the DbSet property), virtual keyword in this case is not related to lazy loading.
===== update =====
Usually we are doing the testing against the service / logic, for example we have another layer for the account type service as follow. And the service accepts the db context instance using some kind of dependency injection through the constructor.
public class AccountTypeService
{
public AppContext _context;
public AccountTypeService(AppContext context)
{
_context = context;
}
public AccountType AddAccountType(string name)
{
var accountType = new AccountType { Id = Guid.NewGuid(), Name = name };
_context.AccountTypes.Add(accountType);
_context.SaveChanges();
return accountType;
}
}
And now we need to test the account type service, in this case I used mstest and automoq to create the mock class.
[TestClass]
public class AccountTypeServiceTest
{
[TestMethod]
public void AddAccountType_NormalTest()
{
// Arranges.
var accountTypes = new List<AccountType>();
var accountTypeSetMock = new Mock<DbSet<AccountType>>();
accountTypeSetMock.Setup(m => m.Add(It.IsAny<AccountType>())).Callback<AccountType>(accountType => accountTypes.Add(accountType));
var appContextMock = new Mock<AppContext>();
appContextMock.Setup(m => m.AccountTypes).Returns(accountTypeSetMock.Object);
var target = new AccountTypeService(appContextMock.Object);
// Acts.
var newAccountType = target.AddAccountType("test");
// Asserts.
accountTypeSetMock.Verify(m => m.Add(It.IsAny<AccountType>()), Times.Once());
appContextMock.Verify(m => m.SaveChanges(), Times.Once());
Assert.AreEqual(1, accountTypes.Count);
Assert.IsNotNull(newAccountType);
Assert.AreNotEqual(Guid.Empty, newAccountType.Id);
Assert.AreEqual("test", newAccountType.Name);
}
}
Note that in EF Core (currently 1.0 and 2.0) still not supporting LazyLoading scheme, so using with "virtual" or without does not make a different.
FYI. The scaffolding generate "virtual" keyword may be supporting LazyLoading technique in the future version of EF Core!
In EF Core 2.1 the developer team added support for LazyLoading.
More information is here
Related
TL;DR: what's the most concise method to load a single navigation property on an entity?
Suppose I already have an instance entity Foo with a child Child. Instance of Foo I have has ChildId set but Child was not loaded, i.e. foo.ChildId == 1234 but foo.Child == null.
I want to get Child if it is missing. I know I can do:
if (foo.Child is null) {
foo.Child = _dbContext.Foos.Include(f => f.Child).Single(f => f.Id == foo.Id).Child;
}
but I am looking for a lazy way (pun!) to load it on-demand (I don't want to load all properties on-demand, however, just the one I want to load explicitly), something like:
var child = _dbContext.EnsureLoaded(da, e => e.Child);
Is there a way to do this?
Probably you are looking for Explicit Loading of Related Data
_dbContext.Entry(foo).Reference(f => f.Child).Load();
Lazy Loading is already available. There are two options:
using proxy objects generated by EF Core to automagically load related entities or
use the ILazyLoader service with POCOs to load related entities when requested
Proxies
To use proxies, the DbContext has to be configured first :
.AddDbContext<BloggingContext>(
b => b.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString));
After that, any properties that need to be lazy loaded have to be made virtual :
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
}
At runtime EF will return proxy objects that inherit from the entity classes and overload the lazy properties to load the related object when first requested.
ILazyLoader service
Another option, that doesn't require inheritance, is to use POCOs and the ILazyLoader service to load the entities when needed :
public class Blog
{
private ICollection<Post> _posts;
public Blog()
{
}
private Blog(ILazyLoader lazyLoader)
{
LazyLoader = lazyLoader;
}
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts
{
get => LazyLoader.Load(this, ref _posts);
set => _posts = value;
}
}
This adds a dependency on the ILazyLoader interface itself, which in turn adds a dependency to EF Core in domain or business models.
This can be avoided by injecting the loader as a lambda, along with some convention magic :
public class Blog
{
private ICollection<Post> _posts;
public Blog()
{
}
private Blog(Action<object, string> lazyLoader)
{
LazyLoader = lazyLoader;
}
private Action<object, string> LazyLoader { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts
{
get => LazyLoader.Load(this, ref _posts);
set => _posts = value;
}
}
This is used in combination with an extension method that actually calls the loader using the property's name and sets its backing field :
public static class PocoLoadingExtensions
{
public static TRelated Load<TRelated>(
this Action<object, string> loader,
object entity,
ref TRelated navigationField,
[CallerMemberName] string navigationName = null)
where TRelated : class
{
loader?.Invoke(entity, navigationName);
return navigationField;
}
}
As the docs warn:
The constructor parameter for the lazy-loading delegate must be called "lazyLoader". Configuration to use a different name than this is planned for a future release.
I usually find a way to make this work but here, the included data are null, running in a C# console application
(While the same code works in my web application)
both Contact and Company are null, EF does not fetch associated data
var context = new MyDBContext (configuration);
var leads = context.Leads.AsQueryable()
.Include(p => p.Company) <<< does not create jointure
.Include(p => p.Contact) <<< does not create jointure
.ToList();
I expect the associated Tables to be populated in my object
I tried disabling the lazy loading but apparently LazyLoadingEnabled is not part the configuration class
public class MyDBContext : DbContext
{
IConfiguration configuration;
public MyDBContext (IConfiguration configuration)
{
this.configuration = configuration;
this.configuration.LazyLoadingEnabled = false; <<< cannot compile
}
I create my db context like I do it in my web app
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString = configuration.GetConnectionString("MyDBContext");
optionsBuilder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
})
.EnableSensitiveDataLogging()
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
...
this is how I create the configuration
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
configuration = builder.Build();
thanks for your help on this
[edit]
here is the lead class:
[Table("leads")]
public class Lead
{
[Required]
public int? Id { get; set; }
[Required]
public Company Company { get; set; }
[Required]
public User Contact { get; set; }
}
I think, for navigation properties to work EF core needs FKs. But not sure that how this is working thru the Web App. If possible please try with FKs like:
[Table("leads")]
public class Lead
{
[Required]
public int? Id { get; set; }
public int CompanyId { get; set; }
public int UserId { get; set; }
[Required]
public Company Company { get; set; }
[Required]
public User Contact { get; set; }
}
I am using Entity Framework 6 with Generic Repository and DTOs.
I want to create new entities via navigation property.
Here is my model:
public partial class Project
{
public Project()
{
this.ProjectAssets = new List<ProjectAsset>();
}
public int ProjectID { get; set; }
public string Name { get; set; }
public virtual ICollection<ProjectAsset> ProjectAssets { get; set; }
}
public partial class Asset
{
public Asset()
{
this.Revisions = new List<Revision>();
}
public int AssetID { get; set; }
public string Name { get; set; }
public short Type { get; set; }
public virtual ICollection<Revision> Revisions { get; set; }
}
public partial class ProjectAsset
{
public int MappingID { get; set; }
public int ProjectID { get; set; }
public int AssetID { get; set; }
public virtual Asset Asset { get; set; }
}
I have already created Project. And if i am creating new Asset, then create Project Asset with AssetID from just created Asset, it's OK, but i have to re-fetch Project from DB.
I want to do it in one transaction, like that:
Project.ProjectAssets.Add(new ProjectAsset(new Asset((short)type, fileName)));
ServiceLocator.Default.ResolveType<IPipeLine>().Update(Project);
public void Update<TEntity>(TEntity entity) where TEntity : class
{
var fqen = GetEntityName<TEntity>();
object originalItem;
var key = ((IObjectContextAdapter)DbContext).ObjectContext.CreateEntityKey(fqen, entity);
if (((IObjectContextAdapter)DbContext).ObjectContext.TryGetObjectByKey(key, out originalItem))
((IObjectContextAdapter)DbContext).ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
//DbContext.Entry(entity).State = EntityState.Modified;
}
But after SaveChanges there is no record in DB, and MappingID still 0.
I thought that ApplyCurrentValues must work with Navigation Properties.
Is there any good way to solve that problem?
EDIT:
I accessing DAL throughBusiness Entities with contain the same properties, but they also implement INotifyPropertyChanged and other WPF stuff. So i think i can subscribe to CollectionChanged event and manualy create/delete entities from navigation property. And in property setters i can call update, but i think it can strongly decrease perfomance.
Assume that I have the following little console application which uses Entity Framework 5:
class Program {
static void Main(string[] args) {
using (var ctx = new ConfContext()) {
var personBefore = ctx.People.First();
Console.WriteLine(personBefore.Name);
personBefore.Name = "Foo2";
ctx.SaveChanges();
var personAfter = ctx.People.First();
Console.WriteLine(personAfter.Name);
}
Console.ReadLine();
}
}
public class ConfContext : DbContext {
public IDbSet<Person> People { get; set; }
public IDbSet<Session> Sessions { get; set; }
}
public class Person {
[Key]
public int Key { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime? BirthDate { get; set; }
public ICollection<Session> Sessions { get; set; }
}
public class Session {
[Key]
public int Key { get; set; }
public int PersonKey { get; set; }
public string RoomName { get; set; }
public string SessionName { get; set; }
public Person Person { get; set; }
}
As you can see, I am changing the name of the record and saving it. It works but it feels like magic to me. What I am doing in all of my applications is the following one (to be more accurate, inside the Edit method of my generic repository):
static void Main(string[] args) {
using (var ctx = new ConfContext()) {
var personBefore = ctx.People.First();
Console.WriteLine(personBefore.Name);
personBefore.Name = "Foo2";
var entity = ctx.Entry<Person>(personBefore);
entity.State = EntityState.Modified;
ctx.SaveChanges();
var personAfter = ctx.People.First();
Console.WriteLine(personAfter.Name);
}
Console.ReadLine();
}
There is no doubt that the second one is more semantic but is there any other obvious differences?
Well the second code block where you explicitly set the entity state is redundant, as the change tracker already knows that the entity is modified because the context knows about the entity (as you query the context to retrieve the entity).
Setting (or painting) the state of the entity would be more useful when working with disconnected entities, for example in an n-tier environment where the entity was retrieved in a different context and sent to a client for modification, and you wish to mark those changes back on the server using a different context.
Otherwise, the first code block is cleaner in my opinion.
From what I understand on several posts the TPT architecure, with EF, does not create the necessary ON DELETE CASCADE when using a shared primary key.... It was also said that the EF context will handle the proper order of deletion of the sub-classed tables (however I do get an error that it breaks the constraint and that I can fix it with adding the ON DELETE CASCADE on the sub-class table)...
more background info...
I have a Section class, which has a number, title, and a list of pages. The page is designed using a super class which holds basic page properties. I have about 10+ sub-classes of the page class. The Section class holds an ICollection of these pages. The DB is created properly with the exception of no ON DELETE CASCADE on the sub-classed tables.
My code will create the entities and adds to the DB fine. However, if I try to delete a section (or all sections) it fails todelete due to the FK constraint on my sub-class page table...
public abstract BaseContent
{
... common properties which are Ignored in the DB ...
}
public class Course : BaseContent
{
public int Id {get;set;}
public string Name {get;set;}
public string Descripiton {get;set;}
public virtual ICollection<Chapter> Chapters{get;set;}
...
}
public class Chapter : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Course MyCourse{get;set;}
public virtual ICollection<Section> Sections{get;set;}
...
}
public class Section : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Chapter MyChapter {get;set;}
public virtual ICollection<BasePage> Pages {get;set;}
...
}
public abstract class BasePage : BaseContent, IComparable
{
public int Id { get; set; }
public string Title { get; set; }
public string PageImageRef { get; set; }
public ePageImageLocation ImageLocationOnPage { get; set; }
public int PageNumber { get; set; }
public virtual Section MySection { get; set; }
...
}
public class ChapterPage : BasePage
{
public virtual int ChapterNumber { get; set; }
public virtual string ChapterTitle { get; set; }
public virtual string AudioRef { get; set; }
}
public class SectionPage : BasePage
{
public virtual int SectionNumber { get; set; }
public virtual string SectionTitle { get; set; }
public virtual string SectionIntroduction { get; set; }
}
... plus about 8 other BasePage sub-classes...
public class MyContext: DbContext
{
...
public DbSet<Course> Courses { get; set; }
public DbSet<Chapter> Chapters { get; set; }
public DbSet<Section> Sections { get; set; }
public DbSet<BasePage> Pages { get; set; }
...
}
.. Fluent API ... (note Schema is defined to "" for SqlServer, for Oracle its the schema name)
private EntityTypeConfiguration<T> configureTablePerType<T>(string tableName) where T : BaseContent
{
var config = new EntityTypeConfiguration<T>();
config.ToTable(tableName, Schema);
// This adds the appropriate Ignore calls on config for the base class BaseContent
DataAccessUtilityClass.IgnoreAllBaseContentProperties<T>(config);
return config;
}
public virtual EntityTypeConfiguration<BasePage> ConfigurePageContent()
{
var config = configureTablePerType<BasePage>("PageContent");
config.HasKey(pg => pg.Id);
config.HasRequired(pg => pg.Title);
config.HasOptional(pg => pg.PageImageRef);
config.Ignore(pg => pg.ImageLocationOnPage);
return config;
}
public virtual EntityTypeConfiguration<ChapterPage> ConfigureChapterPage()
{
var config = configureTablePerType<ChapterPage>("ChapterPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.ChapterNumber);
config.Ignore(pg => pg.ChapterTitle);
return config;
}
public virtual EntityTypeConfiguration<SectionPage> ConfigureSectionPage()
{
var config = configureTablePerType<SectionPage>("SectionPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.SectionNumber);
config.Ignore(pg => pg.SectionTitle);
return config;
}
... other code to model other tables...
So the app is able to populate content and the relationships are properly set up. However, when I try to delete the course, I get the error that the delete failed due to the constraint on the ChapterPage to PageContent table..
Here is the code which deletes the Course (actually I delete all courses)...
using (MyContext ctx = new MyContext())
{
ctx.Courses.ToList().ForEach(crs => ctx.Courses.Remove(crs));
AttachLookupEntities(ctx);
ctx.SaveChanges();
}
If I add the 'ON DELETE CASCADE' in the ChapterPage and SectionPage table for its shared primary with PageContent, the delete goes through.
In summary,
The only solution that I have seen is to manually alter the constraints to add the ON DELETE CASCADE for all of my sub-class page tables. I can implement the change, as I have code which generates the DB script for the EF tables I need (a small subset of our whole DB) since we will not use EF to create or instantiate the DB (since it does not properly support migrations as yet...).
I sincerely hope that I have miscoded something, or forgot some setting in the model builder logic. Because if not, the EF designers have defined an architecure (TPT design approach) which cannot be used in any real world situation without a hack workaround. It's a half finished solution. Do not get me wrong, I like the work that has been done, and like most MSFT solutions its works for 70% of most basic application usages. It just is not ready for more complex situations.
I was trying to keep the DB design all within the EF fluent API and self-contained. It's about 98% there for me, just would be nice if they finished the job, maybe in the next release. At least it saves me all the CRUD operations.
Ciao!
Jim Shaw
I have reproduced the problem with a little bit simpler example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace EFTPT
{
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<BasePage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
public Parent Parent { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<BasePage> BasePages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Pages)
.WithRequired(p => p.Parent); // creates casc. delete in DB
modelBuilder.Entity<BasePage>()
.ToTable("BasePages");
modelBuilder.Entity<DerivedPage>()
.ToTable("DerivedPages");
}
}
class Program
{
static void Main(string[] args)
{
using (var ctx = new MyContext())
{
var parent = new Parent { Pages = new List<BasePage>() };
var derivedPage = new DerivedPage();
parent.Pages.Add(derivedPage);
ctx.Parents.Add(parent);
ctx.SaveChanges();
}
using (var ctx = new MyContext())
{
var parent = ctx.Parents.FirstOrDefault();
ctx.Parents.Remove(parent);
ctx.SaveChanges(); // exception here
}
}
}
}
This gives the same exception that you had too. Only solutions seem to be:
Either setup cascading delete for the TPT constraint in the DB manually, as you already tested (or put an appropriate SQL command into the Seed method).
Or load the entites which are involved in the TPT inheritance into memory. In my example code:
var parent = ctx.Parents.Include(p => p.Pages).FirstOrDefault();
When the entities are loaded into the context, EF creates actually two DELETE statements - one for the base table and one for the derived table. In your case, this is a terrible solution because you had to load a much more complex object graph before you can get the TPT entities.
Even more problematic is if Parent has an ICollection<DerivedPage> (and the inverse Parent property is in DerivedPage then):
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<DerivedPage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
public Parent Parent { get; set; }
}
The example code wouldn't throw an exception but instead delete the row from the derived table but not from the base table, leaving a phantom row which cannot represent an entity anymore because BasePage is abstract. This problem is not solvable by a cascading delete but you were actually forced to load the collection into the context before you can delete the parent to avoid such a nonsense in the database.
A similar question and analysis was here: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/