【问题标题】:Add object to Entity Framework dynamically using Reflection使用反射动态地将对象添加到实体框架
【发布时间】:2015-11-14 19:38:55
【问题描述】:

在下面的代码中,domainObject 的类型有所不同(但以DO 结尾,然后我对其进行修剪以获得相应的表名)。有了表的名称及其类型,我想使用来自domainObject 的新属性值在数据库中更新现有对象 - 由于 EF,它的名称与 tableName 相同。因此,我必须先在表中找到具有相同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
var poco = context.TableName.Single(item => item.ID == id);

这里有两个问题:

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

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

有没有办法用反射来做到这一点,还是我必须解决这个问题? (例如,我可以遍历table 中的项目并手动检查[这会将数据库中的所有内容加载到内存中,因此应该避免],或者可以将 EF 配置为执行某种“覆盖”,只要我只是AddID 的对象如果可能的话已经存在)。即使假设我可以解决这个问题,我仍然想知道这个问题的明确答案,因为这对我来说非常有趣!

【问题讨论】:

  • 使用泛型似乎是一个更好的主意...
  • 使用 DbEntry 怎么样?
  • @JanneMatikainen 你能详细说明一下吗?我对这一切都很陌生,所以我只知道(一点)Entity Framework 6,我肯定在那里错过了很多观点。
  • 在实体框架中使用反射添加对象试图达到什么目的?
  • @RaphaëlAlthaus 我绞尽脑汁思考如何做到这一点,但我看不到方法。我从 ASP.NET 应用程序中的 WebDataGrid 获取这些对象,该应用程序具有 IList 作为已切换的 DataSource,因此没有固定类型可以作为通用传递(或不知道如何传递)。跨度>

标签: c# entity-framework system.reflection


【解决方案1】:

如果您想使用反射并通过 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 查询。

【讨论】:

  • 哦,太好了,我不知道Set 方法;我现在不能测试它,但我会尽快测试它,并且 - 如果它有效 - 接受这个答案:-) 在我的情况下,我什至可以将 pocoType 传递给 Set 因为这是里面的内容桌子。
  • 这肯定有效。但我也将为您的问题提供更通用的解决方案,允许通过非主键的属性进行搜索。
【解决方案2】:

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

正如您在代码 cmets 中看到的,您的实体需要实现一个接口,以便您可以访问 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 上下文对象时,界面不会丢失。

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

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

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

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

我更喜欢这个解决方案,比上一个更好。

【讨论】:

  • 您好,谢谢!这看起来很有趣,但我在这里看到了两个可能的问题(我必须更深入地研究第一个问题,也许不是一个):(1)我不知道 TEntity 在我通过的时候domainObject 到我的更新方法,所以它实际上只是一个object 连同它的Type。那么这仍然有效吗?也就是说,CLR 可以使用DbSet&lt;TEntity&gt; 部分吗? (2) 遗憾的是,我们正在开发无法访问互联网的虚拟机。所以我不能使用 NuGet 或其他任何东西。
  • (1) TEntity 是从接收到的参数中推断出来的:在您自己的代码中,您可以:Type type = domainObj.GetType()。这意味着domainObj 是一个Object 类型的变量,它持有一个派生类型的实例:这就是泛型的工作原理! (2) 然后使用不需要AutoMapper 包的最后一个替代方案(来自“Alternatively...”)。无论如何,您是如何将 EF 库添加到您的项目中的?你可以用 AutoMapper 做同样的事情!
  • 啊,我以为我不能在这里使用泛型,因为那时我会有DbSet&lt;object&gt;。很高兴它以这种方式工作! (2)这是我目前不理解的有趣的事情:Install-Package 和 NuGet 包管理器都不是;相反,我必须将 ADO.NET 实体模型添加到项目中,然后从现有数据库创建模型,选择版本 6,但不必选择任何数据库,但它会将必要的 dll 添加到项目中。我不知道它们来自哪里,因为我尝试了以上所有方法;)
猜你喜欢
  • 2015-08-27
  • 1970-01-01
  • 2018-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-22
相关资源
最近更新 更多