【问题标题】:How to generalise access to DbSet<TEntity> members of a DbContext?如何概括对 DbContext 的 DbSet<TEntity> 成员的访问?
【发布时间】:2012-04-06 06:13:17
【问题描述】:

我有一个DbContext,其中有以下几种类型的成员:

public DbSet<JobLevel> JobLevels { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Race> Races { get; set; }
public DbSet<Language> Languages { get; set; }
public DbSet<Title> Titles { get; set; }

所有这些都是where T: IdNamePairBase,它只有IdName 成员。我正在拼命寻找一个通用接口来访问这些成员中的任何一个,以将以下 MVC3 控制器代码概括为一个控制器:

public ActionResult Edit(DropDownListModel model, Guid)
{
    var dbSet =  _dbContext.Countries;
    var newItems = model.Items.Where(i => i.IsNew && !i.IsDeleted).Select(i => new { i.Name });
    foreach (var item in newItems)
    {
        if (!string.IsNullOrWhiteSpace(item.Name))
        {
            var undead = ((IEnumerable<IdNamePairBase>)dbSet).FirstOrDefault(p => p.Name.ToLower() == item.Name.ToLower());
            if (undead != null)
            {
                // Assign new value to update to the new char. case if present.
                undead.Name = item.Name;
                undead.IsDeleted = false;
                _dbContext.SaveChanges();
                continue;
            }
            var newPair = new Country { Name = item.Name };
            dbSet.Add(newPair);
            _dbContext.SaveChanges();
        }
    }
    return RedirectToAction("Edit", new {listName = model.ListName});
}

我该如何解决我现在需要为每个DbContext 成员配备一个控制器的问题,就像上面专用于DbSet&lt;Country&gt; Countries 的控制器一样?

部分解决方案: 与 GertArnold 的回答类似,在我知道 _dbContext.Set&lt;T&gt; 他强调的所有内容之前,我在我的上下文类上实现了这个方法来获取特定类型的集合:

public IEnumerable<DbSet<T>> GetDbSetsByType<T>() where T : class
{
    //var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance;
    var props = GetType().GetProperties()
        .Where(p => p.PropertyType.IsGenericType && p.PropertyType.Name.StartsWith("DbSet"))
        .Where(p => p.PropertyType.GetGenericArguments().All(t => t == typeof(T)));
    return props.Select(p => (DbSet<T>)p.GetValue(this, null));
}

【问题讨论】:

标签: c# .net entity-framework entity-framework-4.1 ef-code-first


【解决方案1】:

可以通过使用进行一些概括

var dbSet = _dbContext.Set<T>

并将您的大部分方法放在具有泛型类型参数的方法中。

但是,应该有一个开关来决定应该指定哪种类型以及创建哪种类型,因为我认为该类型是作为模型的属性提供的(是吗?)。所以它可能看起来并不优雅,但可能会更短,使用 DRY-er 代码。

【讨论】:

    【解决方案2】:

    要补充 Gert Arnold 的回答,我想指出 dbContext 上还有另一个方法重载,它从类型对象返回一般 DbSet:

    var dbSet = dbContext.Set(typeof(T))
    

    如果你想添加一个对象,然后使用set.Create()方法创建对象,或者如果你已经有一个使用“new”keyowrd创建的对象,你可以使用转换它(类似于@ 987654321@)

    var entity = dbSet.Create();
    dbSet.Add(entity);
    DbEntityEntry entry = context.Entry(entity);
    entry.CurrentValues.SetValues(yourObject);
    

    【讨论】:

      【解决方案3】:

      我一直在寻找这个问题的答案,我发现使用托管可扩展性框架很容易做到。在这篇文章的底部有一种更快的方法,但是 MEF 允许一种更具可扩展性的方法。

      MEF 允许您从不同的程序集构建动态访问插件;但是它可以用于在单个程序集应用程序中快速填充集合。本质上,我们将使用它作为将我们的程序集反射回类的安全方式。为了使这个功能充分发挥作用,我还将在实体框架模型中实现策略模式。

      添加对您项目的引用,指向System.ComponentModel.Composition。这将提供对 MEF 库的访问权限。

      现在,我们需要实现策略模式。如果您没有 Interfaces 文件夹,请创建一个并添加 IEntity.cs,如下所示。

      IEntity.cs

      namespace Your.Project.Interfaces
      {
          /// <summary>
          ///     Represents an entity used with Entity Framework Code First.
          /// </summary>
          public interface IEntity
          {
              /// <summary>
              ///     Gets or sets the identifier.
              /// </summary>
              /// <value>  
              ///     The identifier.
              /// </value>
              int Id { get; set; }
          }
      }
      

      现在,你们每个具体实体都需要实现这个接口:

      public class MyEntity : IEntity
      {
          #region Implementation of IEntity
      
          /// <summary>
          ///     Gets or sets the identifier.
          /// </summary>
          /// <value>
          ///     The identifier.
          /// </value>
          public int Id { get; set; }
      
          #endregion
      
          // Other POCO properties...
      }
      

      我发现最好的做法是不要为每个实体创建单独的接口,除非您在高测试环境中工作。务实地说,接口应该只在需要该抽象级别的地方使用;主要是在多个具体类将继承时,或者在使用过度热情的控制反转引擎时。如果您的生产模型中的所有内容都有接口,那么您的架构很可能存在重大缺陷。无论如何,足够的漫无边际。

      现在我们已经对所有实体进行了“战略化”,我们可以使用 MEF 来整理它们并在您的上下文中填充一个集合。

      在您的上下文中,添加一个新属性:

      /// <summary>
      ///     Gets a dynamically populated list of DbSets within the context.
      /// </summary>
      /// <value>
      ///     A dynamically populated list of DbSets within the context.
      /// </value>
      [ImportMany(typeof(DbSet<IEntity>))]
      public IEnumerable<DbSet<IEntity>> Sets { get; private set; }
      

      此处的 [ImportMany(typeof(DbSet&lt;IEntity&gt;))] 允许 MEF 填充集合。

      接下来,将相应的Export 属性添加到上下文中的每个 DbSet:

      [Export(typeof(DbSet<IEntity>))]
      public DbSet<MyEntity> MyEntities { get; set; }
      

      Imported 和Exported 属性中的每一个都称为“部分”。拼图的最后一块是组成这些部分。将以下内容添加到上下文的构造函数中:

      // Instantiate the Sets list.
      Sets = new List<DbSet<IEntity>>();
      
      // Create a new Types catalogue, to hold the exported parts.
      var catalogue = new TypeCatalog(typeof (DbSet<IEntity>));
      
      // Create a new Composition Container, to match all the importable and imported parts.
      var container = new CompositionContainer(catalogue);
      
      // Compose the exported and imported parts for this class.
      container.ComposeParts(this); 
      

      现在,如果运气好的话,您应该在上下文中拥有一个动态填充的 DbSet 列表。

      我已使用此方法通过扩展方法轻松截断所有表。

      /// <summary>
      ///     Provides extension methods for DbSet objects.
      /// </summary>
      public static class DbSetEx
      {
          /// <summary>
          ///     Truncates the specified set.
          /// </summary>
          /// <typeparam name="TEntity">The type of the entity.</typeparam>
          /// <param name="set">The set.</param>
          /// <returns>The truncated set.</returns>
          public static DbSet<TEntity> Truncate<TEntity>(this DbSet<TEntity> set)
              where TEntity : class, IEntity
          {
              set.ToList().ForEach(p => set.Remove(p));
              return set;
          }
      }
      

      我在上下文中添加了一个方法来截断整个数据库。

      /// <summary>
      ///     Truncates the database.
      /// </summary>
      public void TruncateDatabase()
      {
          Sets.ToList().ForEach(s => s.Truncate());
          SaveChanges();
      }
      

      编辑(大修):

      上述解决方案现已弃用。现在必须做一些调整才能使其正常工作。要完成这项工作,您需要将 DbSet 导入到类型为“object”的 DbSet 的临时集合中,然后将此集合转换为所需接口类型的 DbSet。对于基本目的,IEntity 接口就足够了。

          #region Dynamic Table List
      
          /// <summary>
          ///     Gets a dynamically populated list of DbSets within the context.
          /// </summary>
          /// <value>
          ///     A dynamically populated list of DbSets within the context.
          /// </value>
          public List<DbSet<IEntity>> Tables { get; private set; }
      
          /// <summary>
          ///     Gets a dynamically populated list of DbSets within the context.
          /// </summary>
          /// <value>
          ///     A dynamically populated list of DbSets within the context.
          /// </value>
          [ImportMany("Sets", typeof (DbSet<object>), AllowRecomposition = true)]
          private List<object> TableObjects { get; set; }
      
          /// <summary>
          ///     Composes the sets list.
          /// </summary>
          /// <remarks>
          ///     To make this work, you need to import the DbSets into a temporary collection of
          ///     DbSet of type "object", then cast this collection to DbSet of your required
          ///     interface type. For basic purposes, the IEntity interface will suffice.
          /// </remarks>
          private void ComposeSetsList()
          {
              // Instantiate the list of tables.
              Tables = new List<DbSet<IEntity>>();
      
              // Instantiate the MEF Import collection.
              TableObjects = new List<object>();
      
              // Create a new Types catalogue, to hold the exported parts.
              var catalogue = new TypeCatalog(typeof (DbSet<object>));
      
              // Create a new Composition Container, to match all the importable and imported parts.
              var container = new CompositionContainer(catalogue);
      
              // Compose the exported and imported parts for this class.
              container.ComposeParts(this);
      
              // Safe cast each DbSet<object> to the public list as DbSet<IEntity>.
              TableObjects.ForEach(p => Tables.Add(p as DbSet<IEntity>));
          }
      
          #endregion
      

      接下来,从构造函数运行 CompileSetsList() 外观(显示了 Web 的最佳实践):

          public MvcApplicationContext()
          {
              // Enable verification of transactions for ExecuteSQL functions.
              Configuration.EnsureTransactionsForFunctionsAndCommands = true;
      
              // Disable lazy loading.
              Configuration.LazyLoadingEnabled = false;
      
              // Enable tracing of SQL queries.
              Database.Log = msg => Trace.WriteLine(msg);
      
              // Use MEF to compile a list of all sets within the context.
              ComposeSetsList();
          }
      

      然后,像这样装饰你的 DbSet:

          /// <summary>
          /// Gets or sets the job levels.
          /// </summary>
          /// <value>
          /// The job levels.
          /// </value>
          [Export("Sets", typeof(DbSet<object>))]
          public DbSet<JobLevel> JobLevels { get; set; }
      

      现在它可以正常工作了。

      【讨论】:

      • 很好的答案。我今年年初才了解 MEF。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-17
      • 1970-01-01
      相关资源
      最近更新 更多