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

本文关键字:方法 继承 最佳 模型 属性 MVC NET Asp | 更新日期: 2024-04-23 18:23:13

我的数据库中的许多表都有公共字段,我称之为"审计"字段。它们的字段如-UserUpdateId、UserCreateId、DateUpdated、DateCreated、DateDeleted、RowGUID,以及一个通用的"Comments"表等。在数据库中,它们用于跟踪谁在什么时候做了什么。此外,通过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
}

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

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

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
}

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

您所做的基本上是合成。正如其他人所说,有很多方法可以实现你想要的,有些方法比其他方法更好,但每种方法都取决于你的应用程序的需求,只有你才能与之对话

成分

合成涉及对象具有其他对象。例如,如果你要为一辆汽车建模,你可能会有这样的东西:

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

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

实体框架允许您通过使用它所称的"复杂类型"来减轻这种影响。

[ComplexType]
public class Engine
{
    ...
}

复杂类型的属性被映射到主类的表上,因此不涉及联接。然而,正因为如此,复杂类型具有一定的局限性。也就是说,它们不能包含导航属性——只能包含标量属性。此外,您需要注意实例化复杂类型,否则可能会遇到问题。例如,任何为null的导航属性都不会由modelbinder验证,但如果您的复杂类型上有一个必需的属性(这导致主类表上的属性不可为null),并且您在复杂类型属性为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是用该方法扩展的。同样,这并不能解决所有问题,但它确实允许接口更加灵活。

有几种不同的方法可以做到这一点。我个人还没有和英孚合作过,所以我不能谈论它将如何运作。

选项一:接口

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.

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