This might be a simple question, however I'm new to Code First and Migrations so bear with me. I'll keep sample code to a minimum to show the problem:
I have a BaseAuditableEntity which includes this (among other things, but let's simplify):
public abstract class BaseAuditableEntity : BaseEntity, IAuditableEntity
{
public DateTime CreatedOn { get; set; }
public DateTime LastModified { get; set; }
}
Now a (for example) User POCO inherits from it:
public class User : BaseAuditableEntity
{
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public bool Active { get; set; }
public DateTime? LastLogin { get; set; }
}
I have this on my context's SaveChanges method, to fill in the CreatedOn and LastModified dates (simplified):
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditableEntity>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(p => p.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
entry.Entity.CreatedOn = now;
entry.Entity.LastModified = now;
}
}
return base.SaveChanges();
}
And now I have a migration in place that seeds some users, like this:
protected override void Seed(MyContext context)
{
context.Users.AddOrUpdate(
p => p.UserName,
new User { Active = true,
FullName = "My user name",
UserName = "ThisUser",
PasswordHash = "",
Email = "my#email",
LastLogin = null,
}
// etc.
);
}
Now I have a problem on seeding with AddOrUpdate after the migration. When the entity is new (it's being added), CreatedOn gets filled correctly and everything works as expected. However when the entity is modified (it already exists on the database and UserName matches), it tries to update it with the new entity I'm creating... this fails because CreatedOn has an invalid DateTime (in this case, DateTime.MinValue).
Is there any way to use the AddOrUpdate method so that it actually retrieves the matching entity from the database and just update the non-default fields? Or maybe some way to tell it which fields NOT to update? For this specific case, I'd like the CreatedOn field to be unchanged, but a generic solution would be appreciated.
Maybe I should do my own AddOrUpdate method which includes a predicate with the fields I want to change, instead of passing it a completely new entity?
This is EF 6.1
Update
I know I can easily solve this for the CreatedOn date, this is what I'm currently doing for this specific case:
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedOn = now;
}
else
{
if (entry.Property(p => p.CreatedOn).CurrentValue == DateTime.MinValue)
{
var original = entry.Property(p => p.CreatedOn).OriginalValue;
entry.Property(p => p.CreatedOn).CurrentValue = original != SqlDateTime.MinValue ? original : now;
entry.Property(p => p.CreatedOn).IsModified = true;
}
}
entry.Entity.LastModified = now;
}
I am looking for a more generic solution though
The implementation of AddOrUpdate uses CurrentValues.SetValues so that all scalar properties will be modified.
I have extended the functionality to accept properties to be modified when it's an update, otherwise it's a creation, just use DbSet<T>::Add.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class SeedExtension
{
public static void Upsert<T>(this DbContext db, Expression<Func<T, object>> identifierExpression, Expression<Func<T, object>> updatingExpression, params T[] entities)
where T : class
{
if (updatingExpression == null)
{
db.Set<T>().AddOrUpdate(identifierExpression, entities);
return;
}
var identifyingProperties = GetProperties<T>(identifierExpression).ToList();
Debug.Assert(identifyingProperties.Count != 0);
var updatingProperties = GetProperties<T>(updatingExpression).Where(pi => IsModifiedable(pi.PropertyType)).ToList();
Debug.Assert(updatingProperties.Count != 0);
var parameter = Expression.Parameter(typeof(T));
foreach (var entity in entities)
{
var matches = identifyingProperties.Select(pi => Expression.Equal(Expression.Property(parameter, pi.Name), Expression.Constant(pi.GetValue(entity, null))));
var matchExpression = matches.Aggregate<BinaryExpression, Expression>(null, (agg, v) => (agg == null) ? v : Expression.AndAlso(agg, v));
var predicate = Expression.Lambda<Func<T, bool>>(matchExpression, new[] { parameter });
var existing = db.Set<T>().SingleOrDefault(predicate);
if (existing == null)
{
// New.
db.Set<T>().Add(entity);
continue;
}
// Update.
foreach (var prop in updatingProperties)
{
var oldValue = prop.GetValue(existing, null);
var newValue = prop.GetValue(entity, null);
if (Equals(oldValue, newValue)) continue;
db.Entry(existing).Property(prop.Name).IsModified = true;
prop.SetValue(existing, newValue);
}
}
}
private static bool IsModifiedable(Type type)
{
return type.IsPrimitive || type.IsValueType || type == typeof(string);
}
private static IEnumerable<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> exp) where T : class
{
Debug.Assert(exp != null);
Debug.Assert(exp.Body != null);
Debug.Assert(exp.Parameters.Count == 1);
var type = typeof(T);
var properties = new List<PropertyInfo>();
if (exp.Body.NodeType == ExpressionType.MemberAccess)
{
var memExp = exp.Body as MemberExpression;
if (memExp != null && memExp.Member != null)
properties.Add(type.GetProperty(memExp.Member.Name));
}
else if (exp.Body.NodeType == ExpressionType.Convert)
{
var unaryExp = exp.Body as UnaryExpression;
if (unaryExp != null)
{
var propExp = unaryExp.Operand as MemberExpression;
if (propExp != null && propExp.Member != null)
properties.Add(type.GetProperty(propExp.Member.Name));
}
}
else if (exp.Body.NodeType == ExpressionType.New)
{
var newExp = exp.Body as NewExpression;
if (newExp != null)
properties.AddRange(newExp.Members.Select(x => type.GetProperty(x.Name)));
}
return properties.OfType<PropertyInfo>();
}
}
Usage.
context.Upsert(
p => p.UserName,
p => new { p.Active, p.FullName, p.Email },
new User
{
Active = true,
FullName = "My user name",
UserName = "ThisUser",
Email = "my#email",
}
);
I ran into an issue with Expression.Equals in #Yuliam's answer where a type was nullable and had to add the following.
var matches = identifyingProperties.Select(pi =>
Expression.Equal(Expression.Property(parameter, pi.Name),
Expression.Convert(Expression.Constant(pi.GetValue(entity, null)),
Expression.Property(parameter, pi.Name).Type)));
I also updated this to fetch all records first so "SingleOrDefault" doesn't execute a sql query in each for loop iteration.
I also set AddRange which gets a little better performance.
Here is a gist of my solution. Thanks for posting this Yuliam! I've been looking for something like this for a while.
https://gist.github.com/twilly86/eb6b61a22b66b4b33717aff84a31a060
Related
I am having an issue updating a record with a composite key using EF Core 5 and Net Core 5. I am able to get the record using both parts of the composite key (two ids in my case) but when I save the changes to the database I get the following error:
Database operation expected to affect 1 row(s) but actually affected 24 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
After looking in the database, it looks like entity framework is trying to update all the records in the table with same id (first part of the composite key) instead of updating the record that I got.
Has anyone else seen this behavior? Any ideas of how to get around it or what I might be doing wrong?
Here is a basic version of the table set up
Table1:
Id (PK)
Table2:
Id (PK),
Table1Id (PK, FK)
Scenario where it fails:
Table1:
Id
--
1
2
Table2:
Id, Table1Id
------------
1, 1
2, 1
1, 2
2, 2
If I try to update the first record in Table2 (1, 1), EF would try to update it and the third record (1, 2) and throw the above error.
Code:
// Getting record
var record = await context.Table2s
.FirstOrDefaultAsync(e => e.Id == 1 && e.Table1Id == 1, cancellationToken);
// Saving
await context.SaveChangesAsync(cancellationToken);
// I have also tried specifying which record to update before saving
context.Table2s.Update(record);
Configuration for Table2:
builder.ToTable("Table2");
builder.HasKey(e => new { e.Id, e.Table1Id });
builder.HasOne(e => e.Table1)
.WithMany(m => m.Table2s)
.HasForeignKey(e => e.Table1Id)
.OnDelete(DeleteBehavior.Cascade);
It works in my own projects, I hope it will be useful.
protected override void OnModelCreating(DbModelBuilder mb) {
mb.Entity<WebServisUser>().HasKey(x => new
{
x.Proje, /* Composite Keys */
x.KulAdi /* Composite Keys */
});
mb.Entity<WebServisUser>().Property(e => e.Proje).IsUnicode(false);
mb.Entity<WebServisUser>().Property(e => e.KulAdi).IsUnicode(false);
}
I have added the link other functions for better understanding.
public static class Helper
{
public static bool IsNull(this object value) => (value == null || Convert.IsDBNull(value) || Convert.GetTypeCode(value) == TypeCode.Empty);
public static bool IsNotNull(this object value) => !value.IsNull();
public static string ToPropertyName(this Expression value)
{
if (typeof(LambdaExpression).IsAssignableFrom(value.GetType())) { value = ((LambdaExpression)value).Body; }
if (value is MemberExpression e_mem) { return e_mem.Member.Name; }
else if (value is UnaryExpression e_una) { return ((MemberExpression)e_una.Operand).Member.Name; }
else { throw new InvalidCastException(String.Format("value type can be \"{0}\" or \"{1}\"", nameof(MemberExpression), nameof(UnaryExpression))); }
}
public static object ChangeType(object value, Type type)
{
var c = TryTypeIsNullable(type, out Type _outtype);
return (c && value.IsNull()) ? null : (_outtype.IsEnum ? Enum.ToObject(_outtype, value) : Convert.ChangeType(value, c ? Nullable.GetUnderlyingType(type) : type));
}
public static void SetPropertyValue<T>(this T value, string propertyname, object data) where T : class
{
var p = typeof(T).GetProperty(propertyname);
p.SetValue(value, data.IsNull() ? null : ChangeType(data, p.PropertyType));
}
public static bool TryTypeIsNullable(Type value, out Type outvalue)
{
var t = (value.IsGenericType && value.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
outvalue = (t ? value.GenericTypeArguments[0] : value); return t;
}
public static bool TryTypeIsAttribute<T, Y>(T value, out Y outvalue) where T : ICustomAttributeProvider where Y : Attribute
{
bool ret;
try
{
var v = value.GetType();
var y = typeof(Y);
if (typeof(Assembly).IsAssignableFrom(v) && Attribute.IsDefined((Assembly)((object)value), y)) { ret = true; }
else if (typeof(MemberInfo).IsAssignableFrom(v) && Attribute.IsDefined((MemberInfo)((object)value), y)) { ret = true; }
else if (typeof(Module).IsAssignableFrom(v) && Attribute.IsDefined((Module)((object)value), y)) { ret = true; }
else if (typeof(ParameterInfo).IsAssignableFrom(v) && Attribute.IsDefined((ParameterInfo)((object)value), y)) { ret = true; }
else { ret = false; }
outvalue = ret ? value.GetCustomAttributes(y, false).Cast<Y>().Take(1).FirstOrDefault() : default;
}
catch
{
outvalue = default;
ret = false;
}
return ret;
}
public static void SetCompositeKey<T, TKey>(this DbContext context, T entity, Expression<Func<T, TKey>> compositekey, TKey compositekeyvalue, Action<T> actionislem = null) where T : class, new()
{
var t = typeof(T);
var c = compositekey.ToPropertyName();
var ie_prop = typeof(T).GetProperties().Where(x => x.GetMethod.IsNotNull() && x.SetMethod.IsNotNull() && !x.GetMethod.IsVirtual && !x.SetMethod.IsVirtual && !Helper.TryTypeIsAttribute(x, out NotMappedAttribute _)).Select(x => new
{
name = x.Name,
iskey = x.Name == c,
iscompositekey = Helper.TryTypeIsAttribute(x, out KeyAttribute _outkeyvalue) && _outkeyvalue.IsNotNull() && Helper.TryTypeIsAttribute(x, out DatabaseGeneratedAttribute _outdatagenvalue) && _outdatagenvalue.DatabaseGeneratedOption == DatabaseGeneratedOption.None
});
if (ie_prop.Where(x => x.iscompositekey).Count() < 2) { throw new KeyNotFoundException("minimum of 2 composite keys exception message..."); }
else if (ie_prop.Any(x => x.iscompositekey && x.iskey))
{
var addentity = new T();
var e = context.Entry<T>(entity);
var dbset = context.Set<T>();
dbset.Attach(entity);
foreach (var item in ie_prop.Select(x => new
{
x.name,
x.iskey
})) { addentity.SetPropertyValue(item.name, item.iskey ? compositekeyvalue : e.Property(item.name).OriginalValue); }
dbset.Add(addentity); /* Insert record about new key value */
if (actionislem.IsNotNull()) { actionislem(addentity); } /* If there are other areas of connection, do the operations there with the action method. */
dbset.Remove(entity); /* delete old value */
}
else { throw new Exception("compositekey must be composite key exception message..."); }
}
}
and example
using (var mdl = new mdlBayUniOrtak())
{
using (var c = new TransactionScope())
{
var beforeentity = mdl.WebServisUsers.Where(x => x.Proje == "loremipsum").Take(1).FirstOrDefault();
var e = new Action<WebServisUser>((WebServisUser afterentity) =>
{
/* Other actions if necessary. Other foreign key operations can be done here! */
});
mdl.SetCompositeKey(beforeentity, x => x.KulAdi, "galatasaray", e);
mdl.SaveChanges();
c.Complete();
}
}
Update: We are familiar with Automapper but we have code that maps our data tables to models and will be easy to add the SqlTable attribute. And thus we want to be able to do this with simple extensions versus having to do it through Automapper.
Our legacy database has some bad naming practices. In creating our DateModels, we’ve got rid of a lot of the prefixes and extensions.
What we’re working on is a way to take the EF entities we get back from the database and using reflection copy the property values over into our DataModels.
For a simple class, we have everything work. The issue we haven’t figured out just yet is how to handle collections.
A sample of our sql tables would be.
Customer Table
Cust_Id
Cust_Name
Cust_ProductId - FK to Product.Id
Product Table
Product_Id
Product_Name
Then our data models would be
public class CustomerModel : BaseCustomerModel
{
[SLSqlTable("Cust_Id")]
public int Id { get; set; }
[SLSqlTable("Cust_Name")]
public string Name { get; set; }
[SLSqlTable("Cust_ProductId")]
public string ProductId { get; set; }
[SLSqlTable("Products")]
public IList<BaseProduct> Products { get; set; }
}
public class BaseProductModel
{
[SLSqlTable("Product_Id")]
public int? Id { get; set; }
[SLSqlTable("Product_Name")]
public string Name { get; set; }
}
We’re using the SLSqlTableAttribute we created to map the names.
Then from the internet, we’re using the following code to copy the data between properties. It works fine for everything except our collections right now and that’s what we’re trying to figure out. We were think, we detect a collection and then some how just recursively call back into CopyToModelProperties.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static T CopyToModelProperties<T>(this object source) where T : class, new()
{
T destination = new T();
Type typeDest = destination.GetType();
Type typeSrc = source.GetType();
// Collect all the valid properties to map
var results = from srcProp in typeSrc.GetProperties()
let targetProperty = typeDest.GetProperty(GetModelPropertyNameFromSqlAttribute(typeDest, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
select new { sourceProperty = srcProp, targetProperty = targetProperty };
//map the properties
foreach (var props in results)
{
if (props.targetProperty.PropertyType.IsGenericType)
{
var _targetType = props.targetProperty.PropertyType.GetGenericArguments()[0];
// props.sourceProperty.PropertyType.MakeGenericType(_targetType).CopyToModelProperties((dynamic)List<>);
}
else
{
props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null);
}
}
return destination;
}
public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
{
string _ret = "";
var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();
if(_property != null)
{
_ret = _property.Name;
}
return _ret;
}
Here is a sample of code getting customers with all their products.
using (var _dc = new BulkTestEntities())
{
var _custs = _dc.Customers.Include("Products").ToList();
foreach(var cust in _custs)
{
_resp.Add(cust.CopyToModelProperties<CustomerModel>());
}
}
This works fine and the if condition checking IsGenericType works, we just need to figure out what code goes here that handle the collection of products when getting customer back.
We thought it would be a recursive call back to CopyToModelProperties because Products could have a collection inside it as well and that could have a collection so we don’t want to hard code levels.
So the question is how to take props.sourceProperty from the if condition above and copy a collection of SQL Entities over to a collection of DataModels?
I haven't used reflection in a long time but kind of shock something like this wasn't out there or no response.
Digging on the internet, found some code snippets that I pieced together to come up with this. It needs some tweaking but it handles the task we set out to do. Also, not going to handle all scenarios most likely but good enough for us to start and build on. Also not all the PITA configuration with something like AutoMapper. We wrote a program that goes out and gets our sql tables, generates classes and properties and we can easily add our attribute to each property and now all we do is call the functions below to map data.
Code we have to that would use what we came up with.
using (var _dc = new BulkTestEntities())
{
var _custs = _dc.Customers.Include("Products").Include("CustomerType").ToList();
// CopyPropertyData.CopyObjectPropertyData(_custs, _resp);
//foreach(var cust in _custs)
//{
// _resp.Add(cust.CopyToModelProperties<CustomerModel>());
//}
foreach (var cust in _custs)
{
CustomerModel _newCust = new CustomerModel();
SQLExtensions.CopyToModelProperties(cust, _newCust);
// CopyData.CopyObjectData(cust, _newCust);
_resp.Add(_newCust);
}
}
Here are the static classes that will handle an IEnumerable and class.
public static void CopyToModelProperties(object source, object target)
{
Type _typeTarget = target.GetType();
Type _typeSrc = source.GetType();
if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
{
// Dynamic allows us to loop through a collection if it's type IEnumerable
dynamic _sourceList = source;
foreach (var _source in _sourceList)
{
// Create a temp class of the generic type of the target list
object _tempTarget = Activator.CreateInstance(_typeTarget.GetGenericArguments()[0]);
//Recursively call to map all child properties
CopyToModelProperties(_source, _tempTarget);
// Add to target collection passed in
target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
}
}
else
{
var results = from srcProp in _typeSrc.GetProperties()
let targetProperty = _typeTarget.GetProperty(GetModelPropertyNameFromSqlAttribute(_typeTarget, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
// && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
select new { sourceProperty = srcProp, targetProperty = targetProperty };
foreach (var prop in results)
{
if (prop.targetProperty.CanWrite && prop.sourceProperty.CanRead)
{
object targetValue = prop.targetProperty.GetValue(target, null);
object sourceValue = prop.sourceProperty.GetValue(source, null);
if (sourceValue == null) { continue; }
//if (prop.sourceProperty.PropertyType.IsArray
// && prop.targetProperty.PropertyType.IsArray
// && sourceValue != null)
if(prop.sourceProperty.PropertyType.IsClass && prop.targetProperty.PropertyType != typeof(string))
{
var destinationClass = Activator.CreateInstance(prop.targetProperty.PropertyType);
object copyValue = prop.sourceProperty.GetValue(source);
CopyToModelProperties(copyValue, destinationClass);
prop.targetProperty.SetValue(target, destinationClass);
}// See if there is a better way to do this.
else if (prop.targetProperty.PropertyType.GetInterfaces().Any(a => a.Name == "IEnumerable") && prop.sourceProperty.PropertyType.IsGenericType
&& prop.targetProperty.PropertyType.IsGenericType
&& sourceValue != null)
{
CopyToModelList(source, target, prop.targetProperty, prop.sourceProperty, sourceValue);
}
else
{
// CopySingleData(source, target, prop.targetProperty, prop.sourceProperty, targetValue, sourceValue);
prop.targetProperty.SetValue(target, prop.sourceProperty.GetValue(source, null), null);
}
}
}
}
// return target;
}
private static void CopyToModelList(object source, object target, PropertyInfo piTarget, PropertyInfo piSource, object sourceValue)
{
// int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);
// First create a generic collection that matches the piTarget being passed in.
var _listType = typeof(List<>);
props.sourceProperty.PropertyType.GetGenericArguments();
var _genericTargetArgs = piTarget.PropertyType.GetGenericArguments();
var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
var _newtargetList = Activator.CreateInstance(_concreteTargetType);
dynamic _sourceList = piSource.GetValue(source, null);
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
_newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
}
piTarget.SetValue(target, _newtargetList, null);
}
public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
{
string _ret = "";
var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();
if(_property != null)
{
_ret = _property.Name;
}
return _ret;
}
Code is such a PITA to edit on this site, creating a new answer that includes caching. The original posted code performs horribly with 60 records to map. About 5 seconds. The new code with caching, 600 records .36 seconds. In this code we have audit fields and only our mapper can set or change Create audit fields. It may not apply to you and if so, remove. All the code below requires is a simple Sql Attribute such as below. Update that on your model properties if your sql table field names are different than your model property names. So much easier than Automapper because you don't other type of configuration and if your sql properties match your model properties, then you can use the below to create your own mapper method.
[AttributeUsage(AttributeTargets.Property)]
public class SLSqlTableAttribute : Attribute
{
protected string _fieldName { get; set; }
public string FieldName
{
get
{
return _fieldName;
}
set
{
_fieldName = value;
}
}
public SLSqlTableAttribute(string fieldName)
{
FieldName = fieldName;
}
}
public class SourceTargetProperties
{
public PropertyInfo SourceProperty { get; set; }
public PropertyInfo TargetProperty { get; set; }
}
public static class DataMapper
{
static Dictionary<string, List<SourceTargetProperties>> _dictTypeProperties = new Dictionary<string, List<SourceTargetProperties>>();
public static void CopyToSqlProperties(object source, object target, int? userId, DateTimeOffset? modifiedDateTime, bool isInsert)
{
Type _typeTarget = target.GetType();
Type _typeSrc = source.GetType();
// If we're passed in a collection
if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
{
dynamic _sourceList = source;
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(_typeTarget.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
// _newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
}
}
else
{
// Collect all the valid properties to map
if (!_dictTypeProperties.ContainsKey(_typeSrc.BaseType.Name))
{
_dictTypeProperties.Add(_typeSrc.BaseType.Name, (from srcProp in _typeSrc.GetProperties()
let targetProperty = _typeTarget.GetProperty(GetModelPropertyNameFromSqlAttribute(_typeTarget, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
// && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
select new SourceTargetProperties { SourceProperty = srcProp, TargetProperty = targetProperty }).ToList());
}
foreach (var prop in _dictTypeProperties[_typeSrc.BaseType.Name])
{
if (prop.TargetProperty.CanWrite && prop.SourceProperty.CanRead)
{
object targetValue = prop.TargetProperty.GetValue(target, null);
object sourceValue = prop.SourceProperty.GetValue(source, null);
if (sourceValue == null) { continue; }
//if (prop.sourceProperty.PropertyType.IsArray
// && prop.targetProperty.PropertyType.IsArray
// && sourceValue != null)
if (prop.SourceProperty.PropertyType.IsClass && prop.TargetProperty.PropertyType != typeof(string))
{
var destinationClass = Activator.CreateInstance(prop.TargetProperty.PropertyType);
object copyValue = prop.SourceProperty.GetValue(source);
CopyToModelProperties(copyValue, destinationClass);
prop.TargetProperty.SetValue(target, destinationClass);
}// See if there is a better way to do this.
else if (prop.TargetProperty.PropertyType.GetInterfaces().Any(a => a.Name == "IEnumerable") && prop.SourceProperty.PropertyType.IsGenericType
&& prop.TargetProperty.PropertyType.IsGenericType
&& sourceValue != null)
{
CopyToModelList(source, target, prop.TargetProperty, prop.SourceProperty, sourceValue);
}
else
{
string _targetPropertyName = prop.TargetProperty.Name;
if (modifiedDateTime.HasValue && (_targetPropertyName == "CreatedDateTime" || _targetPropertyName == "LastModifiedDateTime" || _targetPropertyName == "CreatedBy" ||
_targetPropertyName == "LastModifiedBy"))
{
switch (_targetPropertyName)
{
case "CreatedDateTime":
if (isInsert)
{
prop.TargetProperty.SetValue(target, modifiedDateTime, null);
}
break;
case "CreatedBy":
if (isInsert)
{
prop.TargetProperty.SetValue(target, userId, null);
}
break;
case "LastModifiedDateTime":
prop.TargetProperty.SetValue(target, modifiedDateTime, null);
break;
case "LastModifiedBy":
prop.TargetProperty.SetValue(target, userId, null);
break;
}
}
else
{
prop.TargetProperty.SetValue(target, prop.SourceProperty.GetValue(source, null), null);
}
}
}
}
}
}
/// <summary>
/// Copy from SQL EF Entities to our Models.
/// Models will have the sql table name as a SLSqlAttribute for this to work.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static void CopyToModelProperties(object source, object target)
{
Type _typeTarget = target.GetType();
Type _typeSrc = source.GetType();
if (_typeSrc.GetInterfaces().Any(a => a.Name == "IEnumerable") && _typeSrc.IsGenericType && _typeTarget.IsGenericType)
{
// figure out a way to take in collections here instead of having to loop through outside of code.
// int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);
var _listType = typeof(List<>);
//var _genericArgs = props.sourceProperty.PropertyType.GetGenericArguments();
//var _genericTargetArgs = _typeTarget.GetGenericArguments();
//var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
//var _newtargetList = Activator.CreateInstance(_concreteTargetType);
//_newtargetList = piSource.GetValue(source, null);
//var _genericTargetArgs
dynamic _sourceList = source;
//dynamic _sourceList = _typeSrc.GetValue(source, null);
// dynamic _targetList = piTarget.GetValue(target, null);
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(_typeTarget.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
// _newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
target.GetType().GetMethod("Add").Invoke(target, new[] { _tempTarget });
}
// _typeTarget.SetValue(target, _newtargetList, null);
}
else
{
// Collect all the valid properties to map
if (!_dictTypeProperties.ContainsKey(_typeSrc.BaseType.Name))
{
_dictTypeProperties.Add(_typeSrc.BaseType.Name, (from srcProp in _typeSrc.GetProperties()
let targetProperty = _typeTarget.GetProperty(GetModelPropertyNameFromSqlAttribute(_typeTarget, srcProp.Name))
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
// && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
select new SourceTargetProperties { SourceProperty = srcProp, TargetProperty = targetProperty }).ToList());
}
foreach (var prop in _dictTypeProperties[_typeSrc.BaseType.Name])
{
if (prop.TargetProperty.CanWrite && prop.SourceProperty.CanRead)
{
object targetValue = prop.TargetProperty.GetValue(target, null);
object sourceValue = prop.SourceProperty.GetValue(source, null);
if (sourceValue == null) { continue; }
//if (prop.sourceProperty.PropertyType.IsArray
// && prop.targetProperty.PropertyType.IsArray
// && sourceValue != null)
if (prop.SourceProperty.PropertyType.IsClass && prop.TargetProperty.PropertyType != typeof(string))
{
var destinationClass = Activator.CreateInstance(prop.TargetProperty.PropertyType);
object copyValue = prop.SourceProperty.GetValue(source);
CopyToModelProperties(copyValue, destinationClass);
prop.TargetProperty.SetValue(target, destinationClass);
}// See if there is a better way to do this.
else if (prop.TargetProperty.PropertyType.GetInterfaces().Any(a => a.Name == "IEnumerable") && prop.SourceProperty.PropertyType.IsGenericType
&& prop.TargetProperty.PropertyType.IsGenericType
&& sourceValue != null)
{
CopyToModelList(source, target, prop.TargetProperty, prop.SourceProperty, sourceValue);
}
else
{
string _targetPropertyName = prop.TargetProperty.Name;
prop.TargetProperty.SetValue(target, prop.SourceProperty.GetValue(source, null), null);
}
}
}
}
// return target;
}
public static string GetModelPropertyNameFromSqlAttribute(Type model, string sqlName)
{
string _ret = "";
var _property = model.GetProperties().Where(w => w.GetCustomAttributes<SLSqlTableAttribute>().Where(w1 => w1.FieldName == sqlName).Any()).FirstOrDefault();
if (_property != null)
{
_ret = _property.Name;
}
return _ret;
}
private static void CopyToModelList(object source, object target, PropertyInfo piTarget, PropertyInfo piSource, object sourceValue)
{
// int _sourceLength = (int)source.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, source, null);
// First create a generic collection that matches the piTarget being passed in.
var _listType = typeof(List<>);
//var _genericArgs = props.sourceProperty.PropertyType.GetGenericArguments();
var _genericTargetArgs = piTarget.PropertyType.GetGenericArguments();
var _concreteTargetType = _listType.MakeGenericType(_genericTargetArgs);
var _newtargetList = Activator.CreateInstance(_concreteTargetType);
//_newtargetList = piSource.GetValue(source, null);
//var _genericTargetArgs
dynamic _sourceList = piSource.GetValue(source, null);
// dynamic _targetList = piTarget.GetValue(target, null);
foreach (var _source in _sourceList)
{
object _tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetGenericArguments()[0]);
CopyToModelProperties(_source, _tempTarget);
// here we have to make recursive call to copy data and populate the target list.
//_targetList.Add(CopyObjectPropertyData(_source, (dynamic)new object()));
_newtargetList.GetType().GetMethod("Add").Invoke(_newtargetList, new[] { _tempTarget });
}
piTarget.SetValue(target, _newtargetList, null);
}
}
I'm looking for a better way for implementing the following logic (HasAnyRelation and SetDeleteMarks) into ChangeTracker in order to avoid rewriting this logic for all my entities.
The problem is that once we call Remove() it will drop all relations with child/parent entities and I can't check the relations into ChangeTracker anymore.
In this post I got a suggestion to implement a copy of my previous entity to keep my collections intact after Remove(). However it works, I also would have to implement the logic many times.
Does anybody would have any other approach to achieve it?
Regards
CONTROLLER DELETE ACTION
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Sector sector)
{
if (ModelState.IsValid)
{
var currentEntity = db.Sector.Find(sector.SectorId);
var hasAnyRelation = EntityHelper.HasAnyRelation(currentEntity);
if (hasAnyRelation)
{
currentEntity = (Sector)EntityHelper.SetDeleteMarks(currentEntity);
db.Sector.Attach(currentEntity);
db.Entry(currentEntity).State = EntityState.Modified;
}
else
{
db.Sector.Attach(currentEntity);
db.Sector.Remove(currentEntity);
}
db.SaveChanges();
}
}
ENTITY HELPER
public static class EntityHelper
{
public static object SetDeleteMarks(object entityObj)
{
var deletedProperty = entityObj.GetType().GetProperties().Where(p => p.Name == "Deleted").FirstOrDefault();
var nameProperties = entityObj.GetType().GetProperties().Where(p => p.Name.Contains("Name"));
if (deletedProperty != null)
{
deletedProperty.SetValue(entityObj, true);
foreach (var nameProperty in nameProperties)
{
var deletedMark = "(*)";
var currentValue = nameProperty.GetValue(entityObj).ToStringNullSafe();
if (!currentValue.Contains(deletedMark) && !String.IsNullOrEmpty(currentValue))
{
nameProperty.SetValue(entityObj, String.Format("{0} {1}", currentValue, deletedMark).Trim());
}
}
}
return entityObj;
}
public static bool HasAnyRelation(object entityObj)
{
var collectionProps = GetManyRelatedEntityNavigatorProperties(entityObj);
foreach (var item in collectionProps)
{
var collectionValue = GetEntityFieldValue(entityObj, item.Name);
if (collectionValue != null && collectionValue is IEnumerable)
{
var col = collectionValue as IEnumerable;
if (col.GetEnumerator().MoveNext())
{
return true;
}
}
}
return false;
}
private static object GetEntityFieldValue(this object entityObj, string propertyName)
{
var pro = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).First(x => x.Name == propertyName);
return pro.GetValue(entityObj, null);
}
private static IEnumerable<PropertyInfo> GetManyRelatedEntityNavigatorProperties(object entityObj)
{
var props = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanWrite && x.GetGetMethod().IsVirtual && x.PropertyType.IsGenericType == true);
return props;
}
}
I have been mucking around with XMLs for entity Framework. I tried to create a type of entity that could have properties injected at runtime,
First I created DynamicEntity object that is dynamic
public class DynamicEntity : DynamicObject
{
Dictionary<string, object> dynamicMembers = new Dictionary<string, object>();
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dynamicMembers[binder.Name] = value;
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (dynamicMembers.TryGetValue(binder.Name, out result))
{
return dynamicMembers.TryGetValue(binder.Name, out result);
}
result = "";
return true;
}
}
then entity inherits from this
public partial class QUOTE_HOUSE : DynamicEntity
(and it does seem to work when I set properties manually after I get data from db).
so based on this mechanism of removing properties I tried to do another one that inserts properties into XMLs, and whole thing seems to hold up ok (at least it does not blow up on mapping which it usually does when XMLs are not right var mappingCollection = new StorageMappingItemCollection(conceptualCollection, storageCollection, new[] {mappingXml.CreateReader()});).
Problem is EF when executing query blows up with
The entity type QUOTE_HOUSE is not part of the model for the current context.
Description: An unhandled exception occurred during the execution of
the current web request. Please review the stack trace for more
information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The entity type
QUOTE_HOUSE is not part of the model for the current context.
[InvalidOperationException: The entity type QUOTE_HOUSE is not part of
the model for the current context.]
System.Data.Entity.Internal.InternalContext.UpdateEntitySetMappingsForType(Type
entityType) +208
System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type
entityType) +50
Which I traced to TryUpdateEntitySetMappingsForType in System.Data.Entity.Internal.InternalContext after loading pdb for EF
Basically what happens my QUOTE_HOUSE is not in this._workspace.GetItemCollection(DataSpace.OSpace) where UpdateEntitySetMappings tries to map it from.
It checks if it's in this._entitySetMappingsCache.ContainsKey(entityType)) and since it's not it then tries update mappings iterating over this._workspace.GetItemCollection(DataSpace.OSpace) where my item doesn't exist
However I can see that my entity does exist in this._workspace.GetItems<EntityContainer>(DataSpace.CSpace).
Full UpdateEntitySetMappings looks following:
private void UpdateEntitySetMappings()
{
ObjectItemCollection objectItemCollection = (ObjectItemCollection) this._workspace.GetItemCollection(DataSpace.OSpace);
ReadOnlyCollection<EntityType> items = this._workspace.GetItems<EntityType>(DataSpace.OSpace);
Stack<EntityType> entityTypeStack = new Stack<EntityType>();
foreach (EntityType entityType1 in items)
{
entityTypeStack.Clear();
EntityType cspaceType = (EntityType) this._workspace.GetEdmSpaceType((StructuralType) entityType1);
do
{
entityTypeStack.Push(cspaceType);
cspaceType = (EntityType) cspaceType.BaseType;
}
while (cspaceType != null);
EntitySet entitySet = (EntitySet) null;
while (entitySet == null && entityTypeStack.Count > 0)
{
cspaceType = entityTypeStack.Pop();
foreach (EntityContainer entityContainer in this._workspace.GetItems<EntityContainer>(DataSpace.CSpace))
{
List<EntitySetBase> list = entityContainer.BaseEntitySets.Where<EntitySetBase>((Func<EntitySetBase, bool>) (s => s.ElementType == cspaceType)).ToList<EntitySetBase>();
int count = list.Count;
if (count > 1 || count == 1 && entitySet != null)
throw Error.DbContext_MESTNotSupported();
if (count == 1)
entitySet = (EntitySet) list[0];
}
}
if (entitySet != null)
{
EntityType entityType2 = (EntityType) this._workspace.GetObjectSpaceType((StructuralType) cspaceType);
Type clrType1 = objectItemCollection.GetClrType((StructuralType) entityType1);
Type clrType2 = objectItemCollection.GetClrType((StructuralType) entityType2);
this._entitySetMappingsCache[clrType1] = new EntitySetTypePair(entitySet, clrType2);
}
}
}
How do entities get into this._workspace.GetItemCollection(DataSpace.OSpace)?
Why would entity be in CSpace but not in OSpace ?
EDIT:
For those who might wanna have a crack at bounty, below are components you might need to set-up environment to reproduce the issue.
public class SystemToDatabaseMapping
{
public SystemToDatabaseMapping(string system, string databaseType, string database, string connectionString, Type enitityType)
{
System = system;
Database = database;
DatabaseType = databaseType;
ConnectionString = connectionString;
EntityType = enitityType;
}
public Type EntityType { get; set; }
public string System { get; set; }
public string Database { get; set; }
public string DatabaseType { get; set; }
public string ConnectionString { get; set; }
public List<ColumnToModify> ColumnsToModify { get; set; }
}
public abstract class ColumnToModify
{
protected ColumnToModify(string table, string column)
{
Table = table;
Column = column;
}
public string Table { get; set; }
public string Column { get; set; }
public abstract bool IsRemove{ get; }
}
public class ColumnToRemove : ColumnToModify
{
public ColumnToRemove(string table, string column) : base(table, column)
{
}
public override bool IsRemove
{
get { return true; }
}
}
public class ColumnToAdd : ColumnToModify
{
public ColumnToAdd(string table, string column, Type type) : base(table, column)
{
this.Type = type;
}
public override bool IsRemove
{
get { return false; }
}
public Type Type { get; set; }
}
Entity generated from db first, (DynamicEntity code is above)
public partial class QUOTE_HOUSE : DynamicEntity
{
public long UNIQUE_ID { get; set; }
}
DbContext for database requires constructor overloads
public partial class EcomEntities : DbContext
{
public EcomEntities(DbConnection connectionString)
: base(connectionString, false)
{
}
public virtual DbSet<QUOTE_HOUSE > QUOTE_HOUSE { get; set; }
....
}
Mechanism that does column injection (it's a rough prototype so be forgiving to how bad it looks atm), when injecting try string column I know that it maps ok.
public static class EntityConnectionExtensions
{
public static IEnumerable<XElement> ElementsAnyNS<T>(this IEnumerable<T> source, string localName)
where T : XContainer
{
return source.Elements().Where(e => e.Name.LocalName == localName);
}
public static IEnumerable<XElement> ElementsAnyNS(this XContainer source, string localName)
{
return source.Elements().Where(e => e.Name.LocalName == localName);
}
private static void ModifyNodes(XElement element, List<ColumnToModify> tableAndColumn)
{
if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value) ||
element.Attribute("StoreEntitySet") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("StoreEntitySet").Value))
{
var matchingRemoveSelectParts = tableAndColumn.Where(oo => oo.IsRemove && element.Value.Contains(string.Format("\"{0}\".\"{1}\" AS \"{1}\"", oo.Table, oo.Column))).ToList();
if (matchingRemoveSelectParts.Any())
{
foreach (var matchingRemoveSelectPart in matchingRemoveSelectParts)
{
var definingQuery = element.ElementsAnyNS("DefiningQuery").Single();
definingQuery.Value = definingQuery.Value.Replace(string.Format(", \n\"{0}\".\"{1}\" AS \"{1}\"", matchingRemoveSelectPart.Table, matchingRemoveSelectPart.Column), "");
}
}
else
{
var nodesToRemove = element.Nodes()
.Where(o =>
o is XElement
&& ((XElement) o).Attribute("Name") != null
&& tableAndColumn.Any(oo => oo.IsRemove && ((XElement) o).Attribute("Name").Value == oo.Column));
foreach (var node in nodesToRemove.ToList())
{
node.Remove();
}
if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value))
{
var elementsToAdd = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("Name").Value);
if (new[] {"Type=\"number\"", "Type=\"varchar2\"", "Type=\"date\""}.Any(o => element.ToString().Contains(o)))
{
foreach (var columnToModify in elementsToAdd)
{
var columnToAdd = (ColumnToAdd) columnToModify;
var type = new[] {typeof (decimal), typeof (float), typeof (int), typeof (bool)}.Contains(columnToAdd.Type)
? "number"
: columnToAdd.Type == typeof (DateTime) ? "date" : "varchar2";
var precision = "";
var scale = "";
var maxLength = "";
if (type == "number")
{
precision = "38";
scale = new[] {typeof (decimal), typeof (float)}.Contains(columnToAdd.Type) ? "2" : "0";
}
if (type == "varchar2")
{
maxLength = "500";
}
var newProperty = new XElement(element.GetDefaultNamespace() + "Property", new XAttribute("Name", columnToAdd.Column), new XAttribute("Type", type));
if (!string.IsNullOrWhiteSpace(precision))
{
newProperty.Add(new XAttribute("Precision", precision));
}
if (!string.IsNullOrWhiteSpace(scale))
{
newProperty.Add(new XAttribute("Scale", scale));
}
if (!string.IsNullOrWhiteSpace(maxLength))
{
newProperty.Add(new XAttribute("MaxLength", maxLength));
}
element.Add(newProperty);
}
}
else if (
new[] {"Type=\"Decimal\"", "Type=\"String\"", "Type=\"DateTime\"", "Type=\"Boolean\"", "Type=\"Byte\"", "Type=\"Int16\"", "Type=\"Int32\"", "Type=\"Int64\""}.Any(
o => element.ToString().Contains(o)))
{
foreach (var columnToModify in elementsToAdd)
{
var columnToAdd = (ColumnToAdd) columnToModify;
var type = new[] {typeof (decimal), typeof (float), typeof (int), typeof (bool)}.Contains(columnToAdd.Type)
? "Decimal"
: columnToAdd.Type == typeof (DateTime) ? "DateTime" : "String";
var precision = "";
var scale = "";
var maxLength = "";
if (type == "Decimal")
{
precision = "38";
scale = new[] {typeof (decimal), typeof (float)}.Contains(columnToAdd.Type) ? "2" : "0";
}
if (type == "String")
{
maxLength = "500";
}
var newProperty = new XElement(element.GetDefaultNamespace() + "Property", new XAttribute("Name", columnToAdd.Column), new XAttribute("Type", type));
if (!string.IsNullOrWhiteSpace(precision))
{
newProperty.Add(new XAttribute("Precision", precision));
}
if (!string.IsNullOrWhiteSpace(scale))
{
newProperty.Add(new XAttribute("Scale", scale));
}
if (!string.IsNullOrWhiteSpace(maxLength))
{
newProperty.Add(new XAttribute("MaxLength", maxLength));
newProperty.Add(new XAttribute("FixedLength", "false"));
newProperty.Add(new XAttribute("Unicode", "false"));
}
element.Add(newProperty);
}
}
}
}
if (element.Attribute("Name") != null && tableAndColumn.Any(oo => oo.Table == element.Attribute("Name").Value) && element.GetNamespaceOfPrefix("store") != null &&
element.Attribute(element.GetNamespaceOfPrefix("store") + "Type") != null &&
element.Attribute(element.GetNamespaceOfPrefix("store") + "Type").Value == "Tables")
{
var matchingAddSelectParts = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("Name").Value);
foreach (var matchingAddSelectPart in matchingAddSelectParts)
{
var definingQuery = element.ElementsAnyNS("DefiningQuery").Single();
var schemaRegex = new Regex(string.Format("\\nFROM \\\"([a-zA-Z0-9]*)\\\".\\\"{0}\\\"", matchingAddSelectPart.Table));
var schema = schemaRegex.Matches(definingQuery.Value)[0].Groups[1].Value;
definingQuery.Value = definingQuery.Value.Replace(
string.Format("\nFROM \"{0}\".\"{1}\" \"{1}\"", schema, matchingAddSelectPart.Table),
string.Format(", \n\"{0}\".\"{1}\" AS \"{1}\"\nFROM \"{2}\".\"{0}\" \"{0}\"", matchingAddSelectPart.Table, matchingAddSelectPart.Column, schema));
}
}
if (element.Attribute("StoreEntitySet") != null && tableAndColumn.Any(oo => !oo.IsRemove && oo.Table == element.Attribute("StoreEntitySet").Value))
{
var matchingAddSelectParts = tableAndColumn.Where(o => !o.IsRemove && o.Table == element.Attribute("StoreEntitySet").Value);
foreach (var matchingAddSelectPart in matchingAddSelectParts)
{
element.Add(new XElement(element.GetDefaultNamespace() + "ScalarProperty", new XAttribute("Name", matchingAddSelectPart.Column),
new XAttribute("ColumnName", matchingAddSelectPart.Column)));
}
}
}
}
public static EntityConnection Create(List<ColumnToModify> tablesAndColumns, string connString)
{
var modelNameRegex = new Regex(#".*metadata=res:\/\/\*\/([a-zA-Z.]*).csdl|.*");
var model = modelNameRegex.Matches(connString).Cast<Match>().SelectMany(o => o.Groups.Cast<Group>().Skip(1).Where(oo => oo.Value != "")).Select(o => o.Value).First();
var conceptualReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".csdl"));
var mappingReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".msl"));
var storageReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".ssdl"));
var conceptualXml = XElement.Load(conceptualReader);
var mappingXml = XElement.Load(mappingReader);
var storageXml = XElement.Load(storageReader);
foreach (var entitySet in new[] {storageXml, conceptualXml}.SelectMany(xml => xml.Elements()))
{
if (entitySet.Attribute("Name").Value == "ModelStoreContainer")
{
foreach (var entityContainerEntitySet in entitySet.Elements())
{
ModifyNodes(entityContainerEntitySet, tablesAndColumns);
}
}
ModifyNodes(entitySet, tablesAndColumns);
}
foreach (var entitySet in mappingXml.Elements().ElementAt(0).Elements())
{
if (entitySet.Name.LocalName == "EntitySetMapping")
{
foreach (var entityContainerEntitySet in entitySet.Elements().First().Elements())
{
ModifyNodes(entityContainerEntitySet, tablesAndColumns);
}
}
ModifyNodes(entitySet, tablesAndColumns);
}
var storageCollection = new StoreItemCollection(new [] {storageXml.CreateReader()});
var conceptualCollection = new EdmItemCollection(new[] { conceptualXml.CreateReader() });
var mappingCollection = new StorageMappingItemCollection(conceptualCollection, storageCollection, new[] {mappingXml.CreateReader()});
var workspace = new MetadataWorkspace();
workspace.RegisterItemCollection(conceptualCollection);
workspace.RegisterItemCollection(storageCollection);
workspace.RegisterItemCollection(mappingCollection);
var connectionData = new EntityConnectionStringBuilder(connString);
var connection = DbProviderFactories
.GetFactory(connectionData.Provider)
.CreateConnection();
connection.ConnectionString = connectionData.ProviderConnectionString;
return new EntityConnection(workspace, connection);
}
}
Initialization:
public ActionResult QUOTE_HOUSE()
{
var onlineDocs = Enumerable.Empty<QUOTE_HOUSE>();
var mappings = new List<SagaSystemToDatabaseMapping>{new SagaSystemToDatabaseMapping("x", "Oracle", "Db1",
"metadata=res://*/Ecom.Ecom.csdl|res://*/Ecom.Ecom.ssdl|res://*/Ecom.Ecom.msl;provider=Oracle.ManagedDataAccess.Client;provider connection string='...'", typeof(EcomEntities))
{
ColumnsToModify = new List<ColumnToModify> { new ColumnToAdd("QUOTE_HOUSE","TESTCOL", typeof(string)) }
}};
var entityConnection = EntityConnectionExtensions.Create(mappings[0].ColumnsToModify,mappings[0].ConnectionString);
using (var db = new EcomEntities(entityConnection))
{
onlineDocs = db.QUOTE_HOUSE.Take(10);
}
return View("QUOTE_HOUSE", onlineDocs.ToList());
}
You should be able to generate oracle database from entity QUOTE_HOUSE and enter some dummy values, don't think you need a view as it blows up on .ToList(). After you generated the database add additional column to database but not model (alter table QUOTE_HOUSE
add TESTCOL Varchar2(20)) - to have column in database that is being injected at runtime in model. You might also need to debug EF assemblies here's how to do it. Please let me know if you need more info or I have missed something.
I know this is probably not what you are expecting, but I guess at least it would help you not wasting more time in that direction.
The good news is that the problem is not caused by your "hackish" code. In fact I was able to reproduce the issue without using that code. Just created a QUOTE_HOUSE table containing a TestCol, imported it into a new edmx context and just deleted the generated TestCol property from the entity class. Then I've made the class inherit the DynamicEntity, created a context using the default constructor, called context.QUOTE_HOUSE.ToList() and it blew up with the exact same exception.
The bad news is that what you are trying to achieve is just not possible. EF uses nothing more and nothing less than reflection for mapping "object space" members. It does not offer any type extension mechanism like TypeDescriptor for instance, or dynamic runtime (it does not even allow you to project to dynamic objects). The later is understandable, since every dynamic object may have a different properties, and there is no such thing as dynamic type. Note that the trick with "removing" columns at run time works because what it does is basically the same as using NotMapped in code first, i.e. the property really exists, but is ignored by EF.
If you are interested in why the entity is in CSpace but not in OSpace, the answer is contained in an internal class called OSpaceTypeFactory (inside System.Data.Entity.Core.Metadata.Edm namespace) - source. There is a method called TryCreateStructuralType, which calls TryCreateMembers and if it returns false, the type is not added. TryCreateMembers in turns calls TryFindAndCreatePrimitiveProperties, passing the PropertyInfo list extracted using reflection, and the later returns false if it cannot map any CSpace member to the OSpace object property, thus effectively preventing the type to be added to the OSpace type collection.
Hope at least that satisfied your curiosity :) But again, "adding" properties at runtime to EF entity is unfortunately a dead idea.
I have a method to update some customer information
public UpdateCustomerInformationResponse UpdateCustomerInformation(UpdateCustomerInformationRequest request)
{
var customer = new Customer
{
FirstName = request.Customer
LastName = request.LastName,
MiddleInitial = request.MiddleInitial,
CustomerEmail = request.CustomerEmail,
UnitNumber = request.UnitNumber,
}
Another object
var fieldRequired = new FieldRequired{
FieldName = "CustomerEmail ",
IsRequired = 1
}
I want to compare 2 objects so that I can find out if FieldName "CustomerEmail"
is present in Customer object.
Its so simple buddy. I found a simple solution using the reflection and extnetion method just follow as below
Create a static class with the name "CompareTwoObjects" and add the code to it.
public static object CompareEquals(this T objectFromCompare, T objectToCompare)//Generic method
{
if (objectFromCompare == null && objectToCompare == null)
return true;
else if (objectFromCompare == null && objectToCompare != null)
return false;
else if (objectFromCompare != null && objectToCompare == null)
return false;
//Gets all the properties of the class
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
object dataFromCompare = objectFromCompare.GetType().GetProperty(prop.Name).GetValue(objectFromCompare, null);
object dataToCompare = objectToCompare.GetType().GetProperty(prop.Name).GetValue(objectToCompare, null);
Type type = objectFromCompare.GetType().GetProperty(prop.Name).GetValue(objectToCompare, null).GetType();
if (prop.PropertyType.IsClass && !prop.PropertyType.FullName.Contains("System.String"))
{
dynamic convertedFromValue = Convert.ChangeType(dataFromCompare, type);
dynamic convertedToValue = Convert.ChangeType(dataToCompare, type);
object result = CompareTwoObjects.CompareEquals(convertedFromValue, convertedToValue);
bool compareResult = (bool)result;
if (!compareResult)
return false;
}
else if (!dataFromCompare.Equals(dataToCompare))
return false;
}
return true;
}
This will give the result of two objects are having same values or not.
Usage:-
Object1.CompareEquals(Object2);
If the class object is complex also this will work. If you found any issues please post back
Your question is a little vague. Is this what you are looking for?
if (fieldRequired.FieldName == "CustomerEmail" && fieldRequired.IsRequired == 1)
{
CustomerEmail = request.CustomerEmail;
}
You can implement the interface IEqualtable, then design the method Equals with the data members that you want to compare. You can see an example of this implementation below:
class Customer: IEquatable<Customer>
{
public string FirstName { get; set; }
public string LastName { get; set; }
...
public bool Equals(Person other)
{
return ((FirstName == other.FirstName) &&
(LastName == other.LastName));
}
}