简体   繁体   中英

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. 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".

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. 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. 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.

However, this requires that the base class "Entity" has some features. 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?

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?

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.

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.

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.

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? 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?

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.

Thanks!

EDIT April 10th:

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.

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".

If you use "Id" as a primary keys in all tables so all the generated classes would have a public property like:

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.

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). 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. Returning IQueryable<T> would be better, but still not ideal, IMO.

Re finding by ID - see here for a LINQ-to-SQL answer ; this uses LINQ-to-SQL's meta-model to locate the primary key, so that you don't have to. 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:

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. 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.

I too use this code with LinqToSql objects further into the stack so the equality comparers do convert to SQL.

You've got really 2 options for the Id field:

  1. Use a object to represent the Id in your entity. (This gives you a bit more flexibility in terms of you're primary keys )

  2. Simply use an 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

Typically you have audit fields and perhaps Validation methods. I'm using the CommonLibrary.NET

Entity : IEntity

  • Id
  • CreateDate
  • UpdateDate
  • CreateUser
  • UpdateUser
  • Validate(...)
  • IsValid
  • Errors

The IEntity interface is a combination of audit fields ( CreateDate, UpdateUser etc ) and validation methods for the entity.

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

You are doing the same as us. We have a generic Persist interface and two implementations: one with LinqToSql, another for mocking.

We faced the same problem about 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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