EF 4.1 RTM - EntityTypeConfiguration - entity-framework

I have been using EF Code First CTP5 with dedicated mapping classes for each entity, like this:
public class UserMapping : EntityTypeConfiguration<User>
{
public UserMapping()
{
ToTable("Users");
HasKey(t => t.ID);
Property(t => t.ID).HasColumnName("user_id");
Property(t => t.Name).HasColumnName("name");
}
}
And loading them the way Jonas Cannehag describes here:
http://areaofinterest.wordpress.com/2010/12/08/dynamically-load-entity-configurations-in-ef-codefirst-ctp5/
But that doesn't work in RTM and I haven't been able to figure out how to use dedicated mapping classes. Have you? :-)

public class DataContext : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserMapping());
base.OnModelCreating(modelBuilder);
}
}
dynamic version (probably should test extensive before putting in production)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister =
Assembly.GetExecutingAssembly().GetTypes().Where(
type =>
type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof (EntityTypeConfiguration<>));
foreach (object configurationInstance in typesToRegister.Select(Activator.CreateInstance))
{
modelBuilder.Configurations.Add((dynamic) configurationInstance);
}
base.OnModelCreating(modelBuilder);
}

Related

Apply EF global query filter in an async way

I need to apply a global query filter in my EF DbContext.
The value to check against is retrieved by an async operation.
So something like this.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasQueryFilter(async u => u.CustomerId == await GetIdAsync());
}
Of course this doesn't work, as HasQueryFilter expects a different lamda expression.
And I guess, that adding async to the OnModelCreating will bring me into troubles, too.
protected override async void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var id = await GetIdAsync();
modelBuilder.Entity<User>().HasQueryFilter(u => u.CustomerId == id);
}
Any ideas, how to solve this?
Define property CustomerId in your DbContext:
public class MyDbContext: DbContext
{
public int? CustomerId { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasQueryFilter(u => CustomerId == null || u.CustomerId == CustomerId);
}
}
Assign property in controller call:
context.CustomerId = await context.GetIdAsync();
And then you can sefely use Query Filter.

why isn't eager loading working in Entity Framework

I'm trying to do basic eager loading on a list of ProjectVersions where each ProjectVersion has a list of FieldValues and ChildProjects. I want the FieldValues and ChildProjects to be loaded along with all their properties when ProjectVersions is loaded, but it seems that in my code when going through each ProjectVersion, it still hits the database to get these collections (checking sql server profiler). Any pointers would be helpful.
var publishedList = Repository.Find<Project>().//a bunch of wheres and selects
IEnumerable<ProjectVersion> publishedList = published
.Include(x => x.FieldValues)
.Include(x => x.ChildProjects)
.ToList();
//EDIT: the context is hidden behind a generic Repository. Below are some details:
public class Repository : IRepository
{
internal readonly IDataContext _context;
public Repository(IDataContext context)
{
_context = context;
_context.Committed += _context_Committed;
_context.Deleted += _context_Deleted;
}
public IQueryable<T> Find<T>() where T : class, IEntity
{
return _context.Repository<T>();
}
}
public class EfDataContext : IDataContext
{
public IQueryable<T> Repository<T>() where T : class, IEntity
{
var table = _context.Set(typeof(T));
WrappedFieldsObjectQuery<T>(table.Cast<T>().AsExpandable()));
return table.Cast<T>().AsExpandable();
}
}
public class MsmDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type =>
type.IsClass &&
type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var config in typesToRegister.Select(Activator.CreateInstance))
{
modelBuilder.Configurations.Add((dynamic)config);
}
base.OnModelCreating(modelBuilder);
}
}
public class ProjectMapping : EntityTypeConfiguration<Project>
{
public ProjectMapping()
{
HasOptional(p => p.LastChangedBy).WithMany(p => p.ProjectsChanged).WillCascadeOnDelete();
HasRequired(p => p.CreatedBy).WithMany(p => p.ProjectsCreated).WillCascadeOnDelete();
HasRequired(d => d.Account).WithMany(p => p.Projects).WillCascadeOnDelete();
HasRequired(d => d.PinType).WithMany(p => p.Projects).HasForeignKey(p => p.PinType_Id).WillCascadeOnDelete();
}
}
public static class RepositoryFactory
{
public static IRepository CreateRepository()
{
return CreateEfRepository();
}
internal static IRepository CreateEfRepository()
{
return new Repository(new EfDataContext(new MsmDbContext()));
}
}
Ok, I can't see your complete query, but in your comments you wrote something about select. EF will ignore Include() statements as soon as you are doing custom projections using Select(), my guess is that's why eager loading does not work. Instead of Include(), try to add the properties you want to load to your projection, something like
Repository.Find<Project>()
.Select(p => new { project = p, p.FieldValues, p.ChildProjects })
.AsEnumerable().Select(p => p.project).ToList()
This way the projection will take care of loading your data, you don't need Include().
Actually, I got it to work by just directly working with the DataContext. Somehow the Repository is screwing it up.

Convention for DatabaseGeneratedOption.None

