I'm using the Poco & DbContext T4 template in vs 2010 to generate the pocos for my data access, i've modified it to match some syntax required by my data access layer.
I've one problem that, the identity column (StoreGeneratedPattern = Identity) in the edmx file dosn't affect the T4 generating process, here is my code for it:
var identity = edmProperty.TypeUsage.Facets.Where(f => f.Name == "StoreGeneratedPattern").FirstOrDefault();
if (identity != null && ((System.Data.Metadata.Edm.StoreGeneratedPattern)identity.Value) == System.Data.Metadata.Edm.StoreGeneratedPattern.Identity)
isIdentity = true;
this is always be false , is there a any reason for this?
StoreGeneratedPattern is not stored in facets but in MetadataProperties try something like this:
var identity = edmProperty.MetadataProperties
.Where(m => m.Name == "http://schemas.microsoft.com/ado/2009/02/edm/annotation:StoreGeneratedPattern")
.FirstOrDefault();
bool isIdentity = identity != null && identity.Value == System.Data.Metadata.Edm.StoreGeneratedPattern.Identity.ToString();
Ladislav has indeed right.
(this post hopefully can spare time for someone who are trying to use IsStoreGeneratedIdentity, IsStoreGeneratedComputed and StoreGeneratedPattern regardless of T4.) It did cost me more than 2 hours to locate the problem and with narrowed googling keywords locate this post)
Interestingly enough even MS does not know about this fact, in its latest EF 6 in 2015, and EdmProperty class exposes trivially not working properties. It also interesting how can these simple (not working) properties pass a unit test in the last 4 years? The question arises, how many places these not working properties are utilized inside and dependent utilities and cause issues?
Not working IsStoreGeneratedIdentitycode from MS EF6 assembly:
public bool IsStoreGeneratedIdentity
{
get
{
Facet facet;
if (this.TypeUsage.Facets.TryGetValue("StoreGeneratedPattern", false, out facet))
return (StoreGeneratedPattern) facet.Value == StoreGeneratedPattern.Identity;
return false;
}
}
Not working IsStoreGeneratedComputedcode from MS EF6 assembly:
public bool IsStoreGeneratedComputed
{
get
{
Facet facet;
if (this.TypeUsage.Facets.TryGetValue("StoreGeneratedPattern", false, out facet))
return (StoreGeneratedPattern) facet.Value == StoreGeneratedPattern.Computed;
return false;
}
}
And the the StoreGeneratedPattern property uses this underlying and not working code in MS EF6 assembly:
internal static StoreGeneratedPattern GetStoreGeneratedPattern(EdmMember member)
{
Facet facet;
if (member.TypeUsage.Facets.TryGetValue("StoreGeneratedPattern", false, out facet) && facet.Value != null)
return (StoreGeneratedPattern) facet.Value;
return StoreGeneratedPattern.None;
}
Code comes from:
// Type: System.Data.Entity.Core.Metadata.Edm.EdmMember
// Assembly: EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// MVID: 9A9FD4AC-352D-4B91-95F8-2AF29ABDC792
// Assembly location: C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\EntityFramework.dll
Finally I've ended with the following extension methods as a replacement of the not working original ones:
public static class EdmMemberExtensions
{
public static StoreGeneratedPattern StoreGeneratedPattern2(this EdmMember #this)
{
const string name = "http://schemas.microsoft.com/ado/2009/02/edm/annotation:StoreGeneratedPattern";
var metaDataProperty = #this.MetadataProperties.FirstOrDefault(m => m.Name == name);
if (metaDataProperty == null)
{
return StoreGeneratedPattern.None;
}
return (StoreGeneratedPattern) Enum.Parse(typeof (StoreGeneratedPattern), (string) metaDataProperty.Value);
}
public static bool IsStoreGeneratedIdentity2(EdmMember #this)
{
return StoreGeneratedPattern2(#this) == StoreGeneratedPattern.Identity;
}
public static bool IsStoreGeneratedComputed2(EdmMember #this)
{
return StoreGeneratedPattern2(#this) == StoreGeneratedPattern.Computed;
}
}
Related
I have a problem with EF Core 5 that is really getting me down.
FYI, LazyLoadingProxies are used (something else that just gives me a headache, but well, different topic).
Information for the code below:
Service: A service per entity, contains all CRUD operations into the database and other methods if needed.
Workflow: Uses multiple services at once to perform certain operations (e.g. create product -> create product folder -> save product).
Problem:
I have an entity "Product" which contains the following update method which is used to update the properties of the entity with those of another object:
public override void Update(Product source)
{
// Properties
AnnualPrice = source.AnnualPrice;
...
// Relations
var sourceRelatedProductIds = source.RelatedWithProductIds.Where(x => x != Id);
if (sourceRelatedProductIds.Count() != 0)
{
RelatedWithProducts.Clear();
foreach (var relatedWithProduct in ctx.Set<Product>().Where(x => source.RelatedWithProductIds.Contains(x.Id)).AsNoTracking())
{
RelatedWithProducts.Add(relatedWithProduct);
}
}
var oldShortDescriptions = ShortDescriptions.ToList(); <--- EXCEPTION
ShortDescriptions.Clear();
foreach (var shortDescription in source.ShortDescriptions)
{
shortDescription.Id = oldShortDescriptions.FirstOrDefault(x => x.Culture == shortDescription.Culture)?.Id ?? 0;
ShortDescriptions.Add(shortDescription);
}
...
}
In the line with the arrow and "Exception", I get the following exception:
System.InvalidOperationException: 'The instance of entity type 'Product' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
In itself, I understand what the exception is trying to tell me. My problem is that I can't find the reason for it anywhere. Because as far as I can tell, the product with ID 1 can't be tracked yet.
Of course, the problem is not in the update method, but before it, so here is the rest of the code.
ProductController.Update:
[HttpPut("update")]
public IActionResult Update(C.Product[] products)
{
if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState.Values.SelectMany(x => x.Errors));
}
var dbProducts = products.Select(ToDatabase).ToArray(); <--- Just converts the given client model into a Database model
var result = productWorkflow.Update(dbProducts); <--- Calls a workflow class, NOT the update method of the entity
return CoreToActionResultConverter.ToActionResult<Db.Product>(result);
}
ProductWorkflow.Update:
public ResultBase Update(params Product[] products)
{
var result = productService.AddOrUpdate(products); <--- This calls the Service CRUD AddOrUpdate method
if (result is not ServiceResult<Product>)
{
return result;
}
return new ServiceResult<Product>(ResultType.AddedOrUpdated);
}
ProductService.AddOrUpdate:
public virtual ResultBase AddOrUpdate(IEnumerable<TEntity> entities)
{
var currentEntities = new List<TEntity>();
foreach (var entity in entities)
{
var currentEntity = Get(entity.Id); <--- This line is the only one where I could imagine that it is already tracked here. The problem is only that it does not work ONLY with the workflow. If I call my AddOrUpdate method from the controller, which directly calls THIS method, it works (although this line is just executed the same way).
if (currentEntity == null)
{
currentEntity = Ctx.CreateProxy<TEntity>();
Ctx.Attach(currentEntity);
}
if (currentEntity != entity)
{
currentEntity.Update(entity);
}
currentEntities.Add(currentEntity);
}
Ctx.AddRange(currentEntities.Where(x => x.Id == 0));
Ctx.UpdateRange(currentEntities.Where(x => x.Id != 0));
try
{
Ctx.SaveChanges();
}
catch (DbUpdateException ex)
{
// Commented out the error handling to remove unnecessary things for the post
}
return new ServiceResult<TEntity>(ServiceResult.ResultType.AddedOrUpdated, currentEntities);
}
I found the problem and it was not on the line where the exception was thrown, but before.
In my Product.Update() method (the first code snippet), I get the Related Products by ID and add them to the list (Simply a Many to Many relationship, from Product <--> Product). When calling Update, I specified ID 1 in the RelatedProductIds, however the entity itself also has ID 1, so it references itself. I have now just fixed that by omitting the ID, if the same as the object itself.
This still doesn't explain why it works with a breakpoint, because it's still tracked in that case (or not tracked, since I'm using AsNoTracking(), but good).
I made a DLL to include in an ASP (MVC) project, in the DLL there is some 'base' functionality and ofcourse, I'm including it in other MVC projects.
The problem is I have a EF - Code first project now and it's using a Configuration class for the migrations:
internal sealed class Configuration : DbMigrationsConfiguration<Project.Models.DbContextTest>
{
public Configuration()
{
//AutomaticMigrationsEnabled = true;
}
protected override void Seed(Project.Models.DbContextTest context)
{
// 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" }
// );
//
}
}
When I'm using this configuration i get the following exception:
Sequence contains more than one matching element
On the following function:
return orderBy != null ? orderBy(query).ToList() : query.ToList();
The function is not the problem, because when i remove the configuration class everything is working fine.
The exception is thrown on a query where I get a list of a table, it only has a Primary Key and no related tables (Foreign Keys).
The configuration class is empty, so I'm wondering what it's doing to make my Dll broken?
Thanks in advance,
Stefan
It was a combination of starting the project and doing manual migration.
When starting the project it's auto migrating the code-first models, this in combination with the manual migration gives this errors / exceptions.
I was using a convention like this in the beta version of EF6:
public class NavigationPropertyConfigurationConvention : IConfigurationConvention<PropertyInfo, NavigationPropertyConfiguration>
{
public void Apply(PropertyInfo propertyInfo, Func<NavigationPropertyConfiguration> configuration)
{
var foreignKeyProperty = propertyInfo.DeclaringType.GetProperty("Id" + propertyInfo.Name);
if (foreignKeyProperty != null && configuration().Constraint == null)
{
var fkConstraint = new ForeignKeyConstraintConfiguration();
fkConstraint.AddColumn(foreignKeyProperty);
configuration().Constraint = fkConstraint;
}
}
}
But with the IConfigurationConvention interface has been marked as internal, I can't upgrade my EF's references. Have searched by many places, but not found how to reproduce that functionality in the RTM version.
I have also tried this, but seems only works with independent associations (IAs), what is not my case cause I have the FKs in CLR objects.
Has anyone done it works again?
Thanks!
I'm a little confused as to the purpose of a data model in Entity Framework code-first. Because EF will auto-generate a database from scratch for you if it doesn't already exist using nothing more than the data model (including data annotations and Fluent API stuff in DbContext.OnModelCreating), I was assuming that the data model should fully describe your database's structure, and you wouldn't need to modify anything fundamental after that.
However, I came across this Codeplex issue in which one of the EF Triage Team members suggests that custom indexes be added in data migrations, but not as annotations to your data model fields, or Fluent API code.
But wouldn't that mean that anyone auto-generating the database from scratch would not get those custom indexes added to their DB? The assumption seems to be that once you start using data migrations, you're never going to create the database from scratch again. What if you're working in a team and a new team member comes along with a new SQL Server install? Are you expected to copy over a database from another team member? What if you want to start using a new DBMS, like Postgres? I thought one of the cool things about EF was that it was DBMS-independent, but if you're no longer able to create the database from scratch, you can no longer do things in a DBMS-independent way.
For the reasons I outlined above, wouldn't adding custom indexes in a data migration but not in the data model be a bad idea? For that matter, wouldn't adding any DB structure changes in a migration but not in the data model be a bad idea?
Are EF code-first models intended to fully describe a database's structure?
No, they don't fully describe the database structure or schema.Still there are methods to make the database fully described using EF. They are as below:
You can use 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 ...");
}
You can even add Unique Constraints this way.
It is not a workaround, but will be enforced as the database will be generated.
OR
If you are badly in need of the attribute, then here it goes
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
public IndexAttribute(string name, bool unique = false)
{
this.Name = name;
this.IsUnique = unique;
}
public string Name { get; private set; }
public bool IsUnique { get; private set; }
}
After this , you will have an initializer which you will call in your OnModelCreating method as below:
public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext
{
private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});";
public void InitializeDatabase(T context)
{
const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
Dictionary<IndexAttribute, List<string>> indexes = new Dictionary<IndexAttribute, List<string>>();
string query = string.Empty;
foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(p => p.PropertyType.Name == typeof(DbSet<>).Name))
{
var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single();
TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false);
indexes.Clear();
string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name;
foreach (PropertyInfo property in entityType.GetProperties(PublicInstance))
{
IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false);
NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false);
if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0)
{
ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false);
foreach (IndexAttribute indexAttribute in indexAttributes)
{
if (!indexes.ContainsKey(indexAttribute))
{
indexes.Add(indexAttribute, new List<string>());
}
if (property.PropertyType.IsValueType || property.PropertyType == typeof(string))
{
string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name;
indexes[indexAttribute].Add(columnName);
}
else
{
indexes[indexAttribute].Add(property.PropertyType.Name + "_" + GetKeyName(property.PropertyType));
}
}
}
}
foreach (IndexAttribute indexAttribute in indexes.Keys)
{
query += CreateIndexQueryTemplate.Replace("{indexName}", indexAttribute.Name)
.Replace("{tableName}", tableName)
.Replace("{columnName}", string.Join(", ", indexes[indexAttribute].ToArray()))
.Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty);
}
}
if (context.Database.CreateIfNotExists())
{
context.Database.ExecuteSqlCommand(query);
}
}
private string GetKeyName(Type type)
{
PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.GetCustomAttribute(typeof(KeyAttribute), true) != null)
return propertyInfo.Name;
}
throw new Exception("No property was found with the attribute Key");
}
}
Then overload OnModelCreating in your DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new IndexInitializer<MyContext>());
base.OnModelCreating(modelBuilder);
}
Apply the index attribute to your Entity type, with this solution you can have multiple fields in the same index just use the same name and unique.
OR
You can do the migrations later on.
Note:
I have taken a lot of this code from here.
The question seems to be if there is value in having migrations added mid-stream, or if those will cause problems for future database initializations on different machines.
The initial migration that is created also contains the entire data model as it exists, so by adding migrations (enable-migrations in the Package Manager Console) you are, in effect, creating the built-in mechanism for your database to be properly created down the road for other developers.
If you're doing this, I do recommend modifying the database initialization strategy to run all your existing migrations, lest EF should start up and get the next dev's database out of sync.
Something like this would work:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourNamespace.YourDataContext, Migrations.Configuration>());
So, no, this won't inherently introduce problems for future work/developers. Remember that migrations are just turned into valid SQL that executes against the database...you can even use script mode to output the TSQL required to make the DB modifications based on anything in the migrations you have created.
Cheers.
Our database was designed in such a way where there are various schemas for production and various equivalent schemas for test. For example, many tables rest in MyProduction schema while the same tables live in MyTest schema.
What I want to do is determine which schema a table is using so I know which one to change it to. So, by default, everything will be under the production schemas. In the OnModelCreating event of the DbContext, if I need to point to test (determined by some true/false configuration), I need to determine the production schema being used, then point it to it's test equivalent.
I'm already aware of how to set the schema but can't find how to get it. Any ideas how I can determine the schema that a table is using?
Thank You.
Try below code after modifying according to you local settings:
var context = new YouDbContext("ConnectionName");
var adapter = (IObjectContextAdapter)context;
var objectContext = adapter.ObjectContext;
EntitySetBase schema = null;
if (objectContext.MetadataWorkspace != null)
{
schema = objectContext.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace).First()
.BaseEntitySets
.First(meta => meta.ElementType.Name == "ClassNameUnderYourDbContext");
}
//See the properties of schema in debug mode to understand details
Entity Framework schemas are System.ComponentModel.DataAnnotations.TableAttribute objects. Here are some methods you can use to get an entitity's schema name and table name. Cheers!
private string GetTableName(Type type)
{
var tableAttribute = type.GetCustomAttributes(false).OfType<System.ComponentModel.DataAnnotations.TableAttribute>().FirstOrDefault();
if (tableAttribute != null && !string.IsNullOrEmpty(tableAttribute.Name))
{
return tableAttribute.Name;
}
else
{
return string.Empty;
}
}
private string GetTableSchema(Type type)
{
var tableAttribute = type.GetCustomAttributes(false).OfType<System.ComponentModel.DataAnnotations.TableAttribute>().FirstOrDefault();
if (tableAttribute != null && !string.IsNullOrEmpty(tableAttribute.Schema))
{
return tableAttribute.Schema;
}
else
{
return string.Empty;
}
}
System.ComponentModel.DataAnnotations.Schema.TableAttribute