I'm using EF 3.5 and I've created partial classes with the same name as my tables and EF generated classes.
public partial class Region
{
public Region()
{
}
public Region(string name)
{
this.RegionName = name;
}
public bool Save()
{
try
{
using (var context = new PhonebookEntities())
{
context.AddToRegions(this);
context.SaveChanges();
}
return true;
}
catch (System.Exception)
{
return false;
}
}
}
When I'm create and save a new Region
var region = new Region("TestRegion");
region.Save().ShouldBeTrue();
I get an exception when I save because the region entity is autopopulated with RegionId = 0, which is setup in the db to autoincrement.
Related
I'm trying to populate my database using Entity Framework. I'm using the Seed override and using set initializer from my DbContext. When I create my database from scratch, it doesn't seem to add these values in!
public class PokemonDatabaseInitializer : CreateDatabaseIfNotExists<PkmnContext>
{
protected override void Seed(PkmnContext context)
{
var pkm = new Pokemon
{
Id = 25,
DisplayName = "Pikachu",
RegionId = 1
};
var region = new PokemonRegion
{
Id = 1,
Kanto = true,
};
var location = new PokemonLocation
{
AreaFound = "Viridian Forest",
Id = 1
};
context.Pokemons.Add(pkm);
context.PokemonRegions.Add(region);
context.PokemonLocations.Add(location);
context.SaveChanges();
base.Seed(context);
}
}
public class PkmnContext : DbContext
{
public PkmnContext()
{
Database.SetInitializer(new PokemonDatabaseInitializer());
}
public DbSet<Pokemon> Pokemons { get; set; }
public DbSet<PokemonRegion> PokemonRegions { get; set; }
public DbSet<PokemonLocation> PokemonLocations { get; set; }
}
you need to call
update-database
from package manager console. It also possible to call it from code after any migrations were applied
Please note that it will seed data each time you call it so i would add some checks if items exists already
I'm having trouble understanding the rules around when SeedData is called in EF Code First. Specifically, does it have anything to do with migrations? Migrations has it's own place to seed that seems different from the datacontext. I'm showing two examples below and I'm not sure which one is correct to use.
My Main Question: If I have a Migration, does the seeddata method not get called in my MultiTenantContext class? I have examples where it does and it does not and I can't figure out why.
In Migrations configuration.cs:
namespace WebApp.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<WebApp.Models.MultiTenantContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
ContextKey = "WebApp.Models.MultiTenantContext";
}
protected override void Seed(WebApp.Models.MultiTenantContext context)
{
var tenants = new List<Tenant>
{...};
tenants.ForEach(a => context.Tenants.Add(a));
context.SaveChanges();
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
}
And in context class directly:
[DbConfigurationType(typeof(DataConfiguration))]
public class MultiTenantContext : DbContext
{
public DbSet<Tenant> Tenants { get; set; }
}
public class DataConfiguration : DbConfiguration
{
public DataConfiguration()
{
SetDatabaseInitializer(new MultiTenantContextInitializer());
}
}
public class MultiTenantContextInitializer :
CreateDatabaseIfNotExists<MultiTenantContext>
{
protected override void Seed(MultiTenantContext context)
{
var tenants = new List<Tenant>
{...}
tenants.ForEach(a => context.Tenants.Add(a));
context.SaveChanges();
}
}
I am having issues right now with initializing the database using code first. I'm basically having problems on how to trigger my initializer if my database does not exist.
I've tried the following,
https://stackoverflow.com/a/28960111/639713
but my problem with this is I have to call this method on my initial page for it to trigger the create database. Even with that, the tables will not be created unless I manually do it. This will be an issue if I'm going to integrate this with an app that is already using sql server and already have about 50 tables on the dbcontext.
Anyway, here's my code:
DbContext
public class TestMigrationsDatabase : DbContext
{
public TestMigrationsDatabase()
: base(nameOrConnectionString: "TestMigrations.Domain.Entities.TestMigrationsDatabase")
{
//Database.SetInitializer<TestMigrationsDatabase>(null);
Database.SetInitializer<TestMigrationsDatabase>(new TestMigrations.Domain.TestMigrationsInitializer());
}
public DbSet<Base> Bases { get; set; }
public DbSet<Fighter> Fighters { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("public"); // postgresql specific
base.OnModelCreating(modelBuilder);
}
public override int SaveChanges()
{
return base.SaveChanges();
}
}
Initializer:
public class TestMigrationsInitializer : CreateDatabaseIfNotExists<TestMigrationsDatabase>
{
protected override void Seed(TestMigrationsDatabase context)
{
this.CreateDatabase(ConfigurationManager.ConnectionStrings["TestMigrations.Domain.Entities.TestMigrationsDatabase"].ToString());
base.Seed(context);
LoadTestTables(context);
}
private void LoadTestTables(TestMigrationsDatabase context){
Base base = new Base();
base.Name = "Test 1 Base";
context.Bases.Add(base);
context.SaveChanges();
}
public void CreateDatabase(string connectionString)
{
var builder = new NpgsqlConnectionStringBuilder(connectionString);
var databaseName = "TestMigrations"; // REMEMBER ORIGINAL DB NAME
builder.Database = "postgres"; // TEMPORARILY USE POSTGRES DATABASE
// Create connection to database server
using (var connection = new NpgsqlConnection(builder.ConnectionString))
{
connection.Open();
// Create database
var createCommand = connection.CreateCommand();
createCommand.CommandText = string.Format(#"CREATE DATABASE ""{0}"" ENCODING = 'UTF8'", databaseName);
createCommand.ExecuteNonQuery();
connection.Close();
}
}
}
Controller
public class HomeController : Controller
{
public TestMigrationsDatabase _context = new TestMigrationsDatabase();
//
// GET: /Home/
public ActionResult Index()
{
TestMigrations.Domain.TestMigrationsInitializer initializer = new Domain.TestMigrationsInitializer();
initializer.CreateDatabase(ConfigurationManager.ConnectionStrings["TestMigrations.Domain.Entities.TestMigrationsDatabase"].ToString());
return View();
}
}
So with all that, my questions are:
1. Is putting that initializer on the first controller to trigger it correct? Or should I just instantiate the context so that the constructor will trigger teh initializer?
2. How do I properly create the tables after the database is created?
Thanks!!!
So the dynamic proxy is created, but I can't figure out what I've done wrong to prevent navigation properties from lazy loading. Here is the exact code I've run to test the issue.
DbContext:
public class MyDbContext : DbContext
{
public MyDbContext()
: base("MyConnection")
{
}
public DbSet<One> Ones { get; set; }
public DbSet<Many> Manies { get; set; }
}
Classes:
public class One
{
public int Id { get; set; }
public virtual ICollection<Many> Manies { get; set; }
public One()
{
Manies = new List<Many>();
}
}
public class Many
{
public int Id { get; set; }
public string Value { get; set; }
public int OneId { get; set; }
public virtual One One { get; set; }
public Many()
{
}
}
Test:
[TestMethod]
public void OneToManyTest()
{
One parent1 = new One();
parent1.Manies.Add(new Many() { Value = "child 1" });
parent1.Manies.Add(new Many() { Value = "child 2" });
using (MyDbContext db = new MyDbContext())
{
db.Ones.Add(parent1);
db.SaveChanges();
}
Assert.IsTrue(parent1.Id > 0, "Id not set");
One parent2;
using (MyDbContext db = new MyDbContext())
{
db.Configuration.ProxyCreationEnabled = true;
db.Configuration.LazyLoadingEnabled = true;
parent2 = db.Ones.Find(parent1.Id);//parent2 is a dynamic proxy
}
Assert.AreEqual(parent1.Id, parent2.Id);
/*parent2.Manies is null*/
Assert.AreEqual(parent1.Manies.Count, parent2.Manies.Count);//fails
}
Database:
I've verified the correct information is being inserted in the database. The relationships look good. I'm sure I'm missing something obvious.
Update
This works:
using (MyDbContext db = new MyDbContext())
{
db.Configuration.ProxyCreationEnabled = true;
db.Configuration.LazyLoadingEnabled = true;
parent2 = db.Ones.Find(parent1.Id);//parent2 is a dynamic proxy
Assert.AreEqual(parent1.Id, parent2.Id);
Assert.AreEqual(parent1.Manies.Count, parent2.Manies.Count);
}
This doesn't:
using (MyDbContext db = new MyDbContext())
{
db.Configuration.ProxyCreationEnabled = true;
db.Configuration.LazyLoadingEnabled = true;
parent2 = db.Ones.Find(parent1.Id);//parent2 is a dynamic proxy
}
using (MyDbContext db = new MyDbContext())
{
Assert.AreEqual(parent1.Id, parent2.Id);
Assert.AreEqual(parent1.Manies.Count, parent2.Manies.Count);//parent2.Manies is null
}
So the same db context is required for built in lazy loading.
To trigger lazy loading you need to access the property in some way, before disposing of the context.
Your test code doesn't acces the property before leaving the context:
One parent2;
using (MyDbContext db = new MyDbContext())
{
db.Configuration.ProxyCreationEnabled = true;
db.Configuration.LazyLoadingEnabled = true;
parent2 = db.Ones.Find(parent1.Id);//parent2 is a dynamic proxy
}
// Context disposed: thsi would throw an exception:
var manies = parent2.Manies.ToList()
At this point, your context has been disposed. If you tried to access the Manies property you'd get an error stating this.
One parent2;
using (MyDbContext db = new MyDbContext())
{
db.Configuration.ProxyCreationEnabled = true;
db.Configuration.LazyLoadingEnabled = true;
parent2 = db.Ones.Find(parent1.Id);//parent2 is a dynamic proxy
// Context available: this sill lazy load the Manies entities
var manies = parent2.Manies.ToList();
}
Now, if you check the manies properties, it will be available.
The idea of lazy loading is that, while the context is available, the first time you access a property which wasn't loaded initially, it will be loaded at that moment.
Please, see this article to understand the different ways (eager, lazy, explicit) of loading entities with EF:
Loading Related Entities
parent2 = db.Ones.Include(o=>o.Manies).FirstOrDefault(o=>o.Id == parent1.Id);
I have an entity object which is connected to another entities.
I want to loop through all entity properties , if the property is String then do something with the value.
If the property is EntityReference, I want to get it's value (it has only one), and do something with the value as well.
I was able to determine if the property is string or EntityReference.
I get the String value by -
value = typeof(entity).GetProperty(property.Name).GetValue(request, null);
but how do I get the value of an entityreference ?
Just trace the property tree.
You have the first step. repeat for lower properties.
var TopLevelProp = poco.GetType().GetProperty(property.Name).GetValue(poco, null);
var LowerProp = TopLevelProp.GetType().GetProperty("aLowerPropName").GetValue(TopLevelProp, null);
although you tagged this EF. What did you mean by entity reference ?
edit: in the hope i have covered the entity and its key question
Here is a sample Repository covering EF Context and entity access. See the Entity field and Entity KEY field methods...
public class Repository<T> : IRepositoryEF<T> where T : BaseObject {
public RepositoryEF(DbContext context) { Context = context; }
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public DbSet<T> EntityDbSet() { return Context.Set<T>(); }
public ObjectContext ObjectContext { get { return ((IObjectContextAdapter) this.Context).ObjectContext; } }
public DbContext Context { get; protected set; }
public EntityState GetEntityState(object entity) { return Context.Entry(entity).State; }
public ObjectSet<T> GetObjectSet() { return ObjectContext.CreateObjectSet<T>(); }
public IList<string> GetEntityFields() {
var entityFields = GetObjectSet().EntitySet.ElementType.Properties;
return entityFields.Select(e => e.Name).ToList();
}
public string[] GetEntityKeyFields() { return GetObjectSet().EntitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray(); }
public EntityKey GetEntityKey(T entity) {
if (entity == null) {
return null;
}
return ObjectContext.CreateEntityKey(GetObjectSet().EntitySet.Name, entity);
}
public string GetEntityKeyAsString(T entity) {
if (entity == null) {
return string.Empty;
}
var eK = GetEntityKey(entity);
var keyAsString = eK.EntityKeyValues.Aggregate("", (current, keyField) => current + keyField.Key + "=" + keyField.Value + ",");
return keyAsString;
}
}
If you want to get all the metadata in the Context:
ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
IEnumerable<EntityType> managedTypes = workspace.GetItems<EntityType>(DataSpace.OSpace);
You can go to town on the meta data. see all enums values in DataSpace to get at various parts of the model