I would like to manually insert id for all my entities.
Is it possible to create some kind of convention or I have to set HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) (or add DatabaseGenerated attribute) for every entity ?
EDIT
There is a simpler way to do this then using accepted answer:
modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();
It's possible to create custom conventions in EntityFramework 6 as follows:
Create a convention class
public class ManualIdentityConvention : Convention
{
public ManualIdentityConvention()
{
this.Properties<int>()
.Where(p => p.Name.EndsWith("Id"))
.Configure(p => p.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None));
}
}
Add the convention to your DbContext
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new ManualIdentityConvention());
}
}
EntityFramework 5
As for EntityFramework 5, I believe something similar can be achieved, but not via a convention class:
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(x => x.Name.EndsWith("Id"))
.Configure(x => x.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None));
}
}
In either case, neither approach is particularly surgical, but one could conceivably tighten up the convention by being more specific in one's where clause.
Does this help?

Sporadic HTTP 500 in Metadata GET : breeze metadata Could not find the CLR type for 'x' using DbContext

I know this will probably sound like a vague issue but I don't have a good handle yet on what is happening. Here's what I know:
I am using Breeze 1.4.2 with EF 5.
I have a sporadic issue while calling the Metadata HTTP Get for a specific controller. This controller is a POCO controller built using a code-first DbContext.
The only way I am able to get rid of this problem is by recycling IIs App pools on my 2 nodes load balanced environment. By the way, that only happens in Release mode on the target environment, I have never reproduced this bug locally.
Here's the MetaData method:
[HttpGet]
public override string Metadata()
{
return new EFContextProvider<PersonalizationdDtoContext>().Metadata();
}
Here's the context + configuration:
internal class PersonalizationdDtoContext : DbContext
{
static PersonalizationdDtoContext()
{
// Prevent attempt to initialize a database for this context
Database.SetInitializer<PersonalizationdDtoContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ApplicationDefaultsConfiguration());
modelBuilder.Configurations.Add(new JsDataTableConfiguration());
modelBuilder.Configurations.Add(new MobilePreferenceModelConfiguration());
}
public DbSet<MobilePreferenceModel> MobilePreferenceModel { get; set; }
public DbSet<ApplicationDefaults> ApplicationDefaults { get; set; }
public DbSet<JsDatatable> JsDatatables { get; set; }
}
internal class ApplicationDefaultsConfiguration : EntityTypeConfiguration<ApplicationDefaults>
{
public ApplicationDefaultsConfiguration()
{
this.HasKey(c => c.Id);
this.Property(c => c.LastUpdateDateTime).IsConcurrencyToken();
this.Property(c => c.PreferenceTypeId).IsRequired();
this.Property(c => c.Id).IsRequired();
}
}
internal class JsDataTableConfiguration : EntityTypeConfiguration<JsDatatable>
{
public JsDataTableConfiguration()
{
this.HasKey(c => c.Id);
this.Property(c => c.LastUpdateDateTime).IsConcurrencyToken();
this.Property(c => c.PreferenceTypeId).IsRequired();
this.Property(c => c.Id).IsRequired();
this.HasRequired(p => p.MobilePreferenceModel)
.WithMany(p => p.JsDatatables)
.HasForeignKey(q => q.MobilePreferenceModelID);
}
}
internal class MobilePreferenceModelConfiguration : EntityTypeConfiguration<MobilePreferenceModel>
{
public MobilePreferenceModelConfiguration()
{
this.HasKey(c => c.Id);
this.HasOptional(p => p.ApplicationDefaults)
.WithRequired();
}
}
I am going to try to upgrade to latest breeze assemblies on the server to see if I can get passed this issue.
Any idea of what might be happening?

Entity Framework - TPC - Override Primary Key Column Name

Here's my model:
public abstract class Entity
{
public long Id { get; set; }
}
public abstract class Audit : Entity
{}
public class UserAudit : Audit
{
public virtual User User { get; set; }
}
public class User : Entity
{}
Here's my DbContext:
public class TestDbContext : DbContext
{
static TestDbContext()
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new AuditConfiguration());
modelBuilder.Configurations.Add(new UserAuditConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}
}
And here's my mappings:
public abstract class EntityConfiguration<T> : EntityTypeConfiguration<T>
where T : Entity
{
protected EntityConfiguration()
{
HasKey(t => t.Id);
Property(t => t.Id)
.HasColumnName("Key");
}
}
public class AuditConfiguration : EntityConfiguration<Audit>
{}
public class UserAuditConfiguration : EntityTypeConfiguration<UserAudit>
{
public UserAuditConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("UserAudits");
});
HasRequired(u => u.User)
.WithMany()
.Map(m => m.MapKey("UserKey"));
}
}
public class UserConfiguration : EntityConfiguration<User>
{}
When I try to generate a migration for this model, I get the following error:
error 2010: The Column 'Id' specified as part of this MSL does not exist in MetadataWorkspace.
If I comment out the ".HasColumnName" call in the constructor of EntityConfiguration, the migration generates correctly (except of course that the column name is Id and not Key).
Is TPC mappings supposed to support primary key columns that don't use the default column name?
The problem appears to be that Entity Framework does not expect me to map Audit. If I remove AuditConfiguration, this works as expected.