包装 DbSet<TEntity>使用自定义 DbSet/IDbSet? [英] Wrapping DbSet&lt;TEntity&gt; with a custom DbSet/IDbSet?

查看:30
本文介绍了包装 DbSet<TEntity>使用自定义 DbSet/IDbSet?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我认为这样做有点荒谬,但我团队的其他成员坚持这样做,除了我认为这很愚蠢"之外,我无法提出一个好的论据......

我们要做的是创建一个完全抽象的数据层,然后对该数据层进行各种实现.够简单了吧?进入实体框架 4.1...

我们的最终目标是程序员(我尽力只停留在数据层)永远不想接触具体的类.除了明显需要实例化工厂之外,他们只想在他们的代码中使用接口.

我想实现如下目标:

首先,我们拥有所有接口的Common"库,我们将其称为Common.Data":

公共接口IEntity{int ID { 获取;放;}}公共接口 IUser : IEntity{int AccountID { 获取;放;}字符串 用户名 { 获取;放;}字符串电子邮件地址 { 获取;放;}IAccount 帐户 { 获取;放;}}公共接口 IAccount : IEntity{字符串名字{获取;放;}字符串姓氏 { 获取;放;}DbSet用户{得到;放;}//或 IDbSet或 [IDbSet 实现]?}公共接口 IEntityFactory{DbSet用户{得到;}数据库集<IAccount>帐户{获取;}}

然后我们就有了一个实现库,我们将其称为Something.Data.Imp":

内部类 User : IUser{公共 int ID { 获取;放;}公共字符串用户名 { 获取;放;}公共字符串电子邮件地址 { 获取;放;}公共 IAccount 帐户 { 获取;放;}公共类配置:EntityTypeConfiguration<用户>{公共配置():基础(){...}}}内部类帐户:IAccount{公共 int ID { 获取;放;}公共字符串名字{获取;放;}公共字符串姓氏 { 获取;放;}公共 DbSet用户{得到;放;}//或 IDbSet或 [IDbSet 实现]?公共类配置:EntityTypeConfiguration{公共配置():基础(){...}}}

工厂:

公共类 ImplEntityFactory : IEntityFactory{私有 ImplEntityFactory(字符串连接字符串){this.dataContext = new MyEfDbContext(connectionString);}私有 MyEfDbContext 数据上下文;公共静态 ImplEntityFactory 实例(字符串 connectionString){if(ImplEntityFactory._instance == null)ImplEntityFactory._instance = new ImplEntityFactory(connectionString);返回 ImplEntityFactory._instance;}私有静态 ImplEntityFactory _instance;公共 DbSet用户//OR IDbSet或 [IDbSet 实现]?{得到 { 返回 dataContext.Users;}}公共 DbSet<IAccount>帐户//OR IDbSet或 [IDbSet 实现]?{得到 { 返回 dataContext.Accounts;}}}

上下文:

公共类 MyEfDataContext : DbContext{公共 MyEfDataContext(字符串连接字符串):基地(连接字符串){Database.SetInitializer(null);}protected override void OnModelCreating(DbModelBuilder modelBuilder){modelBuilder.Configurations.Add(new User.Configuration());modelBuilder.Configurations.Add(new Account.Configuration());base.OnModelCreating(modelBuilder);}公共数据库集<用户>用户{得到;放;}公共数据库集<帐户>帐户{获取;放;}}

然后前端程序员会使用它,例如:

公共类 UsingIt{public static void Main(string[] args){IEntityFactory factory = new ImplEntityFactory("SQLConnectionString");IUser 用户 = factory.Users.Find(5);IAccount usersAccount = user.Account;IAccount 帐户 = factory.Accounts.Find(3);Console.Write(account.Users.Count());}}

差不多就是这样......我希望这里的某个人能够为我指明正确的方向,或者帮助我提出一个好的论点,让我可以回击开发团队.我查看了本站点上有关 EF 无法使用接口的其他一些文章和 一个回复 说你不能实现 IDbSet(我觉得有点奇怪,如果你不能实现它,他们为什么要提供它?)但到目前为止无济于事.

在此先感谢您的帮助!J

解决方案

第一个参数是 EF 不适用于接口.DbSet 必须使用真实的实体实现来定义.

第二个参数是您的实体不应包含 DbSet - 即与上下文相关的类,除非您要实现活动记录模式,否则您的实体应该完全没有这种依赖性.即使在这种情况下,您也绝对无法访问另一个实体中不同实体的 DbSet.即使您包装了集合,您仍然离 EF 太近,并且实体永远不会访问其他实体类型的所有实体(不仅是与当前实例相关的实体).

为了说明清楚,EF 中的 DbSet 具有非常特殊的含义——它不是一个集合.它是数据库的入口点(例如,DbSet 上的每个 LINQ 查询都会命中数据库),并且在正常情况下不会暴露在实体上.

第三个参数是您为每个应用程序使用单个上下文 - 每个单例工厂都有一个私有实例.除非您正在执行一些单次运行的批处理应用程序这绝对是错误的.

最后一个论点很实用.您为交付功能而不是在抽象上浪费时间而获得报酬,这不会给您(和您的客户)带来任何商业价值.这不是要证明为什么不应该创建这种抽象.这是关于证明你为什么应该这样做.使用它你会得到什么价值?如果您的同事无法提出具有商业价值的论据,您可以直接去找您的产品经理,让他使用他的权力 - 他掌握着预算.

通常抽象是精心设计的面向对象应用程序的一部分 - 这是正确的.但是:

  • 每个抽象都会使您的应用程序变得更加复杂,并且会增加开发成本和时间
  • 并非每个抽象都会使您的应用程序更好或更易于维护 - 过多的抽象会产生相反的效果
  • 抽象 EF 很难.说您将以可以用另一个实现替换它的方式抽象数据访问是数据访问专家的任务.首先,你必须对许多数据访问技术有很好的经验,才能定义这样的抽象,它适用于所有这些技术(最后你只能说你的抽象适用于你在设计时考虑的技术)).您的抽象只能与 EF DbContext API 一起使用,而不能用于其他任何东西,因为它不是抽象.如果您想构建通用抽象,您应该开始研究存储库模式、工作单元模式和规范模式——但要使它们成为通用的并实现它们需要大量的工作.所需的第一步是在该抽象背后隐藏与数据访问相关的所有内容 - 包括 LINQ!
  • 抽象数据访问以支持多个 API 仅在您现在需要时才有意义.如果您只认为它在未来有用而不是在业务驱动的项目中是完全错误的决策,那么提出该想法的开发人员就没有能力做出业务目标决策.

什么时候做很多"抽象有意义?

  • 您现在有这样的要求 - 将此类决定的责任转移到负责预算/项目范围/要求等的人员身上.
  • 您现在需要抽象来简化设计或解决一些问题
  • 您正在做开源项目或业余爱好项目,您的驱动力不是业务需求,而是项目的纯度和质量
  • 您正在开发平台(长期存在的零售产品,将长期存在)或公共框架 - 这通常会回到第一点,因为此类产品通常具有诸如需求之类的抽象

如果您只处理目标应用程序(主要是按需应用程序或外包解决方案的单一用途应用程序),则应仅在必要时使用抽象.这些应用程序是由成本驱动的——目标是以最低的成本和最短的时间提供可行的解决方案.即使最终的应用程序在内部不是很好,也必须实现这个目标——唯一重要的是应用程序是否满足要求.任何基于如果……发生会怎样"或我们可能需要……"的抽象都会因虚拟(不存在)需求而增加成本,这在 99% 中永远不会发生,并且在大多数情况下与客户的初始合同不计算在内其中此类额外费用.

顺便说一句.这种类型的应用程序是 MS API 和设计器策略的目标 - MS 将制造大量设计器和代码生成器,它们将创建非最佳但廉价且快速的解决方案,这些解决方案可由技能较少的人创建并且非常便宜.最后一个例子是 LightSwitch.

First off, I think this is somewhat ridiculous to do but the other members of my team insist upon it and I can't come up with a good argument against it other than "I think it's dumb"...

What we're trying to do is create a completely abstract data layer and then have various implementations of that data layer. Simple enough, right? Enter Entity Framework 4.1...

Our end goal here is that the programmers (I do my best to stay only on the data layer) never want to have to be exposed to the concrete classes. They only ever want to have to use interfaces in their code, aside from obviously needing to instantiate the factory.

I want to achieve something like the following:

First we have our "Common" library of all of the interfaces, we'll call it "Common.Data":

public interface IEntity
{
    int ID { get; set; }
}

public interface IUser : IEntity
{
    int AccountID { get; set; }
    string Username { get; set; }
    string EmailAddress { get; set; }
    IAccount Account { get; set; }
}

public interface IAccount : IEntity
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]?
}

