EF是否应该封装在基类中?

4

我是EF的新手,但我已经编程21年了。我喜欢让事情保持DRY和通用,但我刚刚做的事情有些不对劲,但我无法具体指出问题所在。

我看到EF的每个示例都需要开发人员为每个POCO类创建4个单独的CRUD方法。 因此,我就想着不必这样做,以下是我的解决方案:

模型:

  public class Model1 : DbContext
  {
    public Model1()
        : base("name=Model1")
    {
    }

     public virtual DbSet<Member> Members { get; set; }
  }

所有业务层的基类:

using System.Data.Entity;
using System.Reflection;

namespace biz
{
  public abstract class EFObject<T> where T : EFObject<T>
  {
    public int Id { get; set; }

    internal static readonly string DbSetProperyName = typeof(T).Name + "s";

    public static EFCollection<T> Collection
    {
      get
      {
        using (var db = new Model1())
        {
          PropertyInfo p = db.GetType().GetProperty(DbSetProperyName);
          DbSet<T> collection = (DbSet<T>)p.GetValue(db);
          return new EFCollection<T>(collection);
        }
      }
    }

    public void Insert()
    {
      using (var db = new Model1())
      {
        PropertyInfo p = db.GetType().GetProperty(DbSetProperyName);
        DbSet<T> collection = (DbSet<T>)p.GetValue(db);
        collection.Add((T)this);
        db.SaveChanges();
      }
    }

    public void Save()
    {
      if (Id == 0)
        Insert();
      else
        Update();
    }

    public void Update()
    {
      using (var db = new Model1())
      {
        PropertyInfo p = db.GetType().GetProperty(DbSetProperyName);
        DbSet<T> collection = (DbSet<T>)p.GetValue(db);
        T dbItem = collection.Find(Id);
        foreach (PropertyInfo pi in typeof(T).GetProperties())
        {
          pi.SetValue(dbItem, pi.GetValue(this));
        }
        db.SaveChanges();
      }
    }
  }
}

通用集合类:

using System.Collections.Generic;

namespace biz
{
  public class EFCollection<T> : List<T> where T : EFObject<T>
  {
    public EFCollection()
    {
    }

    public EFCollection(IEnumerable<T> collection)
    {
      AddRange(collection);
    }

    public void Save()
    {
      foreach (T item in this)
        item.Save();
    }
  }
}

中间层类示例:

namespace biz
{
  public class Member : EFObject<Member>
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Client[] Clients;
    public Good[] Goods;
    public decimal Percentage;
  }
}

使用方法:

  var member = new biz.Member() { FirstName = "Brad", LastName = "Pitt", Percentage = 1 };//
  member.Save();
  member = biz.Member.Collection.Find(o=>o.Id == member.Id);
  member.FirstName = "Cherry";
  member.Save();

这个使用代码是有效的,但我想知道这种方法可能会遇到什么问题?

有一件事让我感到不舒服,可能是因为我对EF还不够了解。在我的更新场景中,我 1) 使用一个会话从集合中获取一个对象,2) 断开连接,3) 更新对象的属性,3) 开始一个新会话,4) 通过主键从数据库中找到匹配的对象(它不再是同一个对象!),5) 通过反射更新它,然后 6) 保存更改。因此涉及两个对象而不是一个对象和反射。我认为我必须“放手”保持原始对象,一旦我得到它,但我不知道如何修复这个问题。


你还通过继承直接将实体与Entity Framework绑定在一起。 - user47589
根据我的经验,你的EFObject是不必要的。我认为这篇文章会对你有所帮助:http://rob.conery.io/2014/03/04/repositories-and-unitofwork-are-not-a-good-idea/ - Rob Davis
使用这种模式会丢弃 EF 提供的很多潜力,就像 @GertArnold 所指出的那样。你可能会考虑使用 Dapper 代替。Dapper 没有 EF 具备的所有功能(但您不使用或无法使用),它可能更适合。 - user47589
1
我投票将此问题关闭,因为它应该在codereview.stackexchange.com上发布。 - Jim G.
@JimG.,这不是代码审查。我不是在问“帮我改进这段代码”。我包含了代码,因为程序员想要看到/以代码的方式思考,而不是使用他们的想象力。问题的关键部分不是代码;无论如何编码,都是关于方法的问题。 - toddmo
显示剩余2条评论
2个回答

1

Daffy,OP没有提出通用存储库的建议。 - Gert Arnold
@GertArnold 我可能误解了,但从OP提供的代码示例来看,他肯定是朝着那个方向前进的。 - CShark
1
@toddmo,我引用了相关SO答案中的一段评论:“你不仅仅是在把一个经过充分测试的知名代码库(Entity Framework)封装到一个功能更少的接口中,而且还在人为地限制使用者的功能,换来的收益微乎其微。” - CShark
如果您感兴趣的话,我已经添加了我的解决方案。我相信我已经解决了通用存储库反模式周围的问题。哈 :) - toddmo

0

当你将核心基类与EF(或任何持久化工具)联系在一起时,会存在一定的限制。业务层应该是与持久化无关的。所以,EF甚至不应该从业务或数据项目中引用!

