繁体   English   中英

使用Reflection动态地向Entity Framework添加对象

[英]Add object to Entity Framework dynamically using Reflection

在下面的代码中, domainObject的类型各domainObject相同(但以DO结尾,然后我修剪它以获得相应的表名)。 拥有表的名称及其类型,我想更新一个现有对象 - 它的名称与由于EF的tableName相同 - 在数据库中使用来自domainObject的新属性值。 因此,我必须首先在表中找到具有相同ID的POCO来覆盖它。 这是到目前为止的代码:

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");
  }
}

通常,知道实际的表而不仅仅是它的类型,我现在将获得POCO via

var poco = context.TableName.Single(item => item.ID == id);

这里有两个问题:

(1) Single是一种扩展方法。

(2)我不知道如何以object形式获取lambda表达式以将其传递给Invoke of Single

有什么方法可以用Reflection完成这项工作,还是我必须解决这个问题? (例如,我可以遍历table的项目并手动检查[将从DB加载到内存中的所有内容,因此应该避免],或者可以将EF配置为在我Add和时执行某种“覆盖”如果可能的话,其ID已存在的对象)。 即使我认为我可以解决这个问题,我仍然想知道这个问题的确切答案,因为它对我来说非常有趣!

如果你想使用反射并按ID查找给定的实体,那么如果ID是主键,这很简单,因为这就是你所要做的:

object entity = context.Set(domainObject.GetType()).Find(id);

如果您的属性不是主键,则需要按如下方式执行:

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 });

首先使用Expression类构建将传递给Single方法的表达式(在您的情况下,这将是p => p.ID == id )。 然后从Queryable类中搜索正确的Single方法。 最后一件事是使用适当的参数调用此方法。 这样,您可以使用Reflection执行任何linq查询。

您只需要创建一个泛型方法,使用表示实体类型的类型参数并使用相应的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;
}

正如您在代码注释中看到的,您的实体需要实现一个接口,以便您可以访问Id属性:

public interface IId
{
  public int Id { get; set; }
}

然后,您需要将泛型方法包含在具有相应类型约束的泛型类中:

public RepoClass<TEntity>
   where TEntity : IId
{
   // Define the generic method here
}

通过这种方式,您不必诉诸于Reflection

如果您正在使用某种T4模板或其他任何东西来创建POCO,请将它们设为部分类,以便您可以在单独的文件中声明接口,如下所示:

public partial MyDomainClass : IId
{
}

在此wya中,更新Db Context对象时,界面不会丢失。

最后,下载一个使用ValueInjecter,例如使用Nuget Package Manager,或在Nuget Package Manager控制台中运行Install-Package ValueInjecter

当你包括using Omu.ValueInjecter; 在您的代码中,您将获得所有对象的InjectFrom扩展方法,允许从源对象中自动复制所有属性(通过匹配它们的名称)。 不要使用AutoMapper ,否则你将不得不解决其他问题。

或者,您可以检查数据库中是否存在该对象(为了安全性)并使用原始对象,而不复制属性,即

  var updatedObject = ctx.Set<TEntity>().Attach(domainObject);
  ctx.Entry(updatedObject).State = EntityState.Modified;
  ctx.SaveChanges();

我更喜欢这种解决方案,比前一种更好。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM