Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add object to Entity Framework dynamically using Reflection

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!

like image 858
InvisiblePanda Avatar asked Jun 05 '14 10:06

InvisiblePanda


People also ask

How to create object dynamically?

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.

Does EF core use reflection?

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.


2 Answers

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.

like image 80
mr100 Avatar answered Sep 26 '22 01:09

mr100


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.

like image 34
JotaBe Avatar answered Sep 24 '22 01:09

JotaBe