I'm working with asp.net mvc3.
I have a edmx that was created ADO.NET Entity Data Model. (Data-First)
TestDb.Designer.cs
namespace Test.Web.DataModel
{
public partial class TestDbContext : ObjectContext
{
public ObjectSet<T_Members> T_Members { ... }
public ObjectSet<T_Documents> T_Documents { ... }
....
}
}
How to get an ObjectSet by name(string)?
For example, I want to be this.
var members = context.GetTable("T_Members"); // var members = context.T_Members;
I'm not that familiar with ObjectContext internals, so there might be a more 'native' way,
...but what you need is some reflection to get your properties out.
(note: I don't have any db-first handy to check, so you'd need to adjust if some typo - should work though)
public static object GetTableFromName(this TestDbContext db, string propertyName)
{
PropertyInfo property = null;
property = typeof(TestDbContext)
.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(db, null);
}
public static object GetTableFromType(this TestDbContext db, string tableTypeName)
{
// var tableType = typeof(TestDbContext).Assembly.GetType("YourNamespace.T_Members");
var tableType = Type.GetType(tableTypeName);
var genericType = typeof(ObjectSet<>).MakeGenericType(new[] { tableType });
var property = typeof(TestDbContext)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.PropertyType == genericType).FirstOrDefault();
return property.GetValue(db, null);
}
and use it like
var table = db.GetTableFromName("T_Members");
table = db.GetTableFromType("YourNamespace.T_Members);
// this gets you the `object` you'd still need to 'cast' or something
Related
I'm trying to implement item versioning using EF, and what I need to know is whether or not the entity which I called Update() on actually got changed, so I can increment its version number. How can I obtain this information?
My repository Update function looks like this:
public virtual void Update(T entity)
{
dbset.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
}
What I opted for afterall was comparing the serialized versions of the 2:
public void UpdateProduct(Product product)
{
var productInDb = GetByID(product.Id);
if (!JToken.DeepEquals(JsonConvert.SerializeObject(product), JsonConvert.SerializeObject(productInDb)))
product.CurrentVersion++;
product = Update(product);
}
You can get the information from the entity state.
If a property has been changed, the state will be "Modified" otherwise "Unchanged".
using (var ctx = new TestContext())
{
var first = ctx.Entity_Basics.First();
var x1 = ctx.IsModified(first); // false
first.ColumnInt = 9999;
var x2 = ctx.IsModified(first); // true
}
public static class Extensions
{
public static bool IsModified <T>(this DbContext context, T entity) where T : class
{
return context.Entry(entity).State == EntityState.Modified;
}
}
I'd like to assign schema name for my entity, without specifying table name. Now, I can do only: modelBuilder.Entity<T>().ToTable("MyEntities", "myschema"); is there a way to do something like: modelBuilder.Entity<T>().ToTable("myschema") ? Please, take into account that I cannot use PluralizationService and calculate tablename manually, since PluralizationService become internal...
How about...
var t = typeof (T);
var name= t.Name;
modelBuilder.Entity<T>().ToTable(name, "myschema")
If you need the DbSet plural name from the context
public DbSet<Single> Plural{ get; set; }
Then this little extension can be reworked to return the value you want. A combo of both without loop. But im sure you will find the right variation...
public static class BosDalExtensions
{
public static List<string> GetModelNames(this DbContext context ) {
var model = new List<string>();
var propList = context.GetType().GetProperties();
foreach (var propertyInfo in propList)
{
if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
{
model.Add(propertyInfo.Name);
}
}
return model;
}
public static List<string> GetModelTypes(this DbContext context)
{
var model = new List<string>();
var propList = context.GetType().GetProperties();
foreach (var propertyInfo in propList)
{
if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet" ))
{
model.Add(propertyInfo.PropertyType.GenericTypeArguments[0].Name);
}
}
return model;
}
}
I'd like to define an enum for EF5 to use, and a corresponding lookup table. I know EF5 now supports enums, but out-of-the-box, it seems it only supports this at the object level, and does not by default add a table for these lookup values.
For example, I have a User entity:
public class User
{
int Id { get; set; }
string Name { get; set; }
UserType UserType { get; set; }
}
And a UserType enum:
public enum UserType
{
Member = 1,
Moderator = 2,
Administrator = 3
}
I would like for database generation to create a table, something like:
create table UserType
(
Id int,
Name nvarchar(max)
)
Is this possible?
Here's a nuget package I made earlier that generates lookup tables and applies foreign keys, and keeps the lookup table rows in sync with the enum:
https://www.nuget.org/packages/ef-enum-to-lookup
Add that to your project and call the Apply method.
Documentation on github: https://github.com/timabell/ef-enum-to-lookup
It is not directly possible. EF supports enums on the same level as .NET so enum value is just named integer => enum property in class is always integer column in the database. If you want to have table as well you need to create it manually in your own database initializer together with foreign key in User and fill it with enum values.
I made some proposal on user voice to allow more complex mappings. If you find it useful you can vote for the proposal.
I wrote a little helper class, that creates a database table for the enums specified in the UserEntities class. It also creates a foreign key on the tables that referencing the enum.
So here it is:
public class EntityHelper
{
public static void Seed(DbContext context)
{
var contextProperties = context.GetType().GetProperties();
List<PropertyInfo> enumSets = contextProperties.Where(p =>IsSubclassOfRawGeneric(typeof(EnumSet<>),p.PropertyType)).ToList();
foreach (var enumType in enumSets)
{
var referencingTpyes = GetReferencingTypes(enumType, contextProperties);
CreateEnumTable(enumType, referencingTpyes, context);
}
}
private static void CreateEnumTable(PropertyInfo enumProperty, List<PropertyInfo> referencingTypes, DbContext context)
{
var enumType = enumProperty.PropertyType.GetGenericArguments()[0];
//create table
var command = string.Format(
"CREATE TABLE {0} ([Id] [int] NOT NULL,[Value] [varchar](50) NOT NULL,CONSTRAINT pk_{0}_Id PRIMARY KEY (Id));", enumType.Name);
context.Database.ExecuteSqlCommand(command);
//insert value
foreach (var enumvalue in Enum.GetValues(enumType))
{
command = string.Format("INSERT INTO {0} VALUES({1},'{2}');", enumType.Name, (int)enumvalue,
enumvalue);
context.Database.ExecuteSqlCommand(command);
}
//foreign keys
foreach (var referencingType in referencingTypes)
{
var tableType = referencingType.PropertyType.GetGenericArguments()[0];
foreach (var propertyInfo in tableType.GetProperties())
{
if (propertyInfo.PropertyType == enumType)
{
var command2 = string.Format("ALTER TABLE {0} WITH CHECK ADD CONSTRAINT [FK_{0}_{1}] FOREIGN KEY({2}) REFERENCES {1}([Id])",
tableType.Name, enumProperty.Name, propertyInfo.Name
);
context.Database.ExecuteSqlCommand(command2);
}
}
}
}
private static List<PropertyInfo> GetReferencingTypes(PropertyInfo enumProperty, IEnumerable<PropertyInfo> contextProperties)
{
var result = new List<PropertyInfo>();
var enumType = enumProperty.PropertyType.GetGenericArguments()[0];
foreach (var contextProperty in contextProperties)
{
if (IsSubclassOfRawGeneric(typeof(DbSet<>), contextProperty.PropertyType))
{
var tableType = contextProperty.PropertyType.GetGenericArguments()[0];
foreach (var propertyInfo in tableType.GetProperties())
{
if (propertyInfo.PropertyType == enumType)
result.Add(contextProperty);
}
}
}
return result;
}
private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck)
{
while (toCheck != null && toCheck != typeof(object))
{
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (generic == cur)
{
return true;
}
toCheck = toCheck.BaseType;
}
return false;
}
public class EnumSet<T>
{
}
}
using the code:
public partial class UserEntities : DbContext{
public DbSet<User> User { get; set; }
public EntityHelper.EnumSet<UserType> UserType { get; set; }
public static void CreateDatabase(){
using (var db = new UserEntities()){
db.Database.CreateIfNotExists();
db.Database.Initialize(true);
EntityHelper.Seed(db);
}
}
}
I have created a package for it
https://www.nuget.org/packages/SSW.Data.EF.Enums/1.0.0
Use
EnumTableGenerator.Run("your object context", "assembly that contains enums");
"your object context" - is your EntityFramework DbContext
"assembly that contains enums" - an assembly that contains your enums
Call EnumTableGenerator.Run as part of your seed function. This will create tables in sql server for each Enum and populate it with correct data.
I have included this answer as I've made some additional changes from #HerrKater
I made a small addition to Herr Kater's Answer (also based on Tim Abell's comment). The update is to use a method to get the enum value from the DisplayName Attribute if exists else split the PascalCase enum value.
private static string GetDisplayValue(object value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
var descriptionAttributes = fieldInfo.GetCustomAttributes(
typeof(DisplayAttribute), false) as DisplayAttribute[];
if (descriptionAttributes == null) return string.Empty;
return (descriptionAttributes.Length > 0)
? descriptionAttributes[0].Name
: System.Text.RegularExpressions.Regex.Replace(value.ToString(), "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ");
}
Update Herr Katers example to call the method:
command = string.Format("INSERT INTO {0} VALUES({1},'{2}');", enumType.Name, (int)enumvalue,
GetDisplayValue(enumvalue));
Enum Example
public enum PaymentMethod
{
[Display(Name = "Credit Card")]
CreditCard = 1,
[Display(Name = "Direct Debit")]
DirectDebit = 2
}
you must customize your workflow of generation
1. Copy your default template of generation TablePerTypeStrategy
Location : \Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\DBGen.
2. Add custom activity who realize your need (Workflow Foundation)
3. Modify your section Database Generation Workflow in your project EF
I have a DbContext that is empty. Mappings are created dynamically and the DbContext is used generically using Set();
The following is my generic DbContext.
/// <summary>
/// Object context
/// </summary>
public class MethodObjectContext : DbContext, IDbContext
{
private readonly IEventPublisher _eventPublisher;
public MethodObjectContext(string nameOrConnectionString, IEventPublisher eventPublisher)
: base(nameOrConnectionString)
{
_eventPublisher = eventPublisher;
}
public MethodObjectContext(DbConnection existingConnection, bool contextOwnsConnection, IEventPublisher eventPublisher)
: base(existingConnection, contextOwnsConnection)
{
_eventPublisher = eventPublisher;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
_eventPublisher.Publish(new ModelCreating(modelBuilder));
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
}
I am trying write a unit test that will assert that the database is out of sync if I change the mappings (from the ModelCreating event).
The following is my test code.
[TestClass]
public class MigrationTests
{
private string _connectionString = string.Empty;
private string _testDb = string.Empty;
public MigrationTests()
{
_testDb = Path.Combine("C:\\", System.Reflection.Assembly.GetExecutingAssembly().GetName().Name.Replace(".", "") + ".sdf");
if (File.Exists(_testDb))
File.Delete(_testDb);
_connectionString = string.Format("Data Source={0};Persist Security Info=False;", _testDb);
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
}
[TestMethod]
public void ThrowsErrorForOutOfDateDatabase()
{
// The initializer will handle migrating the database.
// If ctor param is false, auto migration is off and an error will be throw saying the database is out of date.
Database.SetInitializer(new MigrationDatabaseInitializer<MethodObjectContext>(false));
// Create the initial database and do a query.
// This will create the database with the conventions of the Product1 type.
TryQueryType<Product1>("Product");
// The next query will create a new model that has conventions for the product2 type.
// It has an additional property which makes the database (created from previous query) out of date.
// An error should be thrown indicating that the database is out of sync.
ExceptionAssert.Throws<InvalidOperationException>(() => TryQueryType<Product2>("Product"));
}
private void TryQueryType<T>(string tableName) where T : class
{
using (var context = new MethodObjectContext(_connectionString, new FakeEventPublisher(x => x.ModelBuilder.Entity<T>().ToTable(tableName))))
{
var respository = new EfRepository<T>(context);
var items = respository.Table.ToList();
}
}
}
My Product1 class is a POCO object, and my Product2 class is the same object with an additional db field.
My problem is that when I new() up the MethodObjectContext the second time and do a query, the ModelCreating method isn't called, causing me to get the following error.
The entity type Product2 is not part of the model for the current context.
Product2 would be a part of the context of the ModelCreating event was being called, but it is not. Any ideas?
NOTE: I am expecting errors since we are using the same connection string (sdf) and the db being created didn't create the additional field that my second call (Product2) requires.
My DbCompiledModel was being cached. The following flushed the cache.
private void ClearDbCompiledModelCache()
{
var type = Type.GetType("System.Data.Entity.Internal.LazyInternalContext, EntityFramework");
var cmField = type.GetField("CachedModels",System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
var cachedModels = cmField.GetValue(null);
cachedModels.GetType().InvokeMember("Clear", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, cachedModels, null);
}
Is it possible? I know I can get the name, when I've specified the TableAttribute, but it should be possible even so, when I let the framework manage the name.
Thanks in advance.
I ended up with this:
public static class DbContextExt
{
public static string GetTableName<T>(this DbContext context) where T : class
{
var type = typeof(T);
var entityName = (context as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext.CreateObjectSet<T>().EntitySet.Name;
var tableAttribute = type.GetCustomAttributes(false).OfType<System.ComponentModel.DataAnnotations.Schema.TableAttribute>().FirstOrDefault();
return tableAttribute == null ? entityName : tableAttribute.Name;
}
}
It's a hybrid of the two answers here: DBset tabel name.
The proper way to do this is to use the GetTableName method from the following page: http://romiller.com/2014/04/08/ef6-1-mapping-between-types-tables/
This includes support for the meta tag and model builder .ToTable() changes. The examples on this page basically return the DbSet property name which is not necessarily the table name in the database.
For example if you had:
DbSet<Config> Settings { get; set; }
The code on this page would return "Settings" for a table name when the actual DB table name is "Configs". And you would have the same issues if you used:
modelBuilder.Entity<Config>().ToTable("UserSettings")
Using the code in the provided link alleviates all of these issues. Here it is written as an extension:
public static class DbContextExtensions
{
public static string GetTableName<T>(this DbContext context) where T : class
{
var type = typeof(T);
var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
// Get the part of the model that contains info about the actual CLR types
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
// Get the entity type from the model that maps to the CLR type
var entityType = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == type);
// Get the entity set that uses this entity type
var entitySet = metadata
.GetItems<EntityContainer>(DataSpace.CSpace)
.Single()
.EntitySets
.Single(s => s.ElementType.Name == entityType.Name);
// Find the mapping between conceptual and storage model for this entity set
var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
.Single()
.EntitySetMappings
.Single(s => s.EntitySet == entitySet);
// Find the storage entity set (table) that the entity is mapped
var table = mapping
.EntityTypeMappings.Single()
.Fragments.Single()
.StoreEntitySet;
// Return the table name from the storage entity set
return (string)table.MetadataProperties["Table"].Value ?? table.Name;
}
}
If you don't use TableAttribute or fluent api to define the table name, the name will be inferred from the name of DbSet property in the context. The only thing which can modify the name in such case is pluralization convention which is used by default.
So if you have:
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
}
The table should be named Users.
This should handle Table per Type and Table per Hierarchy inheritance.
See:
http://weblogs.asp.net/manavi/archive/2010/12/24/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-1-table-per-hierarchy-tph.aspx
The trick is to walk the inheritance tree until you find an overriden Table attribute or a non-object base type. This could potentially fail if its possible to inherit from a class that's not mapped to a table...which I'm not sure about. If you can do [NotMapped] on a class, then we'd just have to alter the GetTableDefType method to walk backwards once in that case.
private static Type GetTableDefType(this Type t, out TableAttribute attr) {
attr = null;
if (t == typeof(Object)) { throw new ArgumentException(); }
var tType = t;
while (true) {
attr = tType.GetCustomAttributes(false).OfType<TableAttribute>().FirstOrDefault();
if (attr != null) { return tType; }
if (tType.BaseType == null || tType.BaseType == typeof(Object)) { return tType; }
tType = tType.BaseType;
}
}
public static string GetTableName(this DbContext context, Type type) {
TableAttribute testAttr = null;
var baseType = type.GetTableDefType(out testAttr);
if (testAttr != null) { return testAttr.TableName; }
var propBinding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty;
var objectContext = context.GetType().GetInterface("System.Data.Entity.Infrastructure.IObjectContextAdapter").GetProperty("ObjectContext", propBinding).GetValue(context, null);
var objectSet = objectContext.GetType().GetMethod("CreateObjectSet", new Type[0]).MakeGenericMethod(baseType).Invoke(objectContext, null);
return ((EntitySet)objectSet.GetType().GetProperty("EntitySet", propBinding).GetValue(objectSet, null)).Name;
}