如何概括对DbContext的DbSet <tentity>成员的访问?</tentity>

时间:2012-03-21 17:09:43

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

我有一个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

PARTIAL SOLUTION:在下面与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));
}

3 个答案:

答案 0 :(得分:9)

使用

可以进行一些概括
var dbSet = _dbContext.Set<T>

并将您的大多数方法放在带有泛型类型参数的方法中。

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

答案 1 :(得分:3)

要添加Gert Arnold的答案,我想要注意dbContext上还有另一个方法重载,它从类型对象返回一般DbSet:

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

如果要添加盲对象,则使用set.Create()方法创建对象,或者如果已经使用“new”keyowrd创建了对象,则可以使用(类似于this answer

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

答案 2 :(得分:3)

我一直在寻找这个问题的答案,我发现使用Managed Extensibility Framework很容易。在这篇文章的底部有一个更快的方法,但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...
}

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

现在我们已将所有实体“制定战略”,我们可以使用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属性都称为“part”。拼图的最后一块是组成这些部分。将以下内容添加到上下文的构造函数中:

// 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&lt;&gt; s:

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

现在它可以正常工作。