C#for repository模型中的实体基类设计

C#for repository模型中的实体基类设计,c#,linq-to-sql,oop,C#,Linq To Sql,Oop,通过学习更多关于依赖注入/IoC和其他最佳实践方法的知识,我正在努力成为一名优秀的编程公民。为此,我有一个项目,我试图做出正确的选择,并以“适当”的方式设计一切,不管这意味着什么。Ninject、Moq和ASP.NET MVC有助于提高可测试性,让应用程序“走出家门” 但是,我有一个关于如何为我的应用程序所包含的对象设计实体基类的问题。我有一个简单的类库,web应用程序就是建立在这个类库之上的。此库公开了一个IRepository接口,默认实现(应用程序使用的实现)在幕后使用Linq to SQ

通过学习更多关于依赖注入/IoC和其他最佳实践方法的知识,我正在努力成为一名优秀的编程公民。为此,我有一个项目,我试图做出正确的选择,并以“适当”的方式设计一切,不管这意味着什么。Ninject、Moq和ASP.NET MVC有助于提高可测试性,让应用程序“走出家门”

但是,我有一个关于如何为我的应用程序所包含的对象设计实体基类的问题。我有一个简单的类库,web应用程序就是建立在这个类库之上的。此库公开了一个IRepository接口,默认实现(应用程序使用的实现)在幕后使用Linq to SQL(DataContext等未向web应用程序公开),并且只包含获取这些实体的方法。存储库基本上如下所示(简化):

公共接口IRepository
{
IEnumerable FindAll(),其中T:Entity
T Get(int-id),其中T:Entity
}
默认实现使用DataContext上的GetTable()方法来提供正确的数据

但是,这要求基类“实体”具有一些特性。通过创建一个与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();
    }
}
公共类存储库:IRepository
{
私有SomeDataContext db=新的SomeDataContext();
public IEnumerable FindAll(),其中T:Entity
{
返回db.GetTable().ToList();
}
公共T Get(int id),其中T:Entity
{
返回db.GetTable();
}
}
我在一台没有编译器的PC上从内存中执行此操作,所以请原谅任何错误,希望您能理解我的意图

这里的诀窍当然是,要编译它,必须确保实体承诺从它派生的每个人都有一个id字段

我可以创建一个抽象字段,或者一个普通字段,它被linqtosql在生成的类中所保留的id字段“隐藏”

但这一切感觉有点像作弊,甚至给了编译器警告

“实体”真的应该是“IEntity”(而是一个接口)吗?我应该尝试以LINQtoSQL将实现的方式定义id字段吗?这还可以方便地指定实体实现者需要实现的其他接口

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

我对C#的了解还不够深入,无法找到一个优雅的解决方案,因此我很想听听更有经验的系统设计人员对这一点的看法,他们具有一些基本类的经验

谢谢

编辑4月10日:

我看我漏掉了一些重要的东西。我的IRepository有两个具体的实现——一个是“真正的”实现,一个使用LINQ to SQL,另一个是InMemoryRepository,目前只使用列表,用于单元测试等

添加的两种解决方案都适用于其中一种情况,而不适用于另一种情况。因此,如果可能的话,我需要一种方法来定义从实体继承的所有内容都将具有“id”属性,并且这将在没有“欺骗”的情况下与LINQ to SQL DataContext一起工作。“FindAll”是一个坏主意,它将强制它获取所有数据。在我看来,返回
IQueryable
会更好,但仍然不理想

通过ID-see重新查找;这使用LINQtoSQL的元模型来定位主键,因此您不必这样做。它也适用于属性模型和基于资源的模型

我不会强迫一个基类。这在Entity Framework中不太流行,而且不太可能变得更流行…

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

public int Id {}
然后创建一个接口

public interface IIdentifiable {
    Id { get; }
}
然后最乏味的部分:(,对于所有实体,创建一个分部类并使其实现IIIdentifiable

然后,repository类可以如下所示:

public class Repository<T> : IRepository where T : IIdentifiable {
}
公共类存储库:IRepository,其中T:IIdentifiable{
}
以下代码将起作用:

db.GetTable<T>().SingleOrDefault(ent => ent.Id.Equals(id));
db.GetTable().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);
    }
}
可识别的公共接口{
int Id{get;}
}
公共类存储库,其中T:class,可识别{
TestDataContext=新的TestDataContext();
公共T GetById(int-id){
T T=dataContext.GetTable().SingleOrDefault(elem=>elem.Id.Equals(Id));
返回t;
}
}
公共部分类项:可标识{
}
班级计划{
静态void Main(字符串[]参数){
Repository itemRepository=新存储库();
Item=itemRepository.GetById(1);
Console.WriteLine(item.Text);
}
}

查看以下方法,这些方法是作为我的问题的答案发布的:

公共虚拟T GetById(短id) { var itemParameter=Expression.Parameter(类型(T),“项目”); var whereExpression=Expression.Lambda ( 表达式。相等( 表达式.属性(
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);
    }
}
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;
            }
protected object GetId()
public interface IModelObject
{
    int ID { get; set; }
}
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);
    }
}