Getting metadata in EF Core: table and column mappings - entity-framework-core

Looking to get metadata in EF Core, to work with the mappings of objects & properties to database tables & columns.
These mappings are defined in the DBContext.cs OnModelCreating() method, mapping tables with .ToTable(), and columns via .Property.HasColumnName().
But I don't see this metadata under the Entity Types returned by...
IEnumerable<IEntityType> entityTypes = [dbContext].Model.GetEntityTypes();
Is this metadata available anywhere in EF Core?

Is this metadata available anywhere in EF Core?
Yes it is. Just additionally to the properties examine the methods (GetXXX, FindXXX etc.). And pay special attention to Relational() extension methods.
For instance:
foreach (var entityType in dbContext.Model.GetEntityTypes())
{
var tableName = entityType.Relational().TableName;
foreach (var propertyType in entityType.GetProperties())
{
var columnName = propertyType.Relational().ColumnName;
}
}
You need to have Microsoft.EntityFrameworkCore.Relational Nuget package installed.
Update (EF Core 3.0+): Relational() provider extensions have been removed and properties have been replaced with direct Get / Set extension methods, so the code for column/table names now is simply
var tableName = entityType.GetTableName();
// ..
var columnName = propertyType.GetColumnName();
Update (EF Core 5.0+): Since now EF Core supports separate column name mappings for table, view, sql etc., you have to pass the desired StoreObjectIdentifier to GetColumnName method, e.g.
var tableName = entityType.GetTableName();
var tableIdentifier = StoreObjectIdentifier.Table(tableName, entityType.GetSchema());
// ...
var tableColumnName = propertyType.GetColumnName(tableIdentifier);

Related

Copying a table in Entity Framework

Using .NET Core 2.2 and Entity Framework what is the easiest way to copy a database table to a new database table.
i.e. creating an archive copy of that table.
I suggest using raw sql in EntityFrameworkCore to accomplish what you need.
dbContext.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction,
"INSERT INTO TABLE2
SELECT * FROM TABLE1" );
if memory is not an issue
var sourceFiles = _context.SourceTables.ToList();
foreach(var sourceFile in sourceFiles)
{
//if matching entity
_context.DestinationTables.Add(sourceFile);
//if not matching
var destination = new DestinationEntity
{
Prop1 = sourceFile.Prop1,
//other properties
}
_context.DestinationTables.Add(destination);
//if need to remove
_context.SourceTables.Remove(sourceFile);
}
_context.SaveChanges();

How can I specify the XElement attribute prefix for an EF edmx <EntitySet> view type?

Using C# on a Win7 machine I am needing to dynamically change the schema type on an Entity Framework 6.2 edmx xml file prior to instansiating EF. I can filter the edmx Xml for the "Schema" attribute for tables but not for views. Tables use the non prefixed Attribute "Schema" and Views for some odd reason use the prefixed Attribute "store:Schema".
Unfortunately if I filter Attributes for the obvious "store:Schema" I get an XmlException saying "The ':' character, hexadecimal value 0x3A, cannot be included in a name.". I've tried various ways to filter for this needed Attribute so that I can change the Attribute to my custom schema name so that I can later have EF run a view from another schema than "dbo". My XML code is below. How can I filter the storage Descendants for a prefixed Attribute?
// How can I specify the schema prefix for an EF edmx <EntitySet> view type?
// Table <EntitySet> has a non prefixed attribute call "Schema"
// EF view <EntitySet> has a prefixed attribute called "store:Schema" that I cannot seem to filter for
// Filtering with "store:Schema" throws an XmlException
// - "The ':' character, hexadecimal value 0x3A, cannot be included in a name."
//
// Change the schema for all the tables and views from the dbo (or whatever) EF specified schema (if any)
// to the desired schema.
foreach (var entitySet in storageXml.Descendants(storageNS + "EntitySet"))
{
// this works fine for tables but views have prefixed attribute "store:Schema" so are not found
var schemaAttribute = entitySet.Attributes("Schema").FirstOrDefault();
if (schemaAttribute != null)
{
schemaAttribute.SetValue(schema);
}
// EF storage xml species a prefix on view Schema attribute as "store:Schema" so are not found
// here we try various ways to get any "store:Scehma" attributes so far unsuccessfully
// the most obvious incantaion throws an XmlException as mentioned above
schemaAttribute = entitySet.Attributes("store:Schema").FirstOrDefault();
// the next three run fine but do not filter out the needed attribute
//schemaAttribute = entitySet.Attributes(XName.Get("store", "Schema")).FirstOrDefault();
//schemaAttribute = entitySet.Attributes(XName.Get("Schema", "store")).FirstOrDefault();
//XNamespace ns = "store";
//schemaAttribute = entitySet.Attributes(ns + "Schema").FirstOrDefault();
if (schemaAttribute != null)
{
// I never hit this though can see the desired XElement w/ a "store:Schema" attribute
// I need to reset this schema // does it need to be set as store: prefixed?
schemaAttribute.SetValue(schema);
}
}
Changing the code to use the NameSpace prefix like this works.
XNamespace store = "http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator";
schemaAttribute = entitySet.Attributes(store + "Schema").FirstOrDefault();
if (schemaAttribute != null)
{
schemaAttribute.SetValue(schema);
}

