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

时间:2009-04-09 11:21:22

标签: c# linq-to-sql oop

我正在努力通过学习更多关于依赖注入/ 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一起使用而不会“作弊”。

6 个答案:

答案 0 :(得分:5)

如果您在所有表中使用“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 =&gt; ent.Id == id使用ent =&gt; 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);
    }
}

答案 1 :(得分:3)

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

按ID查找 - 请参阅here for a LINQ-to-SQL answer;这使用LINQ-to-SQL的元模型来定位主键,因此您不必这样做。它也适用于归因模型和基于资源的模型。

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

答案 2 :(得分:1)

我有一个类似的项目,我非常依赖Rob Conery's Storefront想法,就像上面的建议一样,我使用界面来指示int ID字段:

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

但是,而不是强迫我的回购实施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);
    }
}

..通过这种方式,我可以从我的仓库返回IQueryable<T>类型,只需使用.WithID(x)来拉取单个对象。

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

答案 3 :(得分:0)

请查看Denis Troller发布的以下方法作为question的答案:

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

答案 4 :(得分:0)

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

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

protected object GetId()

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

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

答案 5 :(得分:0)

你真的有两个Id字段选项:

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

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

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

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

    实体:IEntity

    • 编号
    • CREATEDATE
    • UpdateDate
    • CREATEUSER
    • UpdateUser两个
    • 验证(...)
    • 的IsValid
    • 错误

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