如何概括对 DbSetup DbContext 成员的访问

本文关键字:DbContext 成员 访问 TEntity 何概括 DbSetup | 更新日期: 2023-09-27 18:28:27

我有一个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<Country> Countries一样?

部分解决方案:按照类似于 GertArnold 下面答案的行,在我知道他强调的所有_dbContext.Set<T>之前,我在上下文类上实现了此方法以获取特定类型的集合:

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

如何概括对 DbSetup<TEntity> DbContext 成员的访问

通过使用

var dbSet = _dbContext.Set<T>

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

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

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

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

如果要添加盲对象,请使用set.Create()方法创建对象,或者如果您已经使用" new"keyowrd创建了对象,则可以使用(类似于此答案(进行转换

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

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

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

添加对项目的引用,指向System.ComponentModel.Composition 。这将允许访问 MEF 库。

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

.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<IEntity>))]允许 MEF 填充集合。

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

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

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

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

现在它将正常工作。