I'm trying to implement a generic many-to-many Update method using EF6.
Every time I try to update the navigation property, I get an exception:
"Violation of PRIMARY KEY constraint 'PK_dbo.Classes'. Cannot insert
duplicate key in object 'dbo.Classes'. The duplicate key value is
(698d5483-eb48-4d7e-84e7-a9f95a243d3d).\r\nThe statement has been
terminated."
How can I fix it?
public void Update(T entity, params Expression<Func<T, IEnumerable>>[] navProps)
{
using (var context = new Context())
{
T foundEntity = context.Set<T>().Find(entity.Id);
var entry = context.Entry(foundEntity);
entry.CurrentValues.SetValues(entity);
foreach (var prop in navProps)
{
string memberName;
var member = prop.Body as MemberExpression;
if (member != null)
{
memberName = member.Member.Name;
}
else
{
throw new Exception();
}
var collection = entry.Collection(memberName);
collection.Load();
collection.CurrentValue = typeof(T).GetProperty(memberName).GetValue(entity);
}
context.SaveChanges();
}
}
Finally I have found a solution for generic many-to-many update
public void Update(Guid id, T record)
{
using (var context = new FortisDataStoreContext(_client, _user))
{
var set = context.Set<T>().AsQueryable();
foreach (var x in GetNavigationProperties(context, record))
{
foreach (var xx in x.PropertyType.GetGenericArguments())
{
set = set.Include(x.Name);
}
}
var entry = set.FirstOrDefault(x => x.Id == id && x.IsClosed == false);
context.Entry(entry).CurrentValues.SetValues(record);
foreach (var x in GetNavigationProperties(context, record))
{
if(x.PropertyType.GetGenericArguments().Count() > 0)
{
var genericType = x.PropertyType.GenericTypeArguments[0];
var newValues = (IList)x.GetValue(record, null) ?? new object[0];
var genericList = CreateList(genericType);
foreach (var item in newValues)
{
var ident = item.GetType().GetProperty("Id").GetValue(item, null);
var obj = context.Set(genericType).Find(ident);
genericList.Add(obj);
}
context.Entry(entry).Collection(x.Name).CurrentValue = genericList;
}
}
context.SaveChanges();
}
}
Related
I want dinamycally load catalogs to DevExpress grid and repositories. Some columns reference to other tables. I want to realize without "if-else, switch" methods, solve with generic for example.
My project consists of entity framework, unitofwork etc.
RepositoryItemLookUpEdit repositoryItemLookUpEdit = new RepositoryItemLookUpEdit()
{
ValueMember = repositoryItem.ValueMember,
DisplayMember = repositoryItem.DisplayMember
;
// var objectType = GetTypeByName(repositoryItem.FinalTable);
if (repositoryItem.FinalTable=="DeviceTypes")
{
objectList = GetObjectList<DeviceTypes>();
}
else if (repositoryItem.FinalTable == "DeviceKinds")
{
objectList = GetObjectList<DeviceKinds>();
}
repositoryItemLookUpEdit.DataSource = objectList;
bandedGridColumn.ColumnEdit = repositoryItemLookUpEdit;
---
private IEnumerable<T> GetObjectList <T>() where T : Entity
{
IEnumerable<T> objectList=Enumerable.Empty<T>();
using (var unitOfWork = new UnitOfWork(new ApplicationDbContext(optionsBuilder.Options)))
{
objectList = unitOfWork.GetRepository<T>().GetAll();
}
return objectList;
}
---
private T GetTypeByName<T>(string typeName) where T:Entity
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
{
var type = assembly.GetType(typeName);
if (type != null)
{
return type as T;
}
}
return null;
}
My solution is here:
public List<object> GetTableNyName(string tableName)
{
List<object> objectList = new List<object>();
var entityType = GetTypeOfTable(tableName);
if (entityType!=null)
{
var query = (IQueryable)_context.GetType().GetMethod("Set", 1,
Type.EmptyTypes).MakeGenericMethod(entityType.ClrType).Invoke(_context, null);
objectList = query.OfType<object>().ToList();
}
return objectList;
}
--
private IEntityType GetTypeOfTable(string tableName)
{
var entityType = _context.Model.GetEntityTypes().Where(x => x.ClrType.Name == tableName).FirstOrDefault();
return entityType;
}
I'm trying to validate an entity coming from an External context has not changed.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
I have a method which takes in an entity which has not been loaded from the context.
public bool Validate(Employee employee)
{
using (var context = new Context())
{
return context.Entry(employee).State == EntityState.Modified;
}
}
I would like to attach and verify that the attached entity is not modified from whats in the database.
I would prefer not to manually have to iterate of the properties. Is there a way to hack around this?
No need to attach the external entity. You can use the external entity to set values of the database entity and then check the state of the latter:
public bool Validate(Employee externalEmployee)
{
using var context = new Context(); // C# 8.0
var dbEntity = context.Where(x => x.Id == externalEmployee.Id).SingleOrDefault();
if (dbEntity != null)
{
context.Entry(dbEntity).CurrentValues.SetValues(externalEmployee);
return context.Entry(dbEntity).State == EntityState.Modified;
}
return false; // Or true, depending on your semantics.
}
You can try:
public static List<string> GetChanges<T>(this T obj, T dbObj)
{
List<string> result = new List<string>();
var type = typeof(T);
foreach (var prop in type.GetProperties())
{
var newValue = prop.GetValue(obj, null);
var dbValue = prop.GetValue(dbObj, null);
if(newValue == null && dbValue != null)
{
result.Add(prop.Name);
continue;
}
if (newValue != null && dbValue == null)
{
result.Add(prop.Name);
continue;
}
if (newValue == null && dbValue == null)
continue;
if (!newValue.ToString().Equals(dbValue.ToString()))
result.Add(prop.Name);
}
return result;
}
if resultList.Count > 0, your object has changes.
In your Validate Method:
public bool Validate(Employee employee)
{
using (var context = new Context())
{
Employee dbEmployee = context.Employee.Find(employee.Id);
if(employee.GetChanges(dbEmployee).Count > 0)
return true;
return false;
}
}
It's a god workaround =D
Works for me!
I have a generic Update method for Entity Framework in an abstract DatabaseOperations<T,U> class:
public virtual void Update(T updatedObject, int key)
{
if (updatedObject == null)
{
return;
}
using (var databaseContext = new U())
{
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
databaseContext.Entry(foundEntity).CurrentValues.SetValues(updatedObject);
databaseContext.SaveChanges();
}
}
However, this does not handle many-to-many relationships.
This many-to-many update problem can be overcome by overriding the Update method in TrussSetDatabaseOperations : DatabaseOperations<TrussSet, TrussManagementDatabaseContext> to read as follows:
public override void Update(TrussSet updatedTrussSet, int key)
{
if (updatedTrussSet == null)
{
return;
}
using (var databaseContext = new TrussManagementDatabaseContext())
{
databaseContext.Database.Log = Console.Write;
TrussSet foundTrussSet = databaseContext.TrussSets.Find(key);
databaseContext.Entry(foundTrussSet).CurrentValues.SetValues(updatedTrussSet)
// Update the many-to-many relationship of TrussSets to Seals
databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).Load();
databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).CurrentValue = updatedTrussSet.Seals;
databaseContext.SaveChanges();
}
}
However, this overriding would proliferate through all the classes that inherit from DatabaseOperations and have a TrussSet object. Can I somehow inject the added two lines into the generic update method, so that the update method is given the collection properties, loads them, and applies the respective updated collection to that entity? Thanks in advance.
Looking at your code, the following comes to mind:
public virtual void Update(T updatedObject, int key, params string[] navigationProperties) {
if (updatedObject == null) {
return;
}
using (var databaseContext = new U()) {
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
var entry = databaseContext.Entry(foundEntity);
entry.CurrentValues.SetValues(updatedObject);
foreach (var prop in navigationProperties) {
var collection = entry.Collection(prop);
collection.Load();
collection.CurrentValue = typeof(T).GetProperty(prop).GetValue(updatedObject);
}
databaseContext.SaveChanges();
}
}
You can also use Expressions instead of strings (and then extract property names from those expressions) if you want more type-safety.
Update: here is what I mean by "use Expressions" in this case:
public virtual void Update(T updatedObject, int key, params Expression<Func<T, IEnumerable>>[] navigationProperties) {
if (updatedObject == null) {
return;
}
using (var databaseContext = new U()) {
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
var entry = databaseContext.Entry(foundEntity);
entry.CurrentValues.SetValues(updatedObject);
foreach (var prop in navigationProperties) {
string memberName;
var member = prop.Body as MemberExpression;
if (member != null)
memberName = member.Member.Name;
else throw new Exception("One of the navigationProperties is not a member access expression");
var collection = entry.Collection(memberName);
collection.Load();
collection.CurrentValue = typeof (T).GetProperty(memberName).GetValue(updatedObject);
}
databaseContext.SaveChanges();
}
}
I am using EF5 and I have some entities that I wrote myself and also wrote a function that adds all the mappings to the modelbuilder configurations.
Running a simple test query, I can query items from a table successfully but when I try to add a new item and save, I get an exception that the primary key of my entity is null, even though I gave it a value.
It's quite possible that I messed up the mappings, but I don't know why it would work for a query and not a save.
public class User : IMappedEntity
{
[Key]
[Column("USER_ID")]
public int UserID { get; set; }
[Column("FIRST_NAME")]
public String FirstName { get; set; }
[Column("LAST_NAME")]
public String LastName { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingEntitySetNameConvention>();
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
var addMethod = (from m in (modelBuilder.Configurations).GetType().GetMethods()
where m.Name == "Add"
&& m.GetParameters().Count() == 1
&& m.GetParameters()[0].ParameterType.Name == typeof(EntityTypeConfiguration<>).Name
select m).First();
if(mappings != null)
{
foreach(var map in mappings)
{
if(map != null && !mappedTypes.Contains(map.GetType()))
{
var thisType = map.GetType();
if (thisType.IsGenericType)
{
thisType = map.GetType().GenericTypeArguments[0];
}
var thisAddMethod = addMethod.MakeGenericMethod(new[] {thisType});
thisAddMethod.Invoke(modelBuilder.Configurations, new[] { map });
mappedTypes.Add(map.GetType());
}
}
}
}
private List<Object> BuildMappings(IEnumerable<Type> types)
{
List<Object> results = new List<Object>();
var pkType = typeof(KeyAttribute);
var dbGenType = typeof(DatabaseGeneratedAttribute);
foreach (Type t in types)
{
String tableName = GetTableName(t);
String schemaName = GetSchema(t);
var mappingType = typeof(EntityTypeConfiguration<>).MakeGenericType(t);
dynamic mapping = Activator.CreateInstance(mappingType);
if (!String.IsNullOrWhiteSpace(schemaName))
mapping.ToTable(tableName, SchemaName.ToUpper());
else
mapping.ToTable(tableName);
var keys = new List<PropertyInfo>();
foreach (PropertyInfo prop in t.GetProperties())
{
String columnName = prop.Name;
if(Attribute.IsDefined(prop, typeof(ColumnAttribute)))
{
columnName = (prop.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute).Name;
}
if(Attribute.IsDefined(prop, pkType))
keys.Add(prop);
var genFunc = (typeof(Func<,>)).MakeGenericType(t, prop.PropertyType);
var param = Expression.Parameter(t, "t");
var body = Expression.PropertyOrField(param, prop.Name);
dynamic lambda = Expression.Lambda(genFunc, body, new ParameterExpression[] { param });
//if (prop.PropertyType == typeof(Guid) || prop.PropertyType == typeof(Nullable<Guid>))
//{
// mapping.Property(lambda).HasColumnType("Guid");
//}
//else
mapping.Property(lambda).HasColumnName(columnName);
if (Attribute.IsDefined(prop, dbGenType))
mapping.Property(lambda).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
}
if (keys.Count == 0)
throw new InvalidOperationException("Entity must have a primary key");
dynamic entKey = null;
if(keys.Count == 1)
{
var genFunc = (typeof(Func<,>)).MakeGenericType(t, keys[0].PropertyType);
var param = Expression.Parameter(t, "t");
var body = Expression.PropertyOrField(param, keys[0].Name);
entKey = Expression.Lambda(genFunc, body, new ParameterExpression[] { param });
}
else
{
//if entity uses a compound key, it must have a function named "GetPrimaryKey()" which returns Expression<Func<EntityType,Object>>
//this is because I can't create an expression tree that creates an anonymous type
entKey = t.GetMethod("GetPrimaryKey");
}
mapping.HasKey(entKey);
results.Add(mapping);
}
return results;
}
static void Main(string[] args)
{
using (var ctx = new DQSA.Data.DBContext("DQSATEST"))
{
var xxx = (from u in ctx.Query<DQSA.Data.Entities.User>()
select u).ToList(); //this works, I can see my user
ctx.Set<DQSA.Data.Entities.User>().Add(new DQSA.Data.Entities.User()
{ UserID = 0,
FirstName="Sam",
LastName="Sam"
});
ctx.SaveChanges(); //get an exception here
xxx = (from u in ctx.Query<DQSA.Data.Entities.User>()
select u).ToList();
}
}
It looks like your UserID property is being mapped as an Identity column by convention so the EF query provider thinks it doesn't need to insert that value and the database complains because the field is not nullable.
You can override the convention in your model by using the DatabaseGeneratedAttribute ...
public class User : IMappedEntity
{
[Key]
[Column("USER_ID")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserID { get; set; }
...
}
or by removing the convention globally (in your DbContext's OnModelCreating() method) ...
modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();
I think you need to try to seed with one or few records then context.SaveChanges()
by default, entity framework should mark the primary key column of a new table created with Code First as an identity column. Did the database exist prior to your code, or did you use code first to create it?
Can you verify in Management Studio that the column has identity turned on for that field?
Code I think can be Linq:
foreach (var key in _dic.Keys) // for each observed key
if (_changedKeys.Contains(key)) // if it changed
foreach (var item in _dic[key]) // then for each observer
if (_webSitePage.Session[key] != null) // , as long as the value is something
item(_webSitePage.Session[key]); // call the registered delegate
In context of surrounding code:
public class WebSiteContext
{
private WebSitePage _webSitePage;
internal void SetSessionValue<T>(string key, T value)
{
object current = _webSitePage.Session[key];
if (current != null && current.Equals(value)) return;
_webSitePage.Session[key] = value;
_changedKeys.Add(key);
}
Dictionary<string, List<Action<object>>> _dic = new Dictionary<string, List<Action<object>>>();
List<string> _changedKeys = new List<string>();
internal void WhenSessionValueChanges(string key, Action<object> action)
{
if (!_dic.ContainsKey(key)) _dic[key] = new List<Action<object>>(); // create on demand
_dic[key].Add(action);
}
internal void PageLoadComplete()
{
foreach (var key in _dic.Keys) // for each observed key
if (_changedKeys.Contains(key)) // if it changed
foreach (var item in _dic[key]) // then for each observer
if (_webSitePage.Session[key] != null) // , as long as the value is something
item(_webSitePage.Session[key]); // call the registered delegate
}
}
public class WebSitePage : System.Web.UI.Page
{
public WebSitePage()
{
this.WebSiteContext = new WebSiteContext(this);
}
public WebSiteContext WebSiteContext { get; set; }
protected override void OnLoadComplete(EventArgs e)
{
base.OnLoadComplete(e);
this.WebSiteContext.PageLoadComplete();
}
}
It could be changed to LINQ, but since you are calling item(...) for its side-effects, I think using LINQ here is inappropriate and a foreach loop is better.
You could however write something like this:
foreach (var key in _dic.Keys.Where(key => _changedKeys.Contains(key))
{
foreach (var item in _dic[key])
{
var value = _webSitePage.Session[key];
if (value != null)
{
item(value);
}
}
}
var keys = from key in _dic.Keys
where _changedKeys.Contains(key) && _webSitePage.Session[key] != null
from item in _dic[key]
select item;
foreach (var key in keys)
item(_webSitePage.Session[key]);
EDIT:
The above is horribly wrong. Apologies. I was intent on using query comprehension to answer the question. Here's an attempt:
var tuples = from key in _dic.Keys
where _changedKeys.Contains(key) && _webSitePage.Session[key] != null
from item in _dic[key]
select new { key, item };
foreach (var t in tuples)
t.item(_webSitePage.Session[t.key]);
This shortens it a little. Note that I moved the _webSitePage check up because it does not depend on the value of item:
foreach (var kvp in _dic.Where(kvp => !_changedKeys.Contains(kvp.Key) && _webSitePage.Session[kvp.Key] != null))
foreach (var item in kvp.Values) // then for each observer
item(_webSitePage.Session[key]); // call the registered delegate
var keys = _dic.Keys.Where(key => _changedKeys.Contains(key) &&
(_webSitePage.Session[key] != null));
foreach (var key in keys)
foreach (var item in _dic[key])
item(_webSitePage.Session[key]);
(the second test does not need to be in the inner foreach loop unless item modifies _webSitePage.Session)