包装 DbSet使用自定义 DbSet/IDbSet?

发布于 2024-12-04 12:10:27 字数 3894 浏览 1 评论 0原文

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

我们正在尝试的要做的就是创建一个完全抽象的数据层,然后对该数据层进行各种实现。很简单,对吧?进入 Entity Framework 4.1...

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

我想实现如下目标:

首先我们有所有接口的“通用”库,我们将其称为“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; }
}

然后我们有一个实现库,我们将其称为“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; }
}

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

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

所以这就是它......我希望这里有人能够指出我正确的方向或帮我提出一个可以解雇的好论点回到开发团队。我查看了本网站上有关 EF 无法使用接口的其他一些文章,并一个回复< /a> 说你无法实现 IDbSet (我觉得有点好奇,如果你无法实现的话他们为什么要提供它?)但到目前为止还没有结果。

预先感谢您的任何帮助! J

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

私藏温柔 2024-12-11 12:10:27

第一个论点是 EF 不适用于接口。 DbSet 必须使用真实的实体实现来定义。

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

只是为了明确 EF 中的 DbSet 具有非常特殊的含义 - 它不是一个集合。它是数据库的入口点(例如 DbSet 上的每个 LINQ 查询都会命中数据库),并且在正常情况下不会在实体上公开。

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

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

一般来说,抽象是设计良好的面向对象应用程序的一部分——这是正确的。但是:

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

什么时候进行“大量”抽象才有意义?

  • 您现在有这样的要求 - 将此类决策的负担转移给负责预算/项目范围/要求等的人员。
  • 您现在需要抽象来简化设计或解决某些问题
  • 您正在做开源或业余爱好项目,并且您不受驱动业务需求,但取决于项目的纯度和质量
  • 您正在平台(长期存在的零售产品)或公共框架上工作 - 这通常会回到第一点,因为此类产品通常具有需求这样的抽象

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文