EF应该封装在基类中吗?

时间:2016-05-13 21:58:54

标签: c# entity-framework

我是EF的新手,但我已经编程了21年。我喜欢干燥和通用的东西,但我刚刚做的事情似乎有些不对,但是我不能指责它。

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

Generic Collection类:

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)开始新的会话,3)通过主键从中找到匹配的对象db(它不再是同一个对象!),4)通过反射更新它,然后5)保存更改。所以有两个对象涉及的不是一个和反射。我想我必须“放开”连接以保留原始对象一旦得到它,我不知道如何解决这个问题。

2 个答案:

答案 0 :(得分:1)

答案 1 :(得分: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
  }
}