在Asp.NET MVC中将公共属性/方法继承到多个模型的最佳方法

时间:2015-02-12 13:06:19

标签: c# asp.net-mvc asp.net-mvc-4 inheritance

我数据库中的许多表都有公共字段,我称之为“审计”字段。它们的字段如 - UserUpdateId,UserCreateId,DateUpdated,DateCreated,DateDeleted,RowGUID,以及常见的“评论”表等。在数据库中,它们用于跟踪谁做了什么。此外,通过asp.net MVC 4视图,他们使用常见的显示模板(弹出窗口,鼠标悬停等)向用户显示这些属性。

目前,我将这些属性放入[Serializable()] CommonAttributesBase类中。然后我在所有应该继承这些属性的模型中初始化。不可否认,当我的CommonAttribute类调用存储库并且初始化看起来像是不必要的代码时,这有点笨拙且效率低下。

我很感激有关如何以最佳方式实施此建议的建议。

[Serializable()]
public class CommonAttributesBase
{
    #region Notes
    public Boolean AllowNotes { get; set; }

    [UIHint("NoteIcon")]
    public NoteCollection NoteCollection
    {
        get
        {
            if (!AllowNotes) return null;

            INoteRepository noteRepository = new NoteRepository();
            var notes = noteRepository.FindAssociatedNotes(RowGUID);

            return new NoteCollection { ParentGuid = RowGUID, Notes = notes, AuditString = AuditTrail };
        }

    }

    #region Audit Trail related
    public void SetAuditProperties(Guid rowGuid, Guid insertUserGuid, Guid updateUserGuid, Guid? deleteUserGuid, DateTime updateDate, DateTime insertDate, DateTime? deleteDate)
    {
        RowGUID = rowGuid;
        InsertUserGUID = insertUserGuid;
        UpdateUserGUID = updateUserGuid;
        DeleteUserGUID = deleteUserGuid;
        UpdateDate = updateDate;
        InsertDate = insertDate;
        DeleteDate = deleteDate;
    }

    [UIHint("AuditTrail")]
    public string AuditTrail
    {
        get
        {
            ...code to produce readable user audit strings
            return auditTrail;
        }
    }
...additional methods
}

在另一个班级

public partial class SomeModel
{   

    private CommonAttributesBase _common;
    public CommonAttributesBase Common
    {
        get
        {
            if (_common == null)
            {
                _common = new CommonAttributesBase { AllowNotes = true, AllowAttachments = true, RowGUID = RowGUID };
                _common.SetAuditProperties(RowGUID, InsertUserGUID, UpdateUserGUID, DeleteUserGUID, UpdateDate, InsertDate, DeleteDate);
            }
            return _common;
        }
        set
        {
            _common = value;
        }
    }

...rest of model
}

3 个答案:

答案 0 :(得分:2)

对我来说,我更喜欢为每种类型(审计或注释)使用不同的接口,并使用装饰器来检索这些相关数据,而不是将它们嵌入到公共类中:

public class Note
{
    //Node properties
}

public class AuditTrail
{
    //Audit trail properties
}

public interface IAuditable
{
    AuditTrail AuditTrail { get; set; }
}

public interface IHaveNotes
{
    IList<Note> Notes { get; set; }
}

public class SomeModel : IAuditable, IHaveNotes
{
    public IList<Note> Notes { get; set; }
    public AuditTrail AuditTrail { get; set; }

    public SomeModel()
    {
        Notes = new List<Note>();
    }
}