Calling stored procedure from Entity Framework 3.5

I"m using VS 2010 & EF 3.5. I've imported a stored procedure which returns a list of guids using the Function Import feature. How do I invoke it in my code? After instantiating the dbcontext, intellisense doesn't display the procedure I've imported. I know it's pretty easy in EF 4.0 but I'm stuck with EF 3.5 for this project. Any ideas on how get around this other than doing it the old-fashioned way?
I don't think EF versions prior to 4 can use imported stored procedures that don't return entities. That is, your stored procedure must return a complete entity object in order for EF to use it. Since your procedure only returns a list of GUIDs, EF doesn't know how to use it.
You can put this in your partial data-context class to call the procedure:
public IEnumerable<Guid> GetMyGUIDs()
{
if (this.Connection.State != System.Data.ConnectionState.Open)
this.Connection.Open();
var command = new System.Data.EntityClient.EntityCommand
{
CommandType = System.Data.CommandType.StoredProcedure,
CommandText = #"YourContext.YourProcedureName",
Connection = (System.Data.EntityClient.EntityConnection)this.Connection
};
var list = new List<Guid>();
using (var reader = command.ExecuteReader())
{
// get GUID values from the reader here,
// and put them in the list
reader.Close();
}
return list;
}

Execute StoredProcedure in CodeFirst 4.1

I understand stored procedures mapping is not supported by my understanding is that I should be able to call stored procedures.
I have quite a few complex stored procedures and with the designer I could create a complex type and I was all good.
Now in code first let's suppose I have the following stored procedure, just put together something silly to give an idea. I want to return a student with 1 address.
In code I have A Student and Address Entity. But no StudentAddressEntity as it's a link table.
I have tried the following but I get an error
Incorrect syntax near '."}
System.Data.Common.DbException {System.Data.SqlClient.SqlException}
ALTER Procedure [dbo].[GetStudentById]
#StudentID int
AS
SELECT *
FROM Student S
left join StudentAddress SA on S.Studentid = sa.studentid
left join Address A on SA.AddressID = A.AddressID
where S.StudentID = #StudentID
C# code:
using (var ctx = new SchoolContext())
{
var student = ctx.Database.SqlQuery<Student>("GetStudentById,#StudentID",
new SqlParameter("StudentID", id));
}
Any examples out there how to call sp and fill a complexType in code first, using out parameters etc.. Can I hook into ADO.NET?
Trying just an SP that returns all students with no parameters I get this error
System.SystemException = Cannot create a value for property
'StudentAddress' of type
'CodeFirstPrototype.Dal.Address'. Only
properties with primitive types are
supported.
Is it because I have in a way ignore the link table?
Any suggestions?
I believe that your exception actually is:
Incorrect syntax near ','.
because this is invalid statement: "GetStudentById,#StudentID". It should be without comma: "GetStudentById #StudentID".
The problem with stored procedures in EF is that they don't support loading navigation properties. EF will materialize only the main entity and navigation properties will not be loaded. This is solved for example by EFExtensions. EFExtensions are for ObjectContext API so you will have to check if it is also usable for DbContext API.
Using EFExtentions it will look something like
using (var context = new SchoolContext())
{
var command = context.CreateStoreCommand("GetStudentById", CommandType.StoredProcedure,
new SqlParameter("StudentID", id));
using (command.Connection.CreateConnectionScope())
using (var reader = command.ExecuteReader())
{
// use the reader to read the data
// my recommendation is to create a Materializer using EFExtensions see
// http://blogs.msdn.com/b/meek/archive/2008/03/26/ado-entity-framework-stored-procedure-customization.aspx
// ex
var student = Student.Materializer.Materialize(reader).SingleOrDefault();
return student;
}
}

