简体   繁体   English

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

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

I am trying to become a good programming citizen through learning more about Dependency Injection / IoC and other best-practices methods. 我想通过学习更多关于依赖注入/ IoC和其他最佳实践方法来成为一名优秀的编程公民。 For this I have a project where I am trying to make the right choices and design everything in the "proper" way, whatever that might mean. 为此,我有一个项目,我正在努力做出正确的选择,并以“正确”的方式设计一切,无论这意味着什么。 Ninject, Moq and ASP.NET MVC help with testability and getting the app "out the door". Ninject,Moq和ASP.NET MVC帮助实现可测试性并将应用程序“推出门外”。

However, I have a question about how to design an entity base class for the objects that my application consists of. 但是,我有一个关于如何为我的应用程序所包含的对象设计实体基类的问题。 I have a simple class library which the web app is built on top of. 我有一个简单的类库,Web应用程序是建立在它之上的。 This library exposes a IRepository interface, and the default implementation (the one that the app uses) uses Linq-to-SQL under the covers (the DataContext etc. is not exposed to the web app) and simply contains ways to fetch these entities. 该库公开了一个IRepository接口,默认实现(应用程序使用的接口)使用了Linq-to-SQL(DataContext等没有公开给Web应用程序),只是包含获取这些实体的方法。 The repository basically looks like this (simplified): 存储库基本上看起来像这样(简化):

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

The default implementation uses the GetTable() method on the DataContext to provide the correct data. 默认实现使用DataContext上的GetTable()方法来提供正确的数据。

However, this requires that the base class "Entity" has some features. 但是,这要求基类“Entity”具有一些功能。 It is easy enough to get my objects to inherit from it by creating a partial class of the same name as the mapped object that Linq-to-SQL gives me, but what is the "correct" way to do this? 通过创建一个与Linq-to-SQL给我的映射对象同名的部分类,很容易让我的对象继承它,但是这样做的“正确”方法是什么?

For instance, the interface above has a function for getting an Entity by it's id - all the different kinds of classes that derives from entity does indeed have an "id" field of type int (mapped from the primary key of their respective tables), but how can I specify this in a way that lets me implement IRepository like this? 例如,上面的接口具有通过它的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();
    }
}

I am doing this from memory on a compiler-less PC so forgive any errors, you hopefully get my intention. 我在无编译器的PC上从内存中执行此操作,因此原谅任何错误,您希望得到我的意图。

The trick here is of course that for this to compile, it has to be known for sure that Entity promises that everyone that derives from it has an id field. 这里的诀窍当然是为了编译它,必须确定Entity承诺从它派生的每个人都有一个id字段。

And I can make an abstract field, or a normal field that is "hidden" by the id field that Linq-to-SQL sticks in the generated classes. 我可以创建一个抽象字段,或者由Linq-to-SQL粘贴在生成的类中的id字段“隐藏”的普通字段。

But this all feels kind of like a cheat, and even gives compiler warnings. 但这一切都像是作弊,甚至给编译器警告。

Should "Entity" really be "IEntity" (an interface instead), and I should try to make the id field be defined in a way that Linq-to-SQL will fulfill? 应该“实体”真的是“IEntity”(而不是接口),我应该尝试以Linq-to-SQL将实现的方式定义id字段? This would also make it easy to specify other interfaces that Entity-implementors need to implement. 这也可以很容易地指定实体实现者需要实现的其他接口。

Or should "Entity" be an abstract base class with an abstract id field, and should it also implement needed interfaces in an abstract way for others to override? 或者“实体”应该是一个带有抽象id字段的抽象基类,它是否应该以抽象方式实现所需的接口以供其他人覆盖?

I don't know C# well enough to see an elegant solution to this, so I would love to hear from more experienced system designers with some base class experience weigh in on this. 我不太清楚C#是否能够找到优雅的解决方案,所以我很乐意听到更有经验的系统设计师的一些基础经验。

Thanks! 谢谢!

EDIT April 10th: 编辑4月10日:

I see I left out something important here. 我看到我在这里遗漏了一些重要的东西。 My IRepository has two concrete implementations - one that is the "real" one using LINQ to SQL, the other one is a InMemoryRepository that just uses a List for now, which is used for unit testing etc. 我的IRepository有两个具体的实现 - 一个是使用LINQ to SQL的“真实”实现,另一个是现在只使用List的InMemoryRepository,用于单元测试等。

The two solutions added will both work for one of these situations, not for the other. 添加的两个解决方案都适用于其中一种情况,而不适用于另一种情况。 So if possible, I would need a way to define that everything that inherits from Entity will have the "id" property, and that this will work with the LINQ to SQL DataContext without "cheating". 因此,如果可能的话,我需要一种方法来定义从Entity继承的所有内容都将具有“id”属性,并且这将与LINQ to SQL DataContext一起使用而不会“作弊”。

If you use "Id" as a primary keys in all tables so all the generated classes would have a public property like: 如果在所有表中使用“Id”作为主键,则所有生成的类都将具有如下公共属性:

public int Id {}

Then create an interface 然后创建一个接口

public interface IIdentifiable {
    Id { get; }
}