public interface IEntityFactory
{
    DbSet<IUser> Users { get; }
    DbSet<IAccount> Accounts { get; }
}

From that we then have an implementation library, we'll call it "Something.Data.Imp":

internal class User : IUser
{
    public int ID { get; set; }
    public string Username { get; set; }
    public string EmailAddress { get; set; }
    public IAccount Account { get; set; }

    public class Configuration : EntityTypeConfiguration<User>
    {
        public Configuration() : base()
        {
             ...
        }
    }
}

internal class Account : IAccount
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]?

    public class Configuration : EntityTypeConfiguration<Account>
    {
        public Configuration() : base()
        {
             ...
        }
    }
}

Factory:

public class ImplEntityFactory : IEntityFactory
{
    private ImplEntityFactory(string connectionString) 
    {
        this.dataContext = new MyEfDbContext(connectionString);
    }
    private MyEfDbContext dataContext;

    public static ImplEntityFactory Instance(string connectionString)
    {
        if(ImplEntityFactory._instance == null)
            ImplEntityFactory._instance = new ImplEntityFactory(connectionString);

        return ImplEntityFactory._instance;
    }
    private static ImplEntityFactory _instance;

    public DbSet<IUser> Users // OR IDbSet<IUser> OR [IDbSet implementation]?
    { 
        get { return dataContext.Users; }
    }

