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

C# 在Asp.NET MVC中将常用属性/方法继承到多个模型中的最佳方法,c#,asp.net-mvc,asp.net-mvc-4,inheritance,C#,Asp.net Mvc,Asp.net Mvc 4,Inheritance,我数据库中的许多表都有公共字段,我称之为“审计”字段。它们包括-UserUpdateId、UserCreateId、DateUpdated、DateCreated、DateDeleted、RowGUID等字段,以及数据库中常见的“Comments”表等。它们用于跟踪谁在何时做了什么。此外,通过asp.net MVC 4视图,它们使用常见的显示模板(弹出窗口、鼠标悬停等)向用户显示这些属性 目前,我将这些属性放入[Serializable()]CommonAttributesBase类中。然后在所

我数据库中的许多表都有公共字段,我称之为“审计”字段。它们包括-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
}

有两种不同的方法可以做这样的事情。我个人还没有和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; }
}
当然,如果您的用例需要,您可以将其更改为
get
set

选项二:超级/基类

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.

问题在于,如果您不能继承多个基类,但可以实现多个接口。

对于我来说,我更喜欢为每种类型(审计或注释)使用不同的接口,并使用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
}
公共课堂笔记
{
//节点属性
}
公共类审计线索
{
//审计跟踪属性
}
公共接口IAuditable
{
AuditTrail AuditTrail{get;set;}
}
公共接口IHaveNotes
{
IList注释{get;set;}
}
公共类模型:IAuditable、IHaveNotes
{
公共IList注释{get;set;}
公共AuditTrail AuditTrail{get;set;}
公共模型()
{
注释=新列表();
}
}
公共类AuditRepository:IRepository,其中T:iAuditTable
{
私人住宅;
公共审核存储库(IRepository)
{
_装饰的=装饰的;
}
公共找不到(内部id)
{
var模型=_.Find(id);
model.Audit=//访问数据库以获取审核
收益模型;
}
//其他方法
}
公共类NoteRepository:IRepository,其中T:IHaveNotes
{   
私人住宅;
公共记事本存储库(IRepository)
{
_装饰的=装饰的;
}
公共找不到(内部id)
{
var模型=_.Find(id);
model.Notes=//访问数据库以获取注释
收益模型;
}
//其他方法
}

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

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

作文

合成涉及具有其他对象的对象。例如,如果您要为一辆汽车建模,您可能会遇到以下情况:

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

public class Engine
{
    public int Horsepower { get; set; }
}
public class Car
{
    public Car()
    {
        Engine = new Engine();
    }
}
这种方法的好处是,您的
汽车
最终通过
引擎
拥有
马力
属性,但没有继承链。换句话说,您的
Car
类可以自由地从另一个类继承,而不会影响此属性或类似属性。这种方法的问题在于,您必须涉及一个单独的对象,这在通常情况下并不太麻烦,但是当绑定到数据库时,您现在谈论的是将一个外键连接到另一个表,您必须连接该表才能获得所有类的属性

实体框架允许您通过使用它所称的“复杂类型”在某种程度上减轻这种影响

复杂类型的属性映射到主类的表上,因此不涉及连接。然而,正因为如此,复杂类型有一定的局限性。也就是说,它们不能包含导航属性——只能包含标量属性。此外,您需要注意实例化复杂类型,否则可能会遇到问题。例如,modelbinder不会验证任何为null的导航属性,但是如果您的复杂类型上有一个必需的属性(这会导致主类的表上有一个不可为null的属性),并且在复杂类型属性为null时保存主类,则会从数据库中得到一个插入错误。为了安全起见,您应该始终执行以下操作:

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

public class Engine
{
    public int Horsepower { get; set; }
}
public class Car
{
    public Car()
    {
        Engine = new Engine();
    }
}
或者

继承

继承涉及从基类派生类,从而获得该基类的所有成员。这是最直接的方法,但也是最有限的。这主要是因为所有.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 : ????
{
    ...
}
public static class IFlyerExtensions
{
    public static string Fly(this IFlyer flyer)
    {
        return "I'm flying";
    }
}
这有点做作,但关键是
Duck
都是
Flyer
Walker
swimer
,但它只能继承其中一个。在只允许单一继承的语言中使用继承时,必须小心,以确保继承的是可能的最完整的基类,因为您将无法轻易偏离此基类

接口

使用接口有点类似于继承,但还有一个额外的好处,就是您可以实现多个接口。然而,缺点是实际实现不是继承的。在前面的鸭子示例中,您可以执行以下操作:

public class Duck : IFlyer, IWalker, ISwimmer
但是,您将负责在
Duck
类上实现这些接口的所有成员
var duck = new Duck();
Console.WriteLine(duck.Fly());