Is there any way to see if property of an entity is navigation property, from its metadata?
I can determine if property is entity collection by inspecting if it implements ICollection and from there i can conclude if it is navigation property.
But what about if property is not entity collection but only reference to another entity?
You can get the O-Space EDM entity type from MetdataWorkspace and it has NavigationProperties property. Here is an example:
var workspace = ((IObjectContextAdapter) ctx).ObjectContext.MetadataWorkspace;
var itemCollection = (ObjectItemCollection)(workspace.GetItemCollection(DataSpace.OSpace));
var entityType = itemCollection.OfType<EntityType>().Single(e => itemCollection.GetClrType(e) == typeof(MyEntity));
foreach(var navigationProperty in entityType.NavigationProperties)
{
Console.WriteLine(navigationProperty.Name);
}
You can use one more approach to solve the problem.
Obs: found variable is some DbContext entity instance;
foreach (var propertyInfo in found.GetType().GetProperties())
{
var reference = Context.Entry(found).Member(propertyInfo.Name) as DbReferenceEntry;
if (reference != null)
{
reference.Load();
}
}
Just as simple as :
if (context.Model.FindEntityType(propertyInfo.PropertyType) is not null)
Where:
context is an instance of you custom DbContext
propertyInfo is gained by typeof(entity class).GetPropertyInfo(property name)
Related
I have succesfully extended the EF6 designer to allow for some custom properties on my entities, associations and properties using this post:
Extending Entity Framework 6 - adding custom properties to entities in designer
Now I need to use these custom properties when generating code in T4 but I have no clue how to access that information. Can someone point me in the right direction ?
regards,
Jurjen.
I've figured it out.
looking at, for instance, the entity variable in "foreach (var entity in typeMapper.GetItemsToGenerate(itemCollection))", this is a GlobalItem (https://msdn.microsoft.com/en-us/library/system.data.metadata.edm.globalitem(v=vs.110).aspx) wich contains MetadataProperties.
Listing these properties using a simple foreach
foreach( var mp in entity.MetadataProperties)
{
this.WriteLine("{0} = '{1}'", mp.Name, mp.Value);
}
results in a list
Name = 'Role'
NamespaceName = 'Model1'
Abstract = 'False'
...
http://saop.si:RecordTracked = '<a:RecordTracked xmlns:a="http://saop.si">true</a:RecordTracked>'
http://saop.si:DisplayMember = '<a:DisplayMember xmlns:a="http://saop.si">true</a:DisplayMember>'
as you can see, the custom properties (RecordTracked, DisplayName) are listed as well.
I have created 2 functions in within public class CodeStringGenerator to retrieve any custom property. Call it like this :
CodeStringGenerator.GetCustomPropertyAsBoolean(entity, "RecordTracked");
private bool GetCustomPropertyAsBoolean(GlobalItem item, string propertyName)
{
var _value = GetCustomProperty(item, propertyName);
if (string.IsNullOrEmpty(_value ))
{ return false; }
return _value.Equals("true", StringComparison.CurrentCultureIgnoreCase);
}
private string GetCustomProperty(GlobalItem item, string propertyName)
{
var _found = item.MetadataProperties.FirstOrDefault(p => p.Name.StartsWith("http://") &&
p.Name.EndsWith(propertyName, StringComparison.CurrentCultureIgnoreCase ));
if (_found == null)
{ return string.Empty; }
var _value = _found.Value as System.Xml.Linq.XElement;
if (_value == null)
{ return string.Empty; }
return _value.Value;
}
I observed that we can write custom linq queries if we use
dbContext.set<MyEntity>()
But can not on
dbContext.set(SomeType).
I have a context class EGEntities and I have an Entity say "Employee".
How can I assign Employee type Entity to MyEntity? So that I can create a queryable instance of Employee?
Another Failure Idea
var instance = Activator.CreateInstance(thisType);
GetEntityTemplate(instance, thisType);
// Just to Test
public static List<Object> GetEntityTemplate<T>(T instance,Type targetType) where T :class
{
var Context = new EGEntities();
var set = Context.Set<T>();
if (set == null)
{
throw new Exception();
}
List<object> l = null;
return l;
}
But don't know why 'set' is only looking for 'object' and exception is "Model is not current context" though instance is correctly carrying the class instance in the parameter.
The DbContext.Set() method is having another (non-generic) overload that is expecting a System.Type:
var employeesSet = dbContext.Set(typeof(Employee));
And if you're already having a reference to an existing instance of Employee:
var employeeType = myEmployee.GetType();
var employeesSet = dbContext.Set(employeeType);
See MSDN
I am using the following code snippet to save a modified entity in my repository
object id1 = item.GetProperty("Id");
T originalEntity = dbSet.Find(id1); // but this doesnt update navigation properties
((DbContext)context).Entry(originalEntity).CurrentValues.SetValues(item);
navProps = GetNavigationProperties(originalEntity);
foreach (PropertyInfo navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
var newval = (LoggedEntity)navProp.GetValue(item);
object entity = null;
if (newval != null)
{
Type tp = navProp.PropertyType;
DbSet entities = ((DbContext)context).Set(tp);
entity = entities.Find(newval.Id);
}
navProp.SetValue(originalEntity, entity);
}
which calls
public List<PropertyInfo> GetNavigationProperties(T entity)
{
Type t = entity.GetType();
ObjectContext objectContex = ((IObjectContextAdapter)((DbContext)context)).ObjectContext;
EntityType elementType = objectContex.CreateObjectSet<T>().EntitySet.ElementType;
var properties = new List<PropertyInfo>();
Type entityType = entity.GetType();
foreach (NavigationProperty navigationProperty in elementType.NavigationProperties)
{
PropertyInfo prop = entityType.GetProperty(navigationProperty.Name);
properties.Add(prop);
}
return properties;
}
However I have a problem when I want to set the navigation property to null.
The changes simply don't save.
The answer is explained in this question
Which points out that the navigation properties need to be eager loaded.
How do I modify my GetNavigationProperties procedure to eager load?
I inserted the following line
originalEntity.GetProperty(navProp.Name);
before
navProp.SetValue(originalEntity, entity);
and now the code works if entity is null
Say I have a bunch of boolean properties on my entity class public bool isActive etc. Values which will be manipulated by setting check boxes in a web application. I will ONLY be posting back the one changed name/value pair and the primary key at a time, say { isActive : true , NewsPageID: 34 } and the default model binder will create a NewsPage object with only those two properties set. Now if I run the below code it will not only update the values for the properties that have been set on the NewsPage object created by the model binder but of course also attempt to null all the other non set values for the existent entity object because they are not set on NewsPage object created by the model binder.
Is it possible to somehow tell entity framework not to look at the properties that are set to null and attempt to persist those changes back to the retrieved entity object and hence database ? Perhaps there's some code I can write that will only utilize the non-null values and their property names on the NewsPage object created by model binder and only attempt to update those particular properties ?
[HttpPost]
public PartialViewResult SaveNews(NewsPage Np)
{
Np.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(Np);
_db.ObjectStateManager.ChangeObjectState(Np, System.Data.EntityState.Modified);
_db.SaveChanges();
_db.Dispose();
return PartialView("MonthNewsData");
}
I can of course do something like below, but I have a feeling it's not the optimal solution. Especially considering that I have like 6 boolean properties that I need to set.
[HttpPost]
public PartialViewResult SaveNews(int NewsPageID, bool isActive, bool isOnFrontPage)
{
if (isActive != null) { //Get entity and update this property }
if (isOnFontPage != null) { //Get entity and update this property }
}
API is not strongly typed but you can do it as follows. DbContext API has better support for this.
[HttpPost]
public PartialViewResult SaveNews(NewsPage Np)
{
Np.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(Np);
var entry = _db.ObjectStateManager.GetObjectStateEntry(Np);
var cv = entry.CurrentValues;
if (isActive)
{
cv.SetBoolean(cv.GetOrdinal("isActive"), true);
}
_db.SaveChanges();
_db.Dispose();
return PartialView("MonthNewsData");
}
You can go for two options
Register a custom model binder for that action. In the custom model binder you have to get the complete object from the database and only update the POSTed properties.
Use a view model. Instead of directly having the NewsPage model as the action parameter. You can create a custom view model that wraps the necessary properties. Inside the action you have to make a call to db to get the complete NewsPage instance and update only the corresponding properties from the view model.
Somewhat ugly, but did the trick in my case without having to create and register custom model binder or using multiple if statements.
[HttpPost]
public void SaveNews(string propname, bool propvalue, int PageID)
{
var prop = typeof(NewsPage).GetProperties().FirstOrDefault(x => x.Name.ToLower() == propname.ToLower());
var Np = _db.NewsPages.FirstOrDefault(x => x.PageID == PageID);
prop.SetValue(Np, propvalue, null);
Np.ModifyDate = DateTime.Now;
_db.SaveChanges();
_db.Dispose();
}
In EF code first, one specifies field properties and relationships using the fluent interface. This builds up a model. Is it possible to get a reference to this model, and reflect on it?
I want to be able to retrieve for a given field, if it is required, what its datatype is, what length, etc...
You need to access the MetadataWorkspace. The API is pretty cryptic. You may want to replace DataSpace.CSpace with DataSpace.SSpace to get the database metadata.
public class MyContext : DbContext
{
public void Test()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var mdw = objectContext.MetadataWorkspace;
var items = mdw.GetItems<EntityType>(DataSpace.CSpace);
foreach (var i in items)
{
foreach (var member in i.Members)
{
var prop = member as EdmProperty;
if (prop != null)
{
}
}
}
}