Add index with entity framework code first (CTP5)

Is there a way to get EF CTP5 to create an index when it creates a schema?
Update: See here for how EF 6.1 handles this (as pointed out by juFo below).
You can take advantage of the new CTP5’s ExecuteSqlCommand method on Database class which allows raw SQL commands to be executed against the database.
The best place to invoke SqlCommand method for this purpose is inside a Seed method that has been overridden in a custom Initializer class. For example:
protected override void Seed(EntityMappingContext context)
{
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
}
As some mentioned in the comments to Mortezas answer there is a CreateIndex/DropIndex method if you use migrations.
But if you are in "debug"/development mode and is changing the schema all the time and are recreating the database every time you can use the example mentioned in Morteza answer.
To make it a little easier, I have written a very simple extension method to make it strongly typed, as inspiration that I want to share with anyone who reads this question and maybe would like this approach aswell. Just change it to fit your needs and way of naming indexes.
You use it like this: context.Database.CreateUniqueIndex<User>(x => x.Name);
.
public static void CreateUniqueIndex<TModel>(this Database database, Expression<Func<TModel, object>> expression)
{
if (database == null)
throw new ArgumentNullException("database");
// Assumes singular table name matching the name of the Model type
var tableName = typeof(TModel).Name;
var columnName = GetLambdaExpressionName(expression.Body);
var indexName = string.Format("IX_{0}_{1}", tableName, columnName);
var createIndexSql = string.Format("CREATE UNIQUE INDEX {0} ON {1} ({2})", indexName, tableName, columnName);
database.ExecuteSqlCommand(createIndexSql);
}
public static string GetLambdaExpressionName(Expression expression)
{
MemberExpression memberExp = expression as MemberExpression;
if (memberExp == null)
{
// Check if it is an UnaryExpression and unwrap it
var unaryExp = expression as UnaryExpression;
if (unaryExp != null)
memberExp = unaryExp.Operand as MemberExpression;
}
if (memberExp == null)
throw new ArgumentException("Cannot get name from expression", "expression");
return memberExp.Member.Name;
}
Update: From version 6.1 and onwards there is an [Index] attribute available.
For more info, see http://msdn.microsoft.com/en-US/data/jj591583#Index
This feature should be available in the near-future via data annotations and the Fluent API. Microsoft have added it into their public backlog:
http://entityframework.codeplex.com/workitem/list/basic?keywords=DevDiv [Id=87553]
Until then, you'll need to use a seed method on a custom Initializer class to execute the SQL to create the unique index, and if you're using code-first migrations, create a new migration for adding the unique index, and use the CreateIndex and DropIndex methods in your Up and Down methods for the migration to create and drop the index.
Check my answer here Entity Framework Code First Fluent Api: Adding Indexes to columns this allows you to define multi column indexes by using attributes on properties.