public class AuditRepository : IRepository<T> where T : IAuditable
{
    private IRepository<T> _decorated;
    public AuditRepository(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Find(int id)
    {
        var model = _decorated.Find(id);
        model.Audit = //Access database to get audit

        return model;
    }

    //Other methods
}

public class NoteRepository : IRepository<T>  where T : IHaveNotes
{   
    private IRepository<T> _decorated;
    public NoteRepository(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Find(int id)
    {
        var model = _decorated.Find(id);
        model.Notes = //Access database to get notes

        return model;
    }

    //Other methods
}

优点是客户端将能够选择是否加载审计/注释,审计和注释的逻辑也与主实体存储库分开。

答案 1 :(得分:2)

你所做的基本上是作曲。正如其他人所说,有很多方法可以完成你所寻找的,有些方法比其他方法更好,但每种方法都取决于你的应用程序的需求,只有你可以与之交谈。

<强>组合物

组合涉及具有其他对象的对象。例如,如果您打算为汽车建模,可能会有以下内容:

public class Car
{
    public Engine Engine { get; set; }
}

public class Engine
{
    public int Horsepower { get; set; }
}

这种方法的好处是,您的Car通过Horsepower获得Engine属性,但没有继承链。换句话说,您的Car类可以从另一个类继承而不影响此属性或类似属性。这种方法的问题在于你必须涉及一个单独的对象,这通常不会太麻烦,但是当绑定到数据库时,你现在正在谈论有一个外键到另一个表,你必须加入才能获得所有课程。属性。

实体框架允许您通过使用它所调用的内容来减轻这种影响&#34;复杂类型&#34;。

[ComplexType]
public class Engine
{
    ...
}

复杂类型的属性映射到主类的表,因此不涉及连接。然而,正因为如此,复杂类型具有某些局限性。也就是说,它们不能包含导航属性 - 仅包含标量属性。此外,您需要注意实例化复杂类型,否则您可能会遇到问题。例如,模型绑定器不验证任何空的导航属性,但如果您的复杂类型上有属性(这会导致主类的属性不可为空),并且您在复杂类型属性为null时保存主类,您将从数据库中获得插入错误。为了安全起见,你应该总是这样做:

public class Car
{
    public Car()
    {
        Engine = new Engine();
    }
}

或者,

public class Car
{
    private Engine engine;
    public Engine Engine
    {
        get
        {
            if (engine == null)
            {
                engine = new Engine();
            }
            return engine;
        }
        set { engine = value; }
    }
}

<强>继承

继承涉及从基类派生您的类,从而获取该基类的所有成员。这是最直接的方法,也是最有限的方法。这主要是因为所有.NET系列语言都只允许单继承。例如:

public class Flyer
{
    public int WingSpan { get; set; }
}

public class Walker
{
    public int NumberOfLegs { get; set; }
}

public class Swimmer
{
    public bool HasFlippers { get; set; }
}

public class Duck : ????
{
    ...
}

这有点人为,但重点是DuckFlyerWalkerSwimmer,但它只能从一个继承这些。在仅允许单继承的语言中使用继承时必须要小心,以确保您继承的内容是可能的最完整的基类,因为您不能轻易地与此分开。

<强>接口

使用接口有点类似于继承,但是可以实现多个接口的额外好处。但是,缺点是实际的实现不是继承的。在前面的鸭子示例中,您可以这样做:

public class Duck : IFlyer, IWalker, ISwimmer

但是,您将负责手动在Duck类上实现这些接口的所有成员,而继承它们只是通过基类。

接口和.NET能够扩展事物的巧妙技巧是你可以进行接口扩展。这些不会帮助你处理属性等事情,但是你可以放弃一些课程的实施。方法。例如:

public static class IFlyerExtensions
{
    public static string Fly(this IFlyer flyer)
    {
        return "I'm flying";
    }
}

然后,

var duck = new Duck();
Console.WriteLine(duck.Fly());

只需实施IFlyerDuck即可获得Fly方法,因为IFlyer已使用该方法进行了扩展。同样,这并不能解决所有问题,但它确实允许接口更灵活。

答案 2 :(得分:1)

你可以通过几种不同的方式做这样的事情。我个人没有与EF合作,所以我不能谈论它将如何运作。

选项一:接口

public interface IAuditable
{
  Guid RowGUID { get; }
  Guid InsertUserGUID { get; }
  Guid  UpdateUserGUID { get; }
  Guid DeleteUserGUID { get; }
  DateTime UpdateDate { get; }
  DateTime InsertDate { get; }
  DateTime DeleteDate { get; }
}

当然,如果您的用例需要,可以将其更改为getset

选项二:超级/基类

public abstract class AuditableBase 
{
     // Feel free to modify the access modifiers of the get/set and even the properties themselves to fit your use case.
     public Guid RowGUID { get; set;}
     public Guid InsertUserGUID { get; set;}
     public Guid UpdateUserGUID { get; set;}
     public Guid DeleteUserGUID { get; set;}
     public DateTime UpdateDate { get; set;}
     public DateTime InsertDate { get; set;}
     public DateTime DeleteDate { get; set;}

     // Don't forget a protected constructor if you need it!
}

public class SomeModel : AuditableBase { } // This has all of the properties and methods of the AuditableBase class.

这个问题是如果你不能继承多个基类,但你可以实现多个接口。