Is there a way to materialize an instance of your own class that derives from the entity type specified in your model? Let's say I have a "ClassName" property in my model for a given entity and whenever EF materializes an instance of the entity, I want to create an instance of the specified ClassName instead (which is a sub-class of the entity of course).
I know there is the ObjectMaterialized event on the ObjectContext but at this point the entity is already created.
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
objectContext.ObjectMaterialized += OnObjectMaterialized;
I need a hook right before this to customize the object creation. You might ask why on earth would I need to do this. I have a large object model that changes constantly so I can't define all my classes in the EDMX. The properties that I store in the database can be abstracted to a couple classes at the top of the object model hierarchy. I intend to create entities for those using table-per-type inheritance. But if I could retrieve specific object instances directly when using EF that would be awesome.
Thanks!
I wanted to create custom proxys and debugged deep into the entity framework. I found the translator class, which is responsible to create the entities by reflection. The namespace of the class is System.Data.Entity.Core.Common.Internal.Materialization.
The method creating the entites is the private method Emit_ConstructEntity. The entity type is a parameter and is created by the private method LookupObjectMapping based on an EdmType object (I was using model first).
If you want to change the class, which are created, you have to create a custom MetaDataWorkspace. The workspace is responsible to select the EdmType. Maybe try to debug into the methods yourself and have a look how the MetaDataWorkspace is used to identify the CLR type.
Related
I've the following project class libraries structure on my solution:
Application.Domain.Models : Entities like User, Customer.
Application.DataAcess : IUserRepository, ICustomerRepository
Application.Business : IUserService, ..
For one operation i need only the CustomerName and CustomerAddress then i will use entity framework projection to return only this properties.
My question is, should i create a entity for store only this properties and return it from this operation or should i return a Customer entity with only these two properties and all others with no value?
If should create a new entity, what layer it should be put in? Domain.Models, Domain.AnotherFolder or Business?
Technically you should not be constructing invalid objects. So just fetch your entire entity and use what you need. This will also mean you can reuse some existing code. You can make another entity but this should compliment your domain model. It will go in domain. If you are simply retrieving the data to use outside your domain you can consider having a lightweight readonly query layer that just passes data to whoever wants to read it.
When using EF Code First to generate a database, you declare the DB sets within the DbContext class for example..
public DbSet< ProductOption > ProductOptions { get; set; }
Now from the reading I have been doing, if you have a related entity in the type ProductOptions you do not need to declare it as EF will look at the dependents and use them to create the required DB tables.
My question is this: Is this recommend practice? And if so how do you then access the related type in code as it does not exist as a data set.
Also this auto-discover feature of EF, does it do it in both directions, i.e if you declare a db set that does not have any related entities inside it, but it is an entity that is in some other entity, will EF find it?
Any clarification is welcome. Thanks
EDIT
Example of what I am saying in terms of not being able to access the Types that are auto discovered is when your seeding data.
You actually do have access to the types without declaring DbSets inside your context class. Inside your context initialization class under the Seed method you can access any of your entities using the yourContext.Set().Add(theObject). This way you do not need to have DBSet properties inside your context class. I am not sure what version of EF you are using or if your are using an initialization class for seeding the data. Regardless, you can still use the .Set().Add, to seed the data.
With regards to the question will EF find the non related entities. Like you said, as long as an object is a property of an entity EF should create a table for it. As long as in code first you declare a mapping for any entity, it should create it and any child entities that may or may not have mappings for themselves. I'd have to see how you are declaring your context and mappings to be sure.
I'm trying to write an add-on to Entity Framework Code First and I need a way to get the configuration of the model columns at run time. For example, this is the code setup on OnModelCreating by the DbModelBuilder:
builder.Entity<NwdEmployee>()
.Property(n => n.ReportsToID).HasColumnName("ReportsTo");
Once this is done, EntityFramework knows that my property's name is different to the column name in the table, but how can I find that the string "ReportsTo" relates to ReportsToID myself at runtime? Ideally, I'm trying to write a method such as a following:
public string GetMappedColumnName<TFrom>(DbContext context,
Func<TFrom, object> selector);
Which would be used like:
string mappedColumnName = GetMappedColumnName<NwdEmployee>(context,
x => x.ReportsToID);
I just don't know where to find the mapped column names within the DbContext. Are they even accessible?
Theoretically yes. Practically I'm not sure because with simple test I wasn't able to get those information at runtime - I see them in debugger but I cannot get them because the type I need to use is internal in entity framework.
The theory. All mapping information are available at runtime but not through reflection. They are stored in the instance on MetadataWorkspace class which was definitely not designed for direct usage because every interaction with this class demands some time spend in debugger before you find how to get data you need. This data are not accessible through DbContext API. You must convert DbContext back to ObjectContext and access the MetadataWorkspace.
ObjectContext objContext = ((IObjectContextAdapter)dbContext).ObjectContext;
GlobalItem storageMapping = objContext.MetadataWorkspace.GetItem<GlobalItem>("NameOfYourContextClass", DataSpace.CSSpace);
Now storageMapping is instance of System.Data.Mapping.StorageEntityContainerMapping class which is internal. As I understand it this class should be runtime representation of MSL = mapping between storage and conceptual model.
If you use debugger you can explore the instance and you will find information about mapping between properties and columns (its quite deep nested) so you can also use reflection to get them but it is reflection on non public interface of classes you don't own so any .NET framework patch / fix / update can break your application.
Scenario: Trying to extract and rearange information from one database to an other.
DB A has some data I want to get. I want to store it on DB B in a slightly different structure.
DB A I get using an EDMX database generated model so it uses a derivative of ObjectContext. DB B I would like to be Code generated. So I use the code/model first approach by installing EntityFramework 4.1 via Package manager. So DB B uses a DbContext derivative
When I try to store information from DB A to DB B it's says:
Test method RoutIT.Irma.Import.Service.Test.ImportIrma2ProductTests.ImportProducts threw exception: System.ArgumentException: Could not find the conceptual model type for 'Some entity in DB A's EDMX model'
It actually does it when adding a DB B entity to the DB B's Derived DbContext's DbSet property. So the code is like
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
foreach (FirstPVC pvc in pvcs)
{
this._irmaImport.FirstPVCs.Add(pvc); <--
this._irmaImport.SaveChanges();
}
scope.Complete();
}
}
It happens at the point in the code marked by the arrow above ("<--")
FirstPVC is a DB B property yet at the point of the arrow it complanes about not having a conceptual model for an entity belonging to DB B's context.
This is strange since I try to store an DB B entity to the DB B context. Why should it care about entity's of DB A.
All contexts are contained in the same project. But DB B's Derived DbContext only has knowledge about it's own DbSet<> properties, suddenly when trying to add something to the DbSet<> property it give me the error in bold above.
Anybody know why this happens? Why should DbContext care about entity's of another context specifically one of a ObjectContext derived class.
Perhapse it's usefull to note the entity it's complaining about looks a bit like this
[EdmEntityTypeAttribute(NamespaceName="Irma2Model", Name="AccessProvider")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class AccessProvider : EntityObject
{
/*****...... ******/
}
Found an answer, its not what you want to hear though:
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/d2a07542-cb33-48ba-86ed-4fdc70fb3f1a
"If you are using the default code generation for the EDMX file then the generated classes contain a series of attributes to help EF find which class to use for each entity type. EF currently has a restriction that POCO classes can't be loaded from an assembly that contains classes with the EF attributes. (Short answer is no, your classes need to be in separate projects).
This is a somewhat artificial restriction and something that we realize is painful and will try and remove in the future."
So the workaround would be to split the classes into two different assemblies.
Following on from an SO dicsussion here, I have implemented partial classes so as to create default datetime values for my Created and Modified database fields in a Constructor.
Now the problem is that my database has 100+ tables, and 75+ of them have the same basic structure which includes a Created and a Modified column definition.
So.. Instead of creating 75+ partial classes which I need to maintain, is there any way I can create a base class which every EF type inherits from, which inherits the default constructor to populate the DateTime values for Created and Modified?
EDIT: Worthy of note is that I am using EF 4.0
You can certainly specify your own base class with both EF4 and EF1, though it's a lot easier with EF4. Right click on the design surface and you should see an option to add a Code Generation Item. Select the ADO.Net entity object generator. This will a T4 file to your project (.tt extension) that specifies the template to use to generate your entity classes from the model. To specify a different base class, look inside it for a line like
Private Function BaseTypeName(ByVal entity As EntityType, ByVal code As CodeGenerationTools) As String
Return If(entity.BaseType Is Nothing, "EntityObject", MultiSchemaEscape(DirectCast(entity.BaseType, StructuralType), code))
End Function
Replace EntityObject with your base class. Note that if you are using this template then your base class must inherit from System.Data.Objects.DataClasses.EntityObject - you could use a POCO template instead, but this will probably be enough for you.
You can certainly tell EF to use a base class for your entities (it's right in the designer as a property for the entity)...but if you want to make sure of the default value for this field, perhaps you could hook into the two events on your ObjectContext SavingChanges and ObjectMaterialized.
http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.savingchanges.aspx
http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.objectmaterialized.aspx
You could use these opportunities to inject the default value(s) that you want to use. So in your SavingChanges handler, for example, you could check the ObjectStateManager on the context to see if the state of the relevant entity is EntityState.Added, then set the Created and Modified dates as desired.
Alternatively, as suggested, is there a reason the default value for the column in SQL Server can't just be GetDate()? (Assuming you're using SQL Server)....