I have an Odata service which exposes an EF6 code first model. Some of these entities include DbGeography fields. I can GET these field, despite the DbGeography fields having quite an ugly serialized format, but I cannot figure out how to POST or PUT an entity to the service. The DbGeography type seems to struggle with deserialization.
Can anyone provide an example or link for how to do this?
I've had this problem too. Its a bug apparently.
Either two choices here - drop down to EF5 for the old System.Data.Entity.DbGeography (rather than System.Data.Entity.Spatial.DbGeography), or wait until they patch it.
Edit: Its been a long time, and so far the only hack I've come up with is this as a way to write to the properties and hide the ugly serialised format (however still cannot query against it.
private bool hasSetLongitude = false;
private bool hasSetLatitiude = false;
private double? longitude = null;
private double? latitiude = null;
/// <summary>
/// Get or set the coordinates.
/// </summary>
[Column("engi_coord"), Required]
public DbGeography Coordinates { get; set; }
/// <summary>
/// Get or set the lang coordinates.
/// </summary>
[Column("engi_lng"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public double? Longitude
{
get
{
return this.Coordinates != null ? this.Coordinates.Longitude : (double?)null;
}
set
{
this.longitude = value;
this.hasSetLongitude = true;
if (this.hasSetLongitude)
{
if (this.longitude.HasValue &&
this.latitiude.HasValue)
{
this.Coordinates = DbGeography.PointFromText(string.Format("POINT({0} {1})", this.longitude.Value, this.latitiude.Value), 4326);
}
else
{
this.Coordinates = null;
}
}
}
}
/// <summary>
/// Get or set the lat coordinates.
/// </summary>
[Column("engi_lat"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public double? Latitude
{
get
{
return this.Coordinates != null ? this.Coordinates.Latitude : (double?)null;
}
set
{
this.latitiude = value;
this.hasSetLatitiude = true;
if (this.hasSetLatitiude)
{
if (this.longitude.HasValue &&
this.latitiude.HasValue)
{
this.Coordinates = DbGeography.PointFromText(string.Format("POINT({0} {1})", this.longitude.Value, this.latitiude.Value), 4326);
}
else
{
this.Coordinates = null;
}
}
}
}
Migrate to add the computed columns:
this.Sql("ALTER TABLE dbo.engi_engineer ADD engi_lng AS engi_coord.Long");
this.Sql("ALTER TABLE dbo.engi_engineer ADD engi_lat AS engi_coord.Lat");
Now ignore the coordinates property from API:
this.EntityType.Ignore(p => p.Coordinates);
Well it has been over a year now and it doesn't look like this is getting fixed anytime soon. Especially with EF7 coming out very soon.
So, I would like to approach this another way.
What if the DbGeography type was not exposed as a property on the class and instead we expose a Latitude and Longitude property that are not mapped and then on their GET / SET we update the hidden DbGeography property?
I have tried this but have been unsuccessful with EF.
What you can do is inside the POST / PUT controller calls is use a stored procedure or SQL text to update the data.
If I find out how to get this to work with EF I will post here.
Related
I have a string property
public string XYZ
{
get => // do stuff
set => // do stuff which handles null
}
because I'm hoping it will get called....
But will it really?
(EF6.4)
It appears it will. If you implement the property with a backing field it's easy to test this by putting a breakpoint in the setter. EG
private string xyz;
public string XYZ
{
get
{
return xyz;
}
set
{
xyz = value;
}
}
And I think it would have to, as EF does not know whether your entities have a non-standard default value for a property. eg you could write
private string xyz = "none";
public string XYZ
{
get
{
return xyz;
}
set
{
xyz = value;
}
}
And so the hydration code would need to run the setter to get a correct result.
I am reading this http://msdn.microsoft.com/en-us/magazine/jj651572.aspx to learn mvvm light framework.
I download the source code Friend.cs.
My question is why some set method of different property are implemented differently.
For example, the setter for First name is, why I need 'ref' keyword for _firstName.
Set(FirstNamePropertyName, ref _firstName, value);
And the setter for DateOfBirthString is "
RaisePropertyChanged(() => DateOfBirth);
When will the linq expression will be evaluated?
namespace MyFriends.Model
{
[SimpleSerialize]
public class Friend : ObservableObject
{
/// <summary>
/// The <see cref="FirstName" /> property's name.
/// </summary>
public const string FirstNamePropertyName = "FirstName";
private string _firstName;
/// <summary>
/// Sets and gets the FirstName property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[SimpleSerialize(FieldName = "first_name")]
public string FirstName
{
get
{
return _firstName;
}
set
{
Set(FirstNamePropertyName, ref _firstName, value);
}
}
/// <summary>
/// The <see cref="LastName" /> property's name.
/// </summary>
public const string LastNamePropertyName = "LastName";
private string _lastName;
/// <summary>
/// Sets and gets the LastName property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[SimpleSerialize(FieldName = "last_name")]
public string LastName
{
get
{
return _lastName;
}
set
{
Set(LastNamePropertyName, ref _lastName, value);
}
}
/// <summary>
/// The <see cref="DateOfBirth" /> property's name.
/// </summary>
public const string DateOfBirthPropertyName = "DateOfBirth";
private string _dateOfBirthString;
/// <summary>
/// Sets and gets the DateOfBirth property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[SimpleSerialize(FieldName = "birthday")]
public string DateOfBirthString
{
get
{
return _dateOfBirthString;
}
set
{
_dateOfBirthString = value;
RaisePropertyChanged(() => DateOfBirth);
}
}
public DateTime DateOfBirth
{
get
{
if (string.IsNullOrEmpty(_dateOfBirthString))
{
return DateTime.MinValue;
}
return DateTime.ParseExact(DateOfBirthString, "d", CultureInfo.InvariantCulture);
}
set
{
_dateOfBirthString = value.ToString("d", CultureInfo.InvariantCulture);
}
}
private string _imageUrl;
[SimpleSerialize(FieldName = "picture")]
public string ImageUrl
{
get
{
return _imageUrl;
}
set
{
_imageUrl = value;
RaisePropertyChanged(() => ImageUri);
}
}
public Uri ImageUri
{
get
{
return new Uri(_imageUrl);
}
}
}
}
The difference between those two methods is that the Set method replaces the old value of the _firstName field and then raises the PropertyChanged event, while the RaisePropertyChanged only raises the PropertyChanged event.
You'll want to use the Set method in most cases, since it helps to shorten property declarations by wrapping all that typically needs to be done within a property's setter in just one method:
It updates the value of the passed field and overwrites it with the content of value, and then
raises the PropertyChanged event to notify Views about this update.
The reason the field needs to be passed by reference (thus using ref _firstName) is that not the field's content is needed within the Set method, but the field itself is actually updated.
The RaisePropertyChanged method is useful when an update of one property does also affect additional properties. In this case, these properties' contents need to be updated manually, then the RaisePropertyChanged method can be called to inform Views about which properties have actually changed.
One instance I found that Set(ref _prop, value)
doesn't work is when you need to resend an update or need to do a custom comparison. Set(ref _prop, value)
will do a compare on the _prop and value for you but it may not be what you want. For some properties i may want to add some more conditions beyond just a comparison in which case I'll set the _prop = value manually and then raise the change right after;
But in most cases I use the Set(ref _prop, value)
Here is an actual usage, where the IsRunning is the primary operation, but it also raises a change notification on the property IsComplete which is based on IsRunning. This way both properties send a notify without undo extra coding.
private bool _IsRunning;
public bool IsRunning
{
get => _IsRunning;
set
{
SetProperty(ref _IsRunning, value);
RaisePropertyChanged("IsComplete");
}
}
public bool IsComplete => !IsRunning;
I've got multiple classes (including TPT) in my project. Each POCO has a BaseClass, which has a GUID (called GlobalKey) as primary key.
First I used DataAnnotations to create correct foreign keys. But then I've got problems synchronizing the corresponding GUID with the object itself.
Now I want to have only one virtual navigation property so that the GUID field in the database is created by NamingConvention. But the field name always adds an underscore followed by the word GlobalKey (which is right). When I want to remove the underscore, I don't want to go thru all my POCOs in the fluent API to do this:
// Remove underscore from Navigation-Field
modelBuilder.Entity<Person>()
.HasOptional(x => x.Address)
.WithMany()
.Map(a => a.MapKey("AddressGlobalKey"));
Any ideas to do this for all POCOS by overwriting a convention?
Thanks in advance.
Andreas
I finally found an answer for this, by writing a custom convention. This convention works in EF 6.0 RC1 (code from last week), so I think it's likely to continue to work after EF 6.0 is released.
With this approach, the standard EF conventions identify the independent associations (IAs), and then create the EdmProperty for the foreign key field. Then this convention comes along and renames the foreign key fields.
/// <summary>
/// Provides a convention for fixing the independent association (IA) foreign key column names.
/// </summary>
public class ForeignKeyNamingConvention : IStoreModelConvention<AssociationType>
{
public void Apply(AssociationType association, DbModel model)
{
// Identify a ForeignKey properties (including IAs)
if (association.IsForeignKey)
{
// rename FK columns
var constraint = association.Constraint;
if (DoPropertiesHaveDefaultNames(constraint.FromProperties, constraint.ToRole.Name, constraint.ToProperties))
{
NormalizeForeignKeyProperties(constraint.FromProperties);
}
if (DoPropertiesHaveDefaultNames(constraint.ToProperties, constraint.FromRole.Name, constraint.FromProperties))
{
NormalizeForeignKeyProperties(constraint.ToProperties);
}
}
}
private bool DoPropertiesHaveDefaultNames(ReadOnlyMetadataCollection<EdmProperty> properties, string roleName, ReadOnlyMetadataCollection<EdmProperty> otherEndProperties)
{
if (properties.Count != otherEndProperties.Count)
{
return false;
}
for (int i = 0; i < properties.Count; ++i)
{
if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name))
{
return false;
}
}
return true;
}
private void NormalizeForeignKeyProperties(ReadOnlyMetadataCollection<EdmProperty> properties)
{
for (int i = 0; i < properties.Count; ++i)
{
string defaultPropertyName = properties[i].Name;
int ichUnderscore = defaultPropertyName.IndexOf('_');
if (ichUnderscore <= 0)
{
continue;
}
string navigationPropertyName = defaultPropertyName.Substring(0, ichUnderscore);
string targetKey = defaultPropertyName.Substring(ichUnderscore + 1);
string newPropertyName;
if (targetKey.StartsWith(navigationPropertyName))
{
newPropertyName = targetKey;
}
else
{
newPropertyName = navigationPropertyName + targetKey;
}
properties[i].Name = newPropertyName;
}
}
}
Note that the Convention is added to your DbContext in your DbContext.OnModelCreating override, using:
modelBuilder.Conventions.Add(new ForeignKeyNamingConvention());
You can do one of two things:
Follow EF conventions in naming of foreign keys, i.e. if you have virtual Address, define your key property as AddressId
Tell EF explicitly what to use. One way to do this is with Fluent API, as you are currently doing. You can also use data annotations, though:
[ForeignKey("Address")]
public int? AddressGlobalKey { get; set; }
public virtual Address Address { get; set; }
That's your only choices.
I know this is a bit old, but here is a sample how I specify mapping columns through my fluent config (OnModelCreating):
modelBuilder.Entity<Application>()
.HasOptional(c => c.Account)
.WithMany()
.Map(c => c.MapKey("AccountId"));
Hope this helps,
I have also seen the same problem when the type of the field is off. Double check the type of the field Ex:
public string StateId {get;set;}
pointing to a domain object with int as the State.Id type. Make sure that your types are same.
I found that key column customizations were not being caught by the ForeignKeyNamingConvention. Made this change to catch them.
private bool DoPropertiesHaveDefaultNames(ReadOnlyMetadataCollection<EdmProperty> properties, string roleName, ReadOnlyMetadataCollection<EdmProperty> otherEndProperties)
{
if (properties.Count == otherEndProperties.Count)
{
for (int i = 0; i < properties.Count; ++i)
{
if (properties[i].Name.EndsWith("_" + otherEndProperties[i].Name))
{
return true;
}
else
{
var preferredNameProperty =
otherEndProperties[i]
.MetadataProperties
.SingleOrDefault(x => x.Name.Equals("PreferredName"));
if (null != preferredNameProperty)
{
if (properties[i].Name.EndsWith("_" + preferredNameProperty.Value))
{
return true;
}
}
}
}
}
return false;
}
I had issues when combining it with an id naming convention of EntityNameId.
When using the following convention to ensure the Customer table has CustomerId rather than simply Id.
modelBuilder.Properties()
.Where(p => p.Name == "Id")
.Configure(p => p.IsKey().HasColumnName(p.ClrPropertyInfo.ReflectedType == null ? "Id" : p.ClrPropertyInfo.ReflectedType.Name +"Id"));
The foreign key naming convention needs to be changed to the following.
/// <summary>
/// Provides a convention for fixing the independent association (IA) foreign key column names.
/// </summary>
public class ForeignKeyNamingConvention : IStoreModelConvention<AssociationType>
{
public void Apply(AssociationType association, DbModel model)
{
// Identify ForeignKey properties (including IAs)
if (!association.IsForeignKey) return;
// rename FK columns
var constraint = association.Constraint;
if (DoPropertiesHaveDefaultNames(constraint.FromProperties, constraint.ToProperties))
{
NormalizeForeignKeyProperties(constraint.FromProperties);
}
if (DoPropertiesHaveDefaultNames(constraint.ToProperties, constraint.FromProperties))
{
NormalizeForeignKeyProperties(constraint.ToProperties);
}
}
private static bool DoPropertiesHaveDefaultNames(IReadOnlyList<EdmProperty> properties, IReadOnlyList<EdmProperty> otherEndProperties)
{
if (properties.Count != otherEndProperties.Count)
{
return false;
}
for (var i = 0; i < properties.Count; ++i)
{
if (properties[i].Name.Replace("_", "") != otherEndProperties[i].Name)
{
return false;
}
}
return true;
}
private void NormalizeForeignKeyProperties(ReadOnlyMetadataCollection<EdmProperty> properties)
{
for (var i = 0; i < properties.Count; ++i)
{
var underscoreIndex = properties[i].Name.IndexOf('_');
if (underscoreIndex > 0)
{
properties[i].Name = properties[i].Name.Remove(underscoreIndex, 1);
}
}
}
}
Most of these answers have to do with Independent Assocations (where the "MyOtherTable" navigation property is defined, but not the "int MyOtherTableId") instead of Foreign Key Assocations (where both are defined).
That is fine since the question is about IA (it uses MapKey), but I came across this question when searching for a solution to the same problem with FKAs. Since other people may come here for the same reason, I thought I would share my solution that uses a ForeignKeyDiscoveryConvention.
https://stackoverflow.com/a/43809004/799936
I have a scenario where I am using Entity Framework in a WCF service, and changes happen on a non-tracked instance of a type that is mapped back to the database via code-first (non-trivial updates and deletes throughout the instance's object tree). When I try to attach the non-tracked instance into the context, EF is only recognizing changes to the simple value types on the root object.
Does anyone know of an elegant solution for this scenario? I am looking for a way to do this by using a generic repository, and avoiding having to run through the instance's entire object tree managing the "attach/detach" state of every object. I have considered possibly using ValueInjecter or AutoMapper to run the changes on a fully hydrated and tracked instance of the "old" state in order for the context to pickup the changes. Also, how would Nhibernate handle this situation?
Thanks in advance for your input!
UPDATE (7/31/2012): I have updated the code to handle genericly-typed keys, and some typing issues with EF Proxies. Also added some helper extensions when dealing with IEntity types. This implementation isn't perfect, but it is very functional.
UPDATE (3/13/2012): I have added a feature request for cleaner merging in EF. The request is located here: http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/2679160-better-merging-change-tracking
UPDATE (3/12/2012): I have posted my solution below. It uses FubuCore, ValueInjecter, and requires entities to be marked with one of two interfaces, either IEntity, or IRecursiveEntity for recursive classes. The solution will handle recursive, self-linked entities.
Also, I am referencing a generic repository (Repository) that allows me to get a reference to the IDbSet that EF exposes. This could be substituded with any other generic or specific repository. Lastly, the IEntity interface uses an int? id, however you could define that however you want (Guid/Guid?). The solution itself isn't quite as elegant as I would like, however it allows for much more elegant data access code when behind a physical WCF service boundary.
public class DomainMergeInjection : ConventionInjection
{
private readonly Repository _repository;
private readonly Dictionary<string, object> _potentialParentObjectDump;
private readonly Cache<Type, Type> _entityTypesAndKeysCache;
public DomainMergeInjection(Repository repository)
{
_repository = repository;
_potentialParentObjectDump = new Dictionary<string, object>();
_entityTypesAndKeysCache = new Cache<Type, Type>();
}
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if(c.SourceProp.Value == null)
return null;
//for value types and string just return the value as is
if(c.SourceProp.Type.IsSimple())
return c.SourceProp.Value;
//TODO: Expand on this to handle IList/IEnumerable (i.e. the non-generic collections and arrays).
//handle arrays
if(c.SourceProp.Type.IsArray)
{
var sourceArray = c.SourceProp.Value as Array;
// ReSharper disable PossibleNullReferenceException
var clonedArray = sourceArray.Clone() as Array;
// ReSharper restore PossibleNullReferenceException
for(int index = 0; index < sourceArray.Length; index++)
{
var sourceValueAtIndex = sourceArray.GetValue(index);
//Skip null and simple values that would have already been moved in the clone.
if(sourceValueAtIndex == null || sourceValueAtIndex.GetType().IsSimple())
continue;
// ReSharper disable PossibleNullReferenceException
clonedArray.SetValue(RetrieveComplexSourceValue(sourceValueAtIndex), index);
// ReSharper restore PossibleNullReferenceException
}
return clonedArray;
}
//handle IEnumerable<> also ICollection<> IList<> List<>
if(c.SourceProp.Type.IsGenericEnumerable())
{
var t = c.SourceProp.Type.GetGenericArguments()[0];
if(t.IsSimple())
return c.SourceProp.Value;
var tlist = typeof(List<>).MakeGenericType(t);
dynamic list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach(var sourceItem in (IEnumerable)c.SourceProp.Value)
{
addMethod.Invoke(list, new[] { RetrieveComplexSourceValue(sourceItem) });
}
return list;
}
//Get a source value that is in the right state and is tracked if needed.
var itemStateToInject = RetrieveComplexSourceValue(c.SourceProp.Value);
return itemStateToInject;
}
private object RetrieveComplexSourceValue(object source)
{
//If the source is a non-tracked type, or the source is a new value, then return its value.
if(!source.ImplementsIEntity(_entityTypesAndKeysCache) || source.IsEntityIdNull(_entityTypesAndKeysCache))
return source;
object sourceItemFromContext;
//Handle recursive entities, this could probably be cleaned up.
if(source.ImplementsIRecursiveEntity())
{
var itemKey = source.GetEntityIdString(_entityTypesAndKeysCache) + " " + ObjectContext.GetObjectType(source.GetType());
//If we have a context item for this key already, just return it. This solves a recursion problem with self-linking items.
if(_potentialParentObjectDump.ContainsKey(itemKey))
return _potentialParentObjectDump[itemKey];
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Add the class into the object dump in order to avoid any infinite recursion issues with self-linked objects
_potentialParentObjectDump.Add(itemKey, sourceItemFromContext);
}
else
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Recursively use this injection class instance to inject the source state on to the context source state.
var itemStateToInject = sourceItemFromContext.InjectFrom(this, source);
return itemStateToInject;
}
private object GetSourceItemFromContext(object source)
{
if(source == null)
return null;
//Using dynamic here to "AutoCast" to an IEntity<>. We should have one, but it's important to note just in case.
dynamic sourceEntityValue = source;
var sourceEntityType = ObjectContext.GetObjectType(source.GetType());
var sourceKeyType = sourceEntityType.GetEntityKeyType();
var method = typeof(DomainMergeInjection).GetMethod("GetFromContext", BindingFlags.Instance | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(sourceEntityType, sourceKeyType);
var sourceItemFromContext = generic.Invoke(this, new object[] { new object[] { sourceEntityValue.Id } });
return sourceItemFromContext;
}
// ReSharper disable UnusedMember.Local
private TItem GetFromContext<TItem, TKey>(object[] keys) where TItem : class, IEntity<TKey>
// ReSharper restore UnusedMember.Local
{
var foundItem = _repository.GetDbSet<TItem>().Find(keys);
return foundItem;
}
}
public static class EntityTypeExtensions
{
/// <summary>
/// Determines if an object instance implements IEntity.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache">A cache to hold types that do implement IEntity. If the cache does not have the Type and the Type does implement IEntity, it will add the type to the cache along with the </param>
/// <returns></returns>
public static bool ImplementsIEntity(this object entity, Cache<Type, Type> entityCache = null)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityCache != null && entityCache.Has(entityType))
return true;
var implementationOfIEntity = entityType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IEntity<>));
if(implementationOfIEntity == null)
return false;
if(entityCache != null)
{
var keyType = implementationOfIEntity.GetGenericArguments()[0];
entityCache.Fill(entityType, keyType);
}
return true;
}
/// <summary>
/// Determines if an object instances implements IRecurisveEntity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static bool ImplementsIRecursiveEntity(this object entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
var implementsIRecursiveEntity = entityType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRecursiveEntity<>));
return implementsIRecursiveEntity;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static bool IsEntityIdNull(this object entity, Cache<Type, Type> entityCache = null)
{
bool isEntityIdNull = ExecuteEntityIdMethod<bool>("IsEntityIdNull", entity, entityCache);
return isEntityIdNull;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static string GetEntityIdString(this object entity, Cache<Type, Type> entityCache = null)
{
string entityIdString = ExecuteEntityIdMethod<string>("GetEntityIdString", entity, entityCache);
return entityIdString;
}
private static T ExecuteEntityIdMethod<T>(string methodName, object entityInstance, Cache<Type, Type> entityCache = null)
{
if(!entityInstance.ImplementsIEntity(entityCache))
throw new ArgumentException(string.Format("Parameter entity of type {0} does not implement IEntity<>, and so ist not executable for {1}!", entityInstance.GetType(), methodName));
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entityInstance.GetType());
var keyType = entityCache != null ? entityCache[entityType] : entityType.GetEntityKeyType();
var method = typeof(EntityTypeExtensions).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(keyType);
T returnValue = (T)generic.Invoke(null, new[] { entityInstance });
return returnValue;
}
private static string GetEntityIdString<TKey>(IEntity<TKey> entity)
{
var entityIdString = entity.Id.ToString();
return entityIdString;
}
private static bool IsEntityIdNull<TKey>(IEntity<TKey> entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityType.IsPrimitive)
return false;
//NOTE: We know that this entity's type is NOT primitive, therefore we can cleanly test for null, and return properly.
// ReSharper disable CompareNonConstrainedGenericWithNull
var entityIdIsNull = entity.Id == null;
// ReSharper restore CompareNonConstrainedGenericWithNull
return entityIdIsNull;
}
public static Type GetEntityKeyType(this Type typeImplementingIEntity)
{
var implementationOfIEntity = typeImplementingIEntity.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>));
if(implementationOfIEntity == null)
throw new ArgumentException(string.Format("Type {0} does not implement IEntity<>", typeImplementingIEntity));
var keyType = implementationOfIEntity.GetGenericArguments()[0];
return keyType;
}
}
public interface IEntity<TKey>
{
TKey Id { get; set; }
}
public interface IRecursiveEntity<TKey> : IEntity<TKey>
{
IRecursiveEntity<TKey> Parent { get; }
IEnumerable<IRecursiveEntity<TKey>> Children { get; }
}
you could use the detached object only as a DTO,
and after refill the object from context with values from the DTO
with ValueInjecter this would be:
//manually
conObj.InjectFrom(dto);
conObj.RefTypeProp.InjectFrom(dto.RefTypeProp);
...
//or by writing a custom injection:
conObj.InjectFrom<ApplyChangesInjection>(dto);
here's the Injection that will do that automatically, (I did it by modifying a bit the DeepClone Injection from VI's home page)
the trick here is that the Injection uses itself in the SetValue method
public class ApplyChangesInjection : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if (c.SourceProp.Value == null) return null;
//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
return c.SourceProp.Value;
//handle arrays - not impl
//handle IEnumerable<> also ICollection<> IList<> List<> - not impl
//for simple object types apply the inject using the corresponding source
return c.TargetProp.Value
.InjectFrom<ApplyChangesInjection>(c.SourceProp.Value);
}
}
//Note: I'm not handling collections in this injection, I just wanted you to understand the principle,
you can look at the original http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home
I'm trying to migrate to ASP.Net MVC 2 and meet some issues.
Here is one :
I needed to bind directly a Dictionary as result of a view post.
In ASP.Net MVC 1 it worked perfectly using a custom IModelBinder :
/// <summary>
/// Bind Dictionary<int, int>
///
/// convention : <elm name="modelName_key" value="value"></elm>
/// </summary>
public class DictionaryModelBinder : IModelBinder
{
#region IModelBinder Members
/// <summary>
/// Mandatory
/// </summary>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
IDictionary<int, int> retour = new Dictionary<int, int>();
// get the values
var values = bindingContext.ValueProvider;
// get the model name
string modelname = bindingContext.ModelName + '_';
int skip = modelname.Length;
// loop on the keys
foreach(string keyStr in values.Keys)
{
// if an element has been identified
if(keyStr.StartsWith(modelname))
{
// get that key
int key;
if(Int32.TryParse(keyStr.Substring(skip), out key))
{
int value;
if(Int32.TryParse(values[keyStr].AttemptedValue, out value))
retour.Add(key, value);
}
}
}
return retour;
}
#endregion
}
It worked in pair with some smart HtmlBuilder that displayed dictionary of data.
The problem I meet now is that ValueProvider is not a Dictionary<> anymore, it's a IValueProvider that only allow to get values whose name is known
public interface IValueProvider
{
bool ContainsPrefix(string prefix);
ValueProviderResult GetValue(string key);
}
This is really not cool as I cannot perform my smart parsing...
Question :
Is there another way to get all keys ?
Do you know another way to bind a collection of HTML elements to a Dictionary
Thanks for your suggestions
O.
Though this question has been marked 'answered' I think the following may be helpful.
I had the same problem and had a look at the source code of the System.Web.Mvc.DefaultValueProvider. It gets its values from the RouteData, the query string or from a request form submission (in that exact order). To collect all the keys (which is what you ask for in your first question) I wrote the following helper method.
private static IEnumerable<string> GetKeys(ControllerContext context)
{
List<string> keys = new List<string>();
HttpRequestBase request = context.HttpContext.Request;
keys.AddRange(((IDictionary<string,
object>)context.RouteData.Values).Keys.Cast<string>());
keys.AddRange(request.QueryString.Keys.Cast<string>());
keys.AddRange(request.Form.Keys.Cast<string>());
return keys;
}
You can use this method to enumerate over the keys:
foreach (string key in GetKeys(controllerContext))
{
// Do something with the key value.
}
I don't think you'll be able to do it this way anymore in MVC 2.
Alternatively, you could extend DefaultModelBinder and override one of its virtual methods like GetModelProperties and then change the ModelName inside the ModelBindingContext. Another option would be to implement a custom MetadataProvider for your Dictionary type, you can change the model name there as well.