    public DbSet<IAccount> Accounts // OR IDbSet<IUser> OR [IDbSet implementation]?
    {
        get { return dataContext.Accounts; }
    }
}

Context:

public class MyEfDataContext : DbContext
{
    public MyEfDataContext(string connectionString)
        : base(connectionString)
    {
        Database.SetInitializer<MyEfDataContext>(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new User.Configuration());
        modelBuilder.Configurations.Add(new Account.Configuration());
        base.OnModelCreating(modelBuilder);
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Account> Accounts { get; set; }
}

Then the front-end programmers would be using it such as:

public class UsingIt
{
    public static void Main(string[] args)
    {
        IEntityFactory factory = new ImplEntityFactory("SQLConnectionString");
        IUser user = factory.Users.Find(5);
        IAccount usersAccount = user.Account;

        IAccount account = factory.Accounts.Find(3);
        Console.Write(account.Users.Count());
    }
}

So that's pretty much it... I'm hoping someone on here might be able to either point me in the right direction or help me out with a good argument that I can fire back at the development team. I've looked at some other articles on this site about EF not being able to work with interfaces and one reply saying that you can't implement IDbSet (which I find kind of curious, why would they provide it if you couldn't implement it?) but so far to no avail.

Thanks in advance for any help! J

解决方案

The first argument is that EF doesn't work with interfaces. DbSet must be defined with a real entity implementation.

The second argument is that your entities should not contain DbSet - that is context related class and your entities should be pure of such dependency unless you are going to implement Active record pattern. Even in such case you will definitely not have access to DbSet of different entity in another entity. Even if you wrap set you are still too close to EF and entity never have property accessing all entities of another entity type (not only those related to current instance).

Just to make it clear DbSet in EF has very special meaning - it is not a collection. It is entry point to database (for example each LINQ query on DbSet hits database) and it is in normal scenarios not exposed on entities.

The third argument is that you are using a single context per application - you have a single private instance per singleton factory. Unless you are doing some single run batch application it is definitely wrong.

The last argument is simply practical. You are paid for delivering features not for wasting time on abstraction which doesn't give you (and your customer) any business value. It is not about proving why you should not create this abstraction. It is about proving why you should do it. What value will you get from using it? If your colleagues are not able to come with arguments which have business value you can simply go to your product manager and let him use his power - he holds the budget.

Generally abstraction is part of well designed object oriented application - that is correct. BUT:

  • Every abstraction will make your application somehow more complex and it will increase cost and time of development
  • Not every abstraction will make your application better or more maintainable - too much abstraction has reverse effect
  • Abstracting EF is hard. Saying that you will abstract data access in the way that you can replace it with another implementation is task for data access gurus. First of all you must have very good experience with many data access technologies to be able to define such abstraction which will work with all of them (and in the end you can only tell that your abstraction works with technologies you thought about when you design that). Your abstraction will work only with EF DbContext API and with nothing else because it is not an abstraction. If you want to build universal abstraction you should start studying Repository pattern, Unit of Work pattern and Specification pattern - but that is a big deal of work to make them and to implement them universal. The first step needed is to hide everything related to data access behind that abstraction - including LINQ!
  • Abstracting data access to support multiple APIs make sense only if you need it now. If you only think that it can be useful in future than it is in business driven projects completely wrong decision and developer who came with that idea is not competent to make business targeting decisions.

When it make sense to do "a lot of" abstraction?

  • You have such requirement now - that moves burden of such decision to person responsible for budget / project scope / requirements etc.
  • You need abstraction now to simplify design or solve some a problem
  • You are doing open source or hobby project and you are not driven by business needs but by purity and quality of your project
  • You are working on platform (long living retail product which will live for a long time) or public framework - this generally returns to the first point because this type of products usually have such abstraction as requirement

If you are working only targeted application (mostly single purpose applications on demand or outsourced solutions) the abstraction should be used only if necessary. These applications are driven by costs - the target is delivering working solution for minimal costs and in the shortest time. This target must be achieved even if resulting application will not be very good internally - the only thing which matters is if application meets requirements. Any abstraction based on "what if ... happens" or "perhaps we will need ..." increases costs by virtual (non existing) requirements which will in 99% never happen and in most cases initial contract with customer didn't count which such additional costs.

Btw. this type of applications is targeted by MS APIs and designer strategy - MS will make a lot of designers and code generators which will create non optimal but cheap and quick solutions which can be created by people with smaller skill set and are very cheap. The last example is LightSwitch.

这篇关于包装 DbSet<TEntity>使用自定义 DbSet/IDbSet?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