繁体   English   中英

C#中用于存储库模型的实体基类设计

[英]Entity base class design in C# for repository model

我想通过学习更多关于依赖注入/ IoC和其他最佳实践方法来成为一名优秀的编程公民。 为此,我有一个项目,我正在努力做出正确的选择,并以“正确”的方式设计一切,无论这意味着什么。 Ninject,Moq和ASP.NET MVC帮助实现可测试性并将应用程序“推出门外”。

但是,我有一个关于如何为我的应用程序所包含的对象设计实体基类的问题。 我有一个简单的类库,Web应用程序是建立在它之上的。 该库公开了一个IRepository接口,默认实现(应用程序使用的接口)使用了Linq-to-SQL(DataContext等没有公开给Web应用程序),只是包含获取这些实体的方法。 存储库基本上看起来像这样(简化):

public interface IRepository
{
    IEnumerable<T> FindAll<T>() where T : Entity
    T Get<T>(int id) where T : Entity
}

默认实现使用DataContext上的GetTable()方法来提供正确的数据。

但是,这要求基类“Entity”具有一些功能。 通过创建一个与Linq-to-SQL给我的映射对象同名的部分类,很容易让我的对象继承它,但是这样做的“正确”方法是什么?

例如,上面的接口具有通过它的id获取实体的功能 - 从实体派生的所有不同类的类确实具有int类型的“id”字段(从它们各自的表的主键映射),但是我如何以一种允许我像这样实现IRepository的方式来指定它呢?

public class ConcreteRepository : IRepository
{
    private SomeDataContext db = new SomeDataContext();

    public IEnumerable<T> FindAll<T>() where T : Entity
    {
        return db.GetTable<T>().ToList();
    }

    public T Get(int id) where T : Entity
    {
        return db.GetTable<T>().Where(ent => ent.id == id).FirstOrDefault();
    }
}

我在无编译器的PC上从内存中执行此操作,因此原谅任何错误,您希望得到我的意图。

这里的诀窍当然是为了编译它,必须确定Entity承诺从它派生的每个人都有一个id字段。

我可以创建一个抽象字段,或者由Linq-to-SQL粘贴在生成的类中的id字段“隐藏”的普通字段。

但这一切都像是作弊,甚至给编译器警告。

应该“实体”真的是“IEntity”(而不是接口),我应该尝试以Linq-to-SQL将实现的方式定义id字段? 这也可以很容易地指定实体实现者需要实现的其他接口。

或者“实体”应该是一个带有抽象id字段的抽象基类,它是否应该以抽象方式实现所需的接口以供其他人覆盖?

我不太清楚C#是否能够找到优雅的解决方案,所以我很乐意听到更有经验的系统设计师的一些基础经验。

谢谢!

编辑4月10日:

我看到我在这里遗漏了一些重要的东西。 我的IRepository有两个具体的实现 - 一个是使用LINQ to SQL的“真实”实现,另一个是现在只使用List的InMemoryRepository,用于单元测试等。

添加的两个解决方案都适用于其中一种情况,而不适用于另一种情况。 因此,如果可能的话,我需要一种方法来定义从Entity继承的所有内容都将具有“id”属性,并且这将与LINQ to SQL DataContext一起使用而不会“作弊”。

如果在所有表中使用“Id”作为主键,则所有生成的类都将具有如下公共属性:

public int Id {}

然后创建一个接口

public interface IIdentifiable {
    Id { get; }
}