Then the most tedious part :(, for all entities create a partial class and make it implement IIdentifiable. 然后是最乏味的部分:(,为所有实体创建一个部分类,并使其实现IIdentifiable。

The repository class can then look like: 然后,存储库类可以如下所示:

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

And the following code will work: 以下代码将起作用:

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

If you do not use the generated classes and make your own, is even simpler from this point of view. 如果你不使用生成的类并自己制作,从这个角度来看甚至更简单。

EDIT: 编辑:
Instead of ent => ent.Id == id use ent => ent.Id.Equals(id). 而不是ent => ent.Id == id使用ent => ent.Id.Equals(id)。 Just tested, the following is a complete working example: 刚刚测试过,以下是一个完整的工作示例:

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

The "FindAll" is a bad idea, and will force it to fetch all data. “FindAll”是一个坏主意,并将强制它获取所有数据。 Returning IQueryable<T> would be better, but still not ideal, IMO. 返回IQueryable<T>会更好,但仍然不理想,IMO。

Re finding by ID - see here for a LINQ-to-SQL answer ; 通过ID重新查找 - 请参阅此处获取LINQ-to-SQL答案 ; this uses LINQ-to-SQL's meta-model to locate the primary key, so that you don't have to. 这使用LINQ-to-SQL的元模型来定位主键,因此您不必这样做。 It also works for both attributed and resource-based models. 它也适用于归因模型和基于资源的模型。

I wouldn't force a base class. 我不会强迫基类。 That wasn't very popular with Entity Framework, and it isn't likely to get any more popular... 这在实体框架中并不是很受欢迎,并且它不太可能变得更受欢迎......

I have a similar project that i based heavily on Rob Conery's Storefront idea where like the suggestions above i use an interface to dictate the int ID fields: 我有一个类似的项目,我很大程度上基于Rob Conery的店面想法,就像上面的建议我使用一个界面来指定int ID字段:

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

But rather than forcing my repos to implement an IRepository i opted for extension methods for the IQueryable<T> type. 但是,而不是强迫我的repos实现一个IRepository我选择了IQueryable<T>类型的扩展方法。 So i use filters: 所以我使用过滤器:

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

.. this way i can have a IQueryable<T> type come back from my repo and simply use .WithID(x) to pull the single object. ..这样我就可以从我的.WithID(x)返回IQueryable<T>类型,只需使用.WithID(x)来拉动单个对象。

I too use this code with LinqToSql objects further into the stack so the equality comparers do convert to SQL. 我也将此代码与LinqToSql对象一起使用到堆栈中,以便相等比较器转换为SQL。

You've got really 2 options for the Id field: 你有Id领域的两个选项:

  1. Use a object to represent the Id in your entity. 使用对象表示实体中的Id。 (This gives you a bit more flexibility in terms of you're primary keys ) (这使您在主键方面具有更大的灵活性)

  2. Simply use an int Id. 只需使用int Id。 ( This makes your entities very easy to work with and becomes your convention for your entities/repositories. (这使您的实体非常容易使用,并成为您的实体/存储库的约定。

Now regarding how to implement your Entity. 现在关于如何实现您的实体。 Your entity class could be abstract. 您的实体类可以是抽象的。 Take a look at Entity base class implementation in Sharp-Architechture or from CommonLibrary.NET or SubSonic 看看Sharp-Architechture中的Entity基类实现,或者来自CommonLibrary.NET或SubSonic

Typically you have audit fields and perhaps Validation methods. 通常,您有审计字段和验证方法。 I'm using the CommonLibrary.NET 我正在使用CommonLibrary.NET

Entity : IEntity 实体:IEntity

  • Id ID
  • CreateDate CREATEDATE
  • UpdateDate 更新日期
  • CreateUser 创建用户
  • UpdateUser UpdateUser两个
  • Validate(...) 验证(...)
  • IsValid 已验证
  • Errors 错误

The IEntity interface is a combination of audit fields ( CreateDate, UpdateUser etc ) and validation methods for the entity. IEntity接口是审计字段(CreateDate,UpdateUser等)和实体验证方法的组合。

Take a look at the following methods that were posted by Denis Troller as an answer to my question : 看看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;
            }

You are doing the same as us. 你和我们一样。 We have a generic Persist interface and two implementations: one with LinqToSql, another for mocking. 我们有一个通用的Persist接口和两个实现:一个用LinqToSql,另一个用于模拟。

We faced the same problem about GetById. 我们遇到了与GetById相同的问题。 Our solution is to have all the entities inherit from one base class. 我们的解决方案是让所有实体都从一个基类继承。 That base class has a virtual method 该基类有一个虚方法

protected object GetId()

To simplify the composite primary key situation, we changed our DB schema so that every table had a primary key column. 为了简化复合主键情况,我们更改了数据库模式,以便每个表都有一个主键列。 The old composite primary key became a unique constraint. 旧的复合主键成为唯一约束。

We also found it's convenient to have a base class because later we realized we needed some other common features cross all entities. 我们还发现有一个基类很方便,因为后来我们意识到我们需要跨所有实体的其他一些共同特征。

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

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