In the following code, the type of domainObject
varies (but ends with DO
, which I trim then to get the corresponding table name). Having the name of the table and its type, I want to update an existing object - its name is the same as the tableName
due to the EF - in the database with the new property values from domainObject
. Therefore, I have to find the POCO in the table with the same ID
first to overwrite this. This is the code so far:
public void Update(object domainObject)
{
Type type = domainObject.GetType();
string tableName = type.Name.Substring(0, type.Name.Length - 2);
PropertyInfo tableProp = typeof(MyDbContext).GetProperty(tableName);
Type tableType = tableProp.PropertyType;
Type pocoType = tableType.GetGenericArguments()[0];
int id = (int)type.GetProperty("ID").GetValue(domainObject);
using (var context = new MyDbContext())
{
object table = tableProp.GetValue(context);
MethodInfo singleMethod = tableType.GetMethod("Single");
}
}
Usually, knowing the actual table and not just its type, I would now get the POCO via
var poco = context.TableName.Single(item => item.ID == id);
There's 2 problems here:
(1) Single
is an extension method.
(2) I don't have an idea how to get the lambda expression in form of an object
to pass it to the Invoke
of Single
.
Is there any way to do this at all with Reflection, or do I have to work around this? (For example, I could iterate through the items in table
and check manually [which would load everything from the DB into memory and thus should be avoided], or maybe configure the EF to do some kind of 'override' whenever I just Add
and object whose ID
is already present if this is possible). Even supposing I could work around this, I'd still like to know a definitive answer to this question, since it's pretty interesting for me!
You can create custom dynamic objects by using the classes in the System. Dynamic namespace. For example, you can create an ExpandoObject and specify the members of that object at run time. You can also create your own type that inherits the DynamicObject class.
Entity Framework Core uses some reflection magic to access the behind the scenes private properties. So it is now possible to write “real” object oriented entities and persist them to the database. Adding a normal entity class, without any considerations taken to persistence works perfectly well with EF Core.
If you want to use reflection and to find given entity by ID then, if ID is primary key this is fairly simple as this is all you have to do:
object entity = context.Set(domainObject.GetType()).Find(id);
If your property is not primary key then you need to do it as follows:
ParameterExpression p = Expression.Parameter(domainObject.GetType());
Expression property = Expression.Property(p, "ID");
Expression c = Expression.Constant(id);
Expression body = Expression.Equal(property, c);
Expression exp = Expression.Lambda(body, new ParameterExpression []{ p });
MethodInfo singleMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "Single" && m.GetParameters().Count() == 2)
.MakeGenericMethod(domainObject.GetType());
DbSet dbSet = context.Set(domainObject.GetType());
object entity = singleMethod.Invoke(null, new object[]{ dbSet, exp });
First with Expression
class you build expression that will be passed to Single
method (in your case this will be p => p.ID == id
). Then you search proper Single
method from Queryable
class. The last thing is to invoke this method with proper parameters. This way you may do any linq queries with use of Reflection
.
You simply need to make a generic method, with a type parameter that represents the type of your entity and use the corresponding DbSet.
public int Update<TEntity>(TEntity domainObject)
{
int id = domainObject.Id; // Needs interface !!
using (var context = new MyDbContext())
{
var objectInDb
= ctx.DbSet<TEntity>.Single(e => e.Id == id); // Needs interface !!
// Use ValueInjecter (not AutoMapper) to copy the properties
objectInDb.InjectFrom(domainObject); // needs ValueInjecter Nuget Package
context.SaveChanges();
}
return userId;
}
As you see in the code comments, your entities need to implement an interface so that you can access the Id
property:
public interface IId
{
public int Id { get; set; }
}
And then you need to include the generic method in a generic class that has the corresponding type constraint:
public RepoClass<TEntity>
where TEntity : IId
{
// Define the generic method here
}
In this way you don't have to resort to Reflection
.
If you're using some kind of T4 template,or whatever, to create your POCOs, make them partial classes, so that you can declare the interface in a separate file, like this:
public partial MyDomainClass : IId
{
}
In this wya, the interface won't be lost when you update your Db Context objects.
And finally, download an use ValueInjecter, for example using Nuget Package Manager, or running Install-Package ValueInjecter
in the Nuget Package Manager console.
When you include using Omu.ValueInjecter;
namespace in your code, you'll get an InjectFrom
extension method on all objects, that allows to automatically copy all the properties from a source object (by matching their names). Don't use AutoMapper
, or you'll have to solve other problems.
Alternatively, you can check that the object exists in the DB (for security) and use the original object, without copying the properties, i.e.
var updatedObject = ctx.Set<TEntity>().Attach(domainObject);
ctx.Entry(updatedObject).State = EntityState.Modified;
ctx.SaveChanges();
I prefer this solution, better than the previous one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With