然后是最乏味的部分:(,为所有实体创建一个部分类,并使其实现IIdentifiable。

然后,存储库类可以如下所示:

public class Repository<T> : IRepository where T : IIdentifiable {
}

以下代码将起作用:

db.GetTable<T>().SingleOrDefault(ent => ent.Id.Equals(id));

如果你不使用生成的类并自己制作,从这个角度来看甚至更简单。

编辑:
而不是ent => ent.Id == id使用ent => ent.Id.Equals(id)。 刚刚测试过,以下是一个完整的工作示例:

public interface IIdentifiable {
    int Id { get; }
}

public class Repository<T> where T : class, IIdentifiable {
    TestDataContext dataContext = new TestDataContext();

    public T GetById(int id) {
        T t = dataContext.GetTable<T>().SingleOrDefault(elem => elem.Id.Equals(id));
        return t;
    }
}

public partial class Item : IIdentifiable {
}

class Program {
    static void Main(string[] args) {
        Repository<Item> itemRepository = new Repository<Item>();

        Item item = itemRepository.GetById(1);

        Console.WriteLine(item.Text);
    }
}

“FindAll”是一个坏主意,并将强制它获取所有数据。 返回IQueryable<T>会更好,但仍然不理想,IMO。

通过ID重新查找 - 请参阅此处获取LINQ-to-SQL答案 ; 这使用LINQ-to-SQL的元模型来定位主键,因此您不必这样做。 它也适用于归因模型和基于资源的模型。

我不会强迫基类。 这在实体框架中并不是很受欢迎,并且它不太可能变得更受欢迎......

我有一个类似的项目,我很大程度上基于Rob Conery的店面想法,就像上面的建议我使用一个界面来指定int ID字段:

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

但是,而不是强迫我的repos实现一个IRepository我选择了IQueryable<T>类型的扩展方法。 所以我使用过滤器:

public static class ModelObjectFilters
{
    /// <summary>
    /// Filters the query by ID
    /// </summary>
    /// <typeparam name="T">The IModelObject being queried</typeparam>
    /// <param name="qry">The IQueryable being extended</param>
    /// <param name="ID">The ID to filter by</param>
    /// <returns></returns>
    public static IQueryable<T> WithID<T>(this IQueryable<T> qry,
        int ID) where T : IModelObject
    {
        return qry.Where(result => result.ID == ID);
    }
}

..这样我就可以从我的.WithID(x)返回IQueryable<T>类型,只需使用.WithID(x)来拉动单个对象。

我也将此代码与LinqToSql对象一起使用到堆栈中,以便相等比较器转换为SQL。

你有Id领域的两个选项:

  1. 使用对象表示实体中的Id。 (这使您在主键方面具有更大的灵活性)

  2. 只需使用int Id。 (这使您的实体非常容易使用,并成为您的实体/存储库的约定。

现在关于如何实现您的实体。 您的实体类可以是抽象的。 看看Sharp-Architechture中的Entity基类实现,或者来自CommonLibrary.NET或SubSonic

通常,您有审计字段和验证方法。 我正在使用CommonLibrary.NET

实体:IEntity

  • ID
  • CREATEDATE
  • 更新日期
  • 创建用户
  • UpdateUser两个
  • 验证(...)
  • 已验证
  • 错误

IEntity接口是审计字段(CreateDate,UpdateUser等)和实体验证方法的组合。

看看Denis Troller发布的以下方法作为我的问题的答案:

public virtual T GetById(short id)
            {
                var itemParameter = Expression.Parameter(typeof(T), "item");
                var whereExpression = Expression.Lambda<Func<T, bool>>
                    (
                    Expression.Equal(
                        Expression.Property(
                            itemParameter,
                            GetPrimaryKeyName<T>()
                            ),
                        Expression.Constant(id)
                        ),
                    new[] { itemParameter }
                    );
                var table = DB.GetTable<T>();
                return table.Where(whereExpression).Single();
            }


public string GetPrimaryKeyName<T>()
            {
                var type = Mapping.GetMetaType(typeof(T));

                var PK = (from m in type.DataMembers
                          where m.IsPrimaryKey
                          select m).Single();
                return PK.Name;
            }

你和我们一样。 我们有一个通用的Persist接口和两个实现:一个用LinqToSql,另一个用于模拟。

我们遇到了与GetById相同的问题。 我们的解决方案是让所有实体都从一个基类继承。 该基类有一个虚方法

protected object GetId()

为了简化复合主键情况,我们更改了数据库模式,以便每个表都有一个主键列。 旧的复合主键成为唯一约束。

我们还发现有一个基类很方便,因为后来我们意识到我们需要跨所有实体的其他一些共同特征。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM