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();
}
}
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 have the following search function for the search for duplicate values
public bool isRecordExisting(string newValue)
{
bool isExisting = false;
using (var db = new SampleEntities_1())
{
var res = db.SAMPLE_TABLE.Where(x => x.SAMPLE_COLUMN == newValue).First();
if (res != null) {
isExisting = false;
}
}
return isExisting;
}
I got a few DbInstances
SampleEntities_1
SampleEntities_2
SampleEntities_3
Therefore I trying to make this function a generic one, like following
public bool isRecordExisting(string newValue, string column, DbSet<T> tableName, DbContext dbcontextname)
{
bool isExisting = false;
using (var db = new dbcontextname())
{
var res = db.tableName.Where(x=>x.column = newValue).First();
if (res != null)
{
isExisting = false;
}
}
return isExisting;
}
so I can call this function like this
var result = isRecordExisting("Bob", "SAMPLE_COLUMN", "SAMPLE_TABLE", SampleEntities_1);
can I know this approach possible or not, already I have compilation errors :(
'dbcontextname' is a variable but is used like a type
'SampleEntities_1' is a type, which is not valid in the given context
You could use a Generic repository, or a factory type pattern, etc..
Example, if you want to create a Generic Repository in your case, then something like this:
public class GenericRepo<TEntity, TContext> : IGenericRepo<TEntity>, IDisposable where TEntity : class where TContext : DbCOntext, new()
{
public TContext Context;
public GenericRepo(DbContext)
{
Context = dbContext as TContext;
}
public virtual TEntity Get(Expression<Func<TEntity, bool>> where = null)
{
Context.Set<TEntity>.FirstOrDefault(where);
}
}
Then maybe something like this:
public bool isRecordExisting(string newValue, string column)
{
bool isExisting = false;
using (var db = new GenericRepo<EntityType, SampleEntities_1>())
{
var res = db.Get(x => x.column == newValue);
if (res != null)
{
isExisting = false;
}
}
return isExisting;
}
Although you can pass the column and tablename essentially this way ^, you still need to create an instance of the GenericRepo for each DBContext. Same with the factory pattern. You could try what Salomon Zhang mentioned in the comments.
Also, note: Always use async code for accessing the DB.
I'm overriding the ValidateEntity method to check for unique validation and I've hit a stumbling block.
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
if (entityEntry.Entity is ReferenceType && entityEntry.State == EntityState.Added)
{
var entity = entityEntry.Entity as ReferenceType;
var pluralService = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en-gb"));
var pluralEntity = pluralService.Pluralize(entity.GetType().Name);
// I would like Courses to be replaced with the property name of pluralEntity
if (Courses.Where(x => x.Title == entity.Title).Count() > 0)
{
result.ValidationErrors.Add(new DbValidationError(nameof(entity.Title), nameof(entity.Title) + " must be unique."));
}
}
if (result.ValidationErrors.Count > 0)
{
return result;
}
else
{
return base.ValidateEntity(entityEntry, items);
}
}
In my SchoolContext class I have the property DbSet<Course> Courses which is a ReferenceType (a custom abstract class type).
The value of pluralEntity is Courses, but I want to put in the if-statement something similar to:
if (Property(pluralEntity).Where(x => x.Title == entity.Title).Count() > 0)
{
// validate
}
Is there a way to do this?
Update
I've got this:
var prop = (DbSet<ReferenceType>) GetType().GetProperty(pluralEntity).GetValue(this, null);
if (prop.Where(x => x.Title == entity.Title).Count() > 0)
{
result.ValidationErrors.Add(new DbValidationError(nameof(entity.Title), nameof(entity.Title) + " must be unique."));
}
But because ReferenceType is an abstract class it cannot cast it at runtime.
I'd like to do something like this
var prop = (DbSet<typeof(entityEntry.Entity.GetType().Name)>)
But of course that's a variable and can't be passed in as a generic type
The only thing I can think of at the moment is writing a custom validation method, using the repository pattern.
First, create an interface which all your entities will implement
public interface IEntity
{
public string Title {get; set; }
}
Then create the repository:
public class Repository<TEntity> where TEntity: class, IEntity
{
private YourContext context = new YourContext();
private DbSet<TEntity> AppDbSet;
public Repository()
{
AppDbSet = context.Set<TEntity>();
}
//a couple of method to retrieve data...
public List<TEntity> GetAll()
{
return AppDbSet.ToList();
}
public IEnumerable<TEntity> Find(Func<TEntity, bool> predicate)
{
return AppDbSet.Where<TEntity>(predicate);
}
public TEntity Single(Func<TEntity, bool> predicate)
{
return AppDbSet.FirstOrDefault(predicate);
}
//Lastly, implement a validation method
public bool IsValid(TEntity entity)
{
if (AppDbSet.SingleOrDefault(x => x.Title == entity.Title) != null)
return false;
else
return true;
}
}
Use the repository as follow:
Repository<Course> courseRepository = new Repository<Course>();
Course course = new Course();
course.Title = "Boring course";
Console.WriteLine(courseRepository.IsValid(course));
Hope it helps.
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();
}
}
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?