5

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!

7
  • Working with generics would look like a muuuuch better idea... Commented Jun 5, 2014 at 11:01
  • How bout just working with the DbEntry? Commented Jun 5, 2014 at 11:04
  • @JanneMatikainen Could you elaborate on that? I'm quite new to all this, so I only got to know (a bit) the Entity Framework 6 and I'm surely missing a lot of perspectives there. Commented Jun 5, 2014 at 11:23
  • What are trying to achieve using reflection to add object in entity framework? Commented Jun 5, 2014 at 11:25
  • @RaphaëlAlthaus I twisted my mind thinking about how to do exactly this, but I can't see a way. I get these objects from a WebDataGrid in an ASP.NET application that has an IList as DataSource that is switched, so there is no fixed type I can pass as generic (or don't know how to). Commented Jun 5, 2014 at 11:26

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Oh great, I didn't know about the Set method; I can't test it right now, but as soon as I can I will, and - given it works - accept this answer :-) In my case I can even just pass pocoType to Set as this is what's in the table.
This works for sure. But I am going as well to present more general solution to your problem that will allow searching by properties that are not primary keys.
3

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.

4 Comments

Hello and thanks! This looks interesting but I see two possible problems for me here (I'll have to take a deeper look at the first though, maybe it's not one): (1) I don't know TEntity at the point where I pass domainObject to my update method, so it's really just an object together with its Type. Does that then still work? That is, can the CLR make use of the part DbSet<TEntity> then? (2) We're working on VM's without internet access sadly.. so I can't use NuGet or anything else.
(1) TEntity is inferred from the received parameter: in your own code, you do: Type type = domainObj.GetType(). That means that domainObj is a variable of type Object holding an instance of a derived type: That's how generics work! (2) Then use the last alternative (from "Alternatively...") which doesn't require AutoMapper package. Anyway how did you get EF library into your project? You can do exactly the same with AutoMapper!
Ah, I thought that I couldn't use generics here because I'd have DbSet<object> then. Nice that it works this way! (2) That's the funny thing I don't understand so far: neither Install-Package nor NuGet Package Manager; instead, I have to add an ADO.NET Entity Model into the project, then create the model from an existing database, choose Version 6, but don't have to select any DB, but it will add the necessary dll's into the project. I don't know where they come from, since I tried all of the above ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.