这是我最终做出的决定。我可以从我的基类DatabaseObject获得CRUD方法的同样好处,而且我可以使用DI来替换持久化层。我的EF“插件”dll可以看到业务和数据层。它通过后构建命令部署在bin中。

EF implements IPersistenceProvider interface

PersistenceProvider.cs

using Atlas.Data.Kernel;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
using System;
using Atlas.Core.Kernel.Extensions;
using System.ComponentModel.DataAnnotations.Schema;

namespace Atlas.Data.EntityFramework.Kernel
{
  public class PersistenceProvider<T> : IPersistenceProvider<T> where T : DatabaseObject<T>
  {
    public static readonly PersistenceProvider<T> Current = new PersistenceProvider<T>();
    public static readonly string DbSetProperyName = typeof(T).Pluralize();
    public static readonly PropertyInfo DbSetProperyInfo = typeof(DatabaseContext).GetProperty(DbSetProperyName);

    // C
    public void Insert(T item)
    {
      DatabaseOperation((databaseContext, collection) =>
      {
        collection.Add(item);
      },
      item.Inserting,
      item.Inserted
      );
    }

    // R
    public IEnumerable<T> Select(Func<T, bool> predicate = null)
    {
      using (var databaseContext = new DatabaseContext())
      {
        DbSet<T> collection = (DbSet<T>)DbSetProperyInfo.GetValue(databaseContext);
        return predicate != null ? collection.Where(predicate).ToList() : collection.ToList();
      }
    }

    // U
    public void Update(T item)
    {
      DatabaseOperation((databaseContext, collection) =>
      {
        collection.Attach(item);
        MarkModified(databaseContext, item);
      },
      item.Updating,
      item.Updated
      );
    }

    // D
    public void Delete(T item)
    {
      DatabaseOperation((databaseContext, collection) =>
      {
        collection.Attach(item);
        collection.Remove(item);
      },
      item.Deleting,
      item.Deleted
      );
    }

    private void MarkModified(DatabaseContext databaseContext, DatabaseObject<T> efObject)
    {
      databaseContext.Entry(efObject).State = efObject.Id != null ? EntityState.Modified : EntityState.Added;
      foreach (var pi in efObject.GetType().GetProperties().Where(pi => !pi.GetCustomAttributes(typeof(NotMappedAttribute), false).Any() && pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericArguments()[0].IsClass))
      {
        var col = (IEnumerable<T>)pi.GetValue(efObject);
        if (col != null)
          foreach (DatabaseObject<T> item in col)
            MarkModified(databaseContext, item);
      }
    }

    private DatabaseContext databaseContext = null;
    private void DatabaseOperation(Action<DatabaseContext, DbSet<T>> action, Action executing, Action executed)
    {
      bool outerOperation = databaseContext == null;
      try
      {
        if (outerOperation)
          databaseContext = new DatabaseContext();
        executing();
        DbSet<T> collection = (DbSet<T>)DbSetProperyInfo.GetValue(databaseContext);
        action(databaseContext, collection);
        executed();
        databaseContext.SaveChanges();
      }
      finally
      {
        if (outerOperation)
        {
          databaseContext.Dispose();
          databaseContext = null;
        }
      }
    }

  }
}

DatabaseObject.cs

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Configuration;
using System.Linq;
using System.Web;

namespace Atlas.Data.Kernel
{
  public class DatabaseObject<T> where T : DatabaseObject<T>
  {

    #region Constructors
    public DatabaseObject()
    {
      Id = Guid.NewGuid();
    } 
    #endregion

    #region Fields

    [Key]
    [Column(Order = 0)]
    public Guid Id { get; set; }

    #endregion

    // C
    public virtual void Insert()
    {
      PersistenceProvider.Insert((T)this);
    }

    // R
    public static T SingleOrDefault(Guid Id)
    {
      return SingleOrDefault(o => o.Id == Id);
    }

    public static T SingleOrDefault(Func<T, bool> predicate)
    {
      return PersistenceProvider.Select(predicate).SingleOrDefault();
    }

    public static IEnumerable<T> Select(Func<T, bool> predicate = null)
    {
      return PersistenceProvider.Select(predicate);
    }

    // U
    public virtual void Update()
    {
      PersistenceProvider.Update((T)this);
    }

    // D
    public virtual void Delete()
    {
      PersistenceProvider.Delete((T)this);
    }


    #region Callbacks
    public virtual void Deleting() { }
    public virtual void Deleted() { }
    public virtual void Inserting() { }
    public virtual void Inserted() { }
    public virtual void Updating() { }
    public virtual void Updated() { }
    #endregion

    #region Static Properties
    private static IPersistenceProvider<T> persistenceProvider;
    [Dependency]
    public static IPersistenceProvider<T> PersistenceProvider
    {
      get
      {
        if(persistenceProvider == null)
        {
          var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = HttpContext.Current.Server.MapPath("~/bin/Atlas.Data.Kernel.dll.config") };
          Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
          var unitySection = (UnityConfigurationSection)configuration.GetSection("unity");

          var container = new UnityContainer().LoadConfiguration(unitySection);
          persistenceProvider = container.Resolve<IPersistenceProvider<T>>();
        }
        return persistenceProvider;
      }
      set => persistenceProvider = value;
    }
    #endregion
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接