简体   繁体   English

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

[英]Best method to inherit common properties/methods into several models in Asp.NET MVC

Many tables in my database have common fields which I call 'audit' fields. 我的数据库中的许多表都有公共字段,我称之为“审计”字段。 They fields like - UserUpdateId, UserCreateId, DateUpdated, DateCreated, DateDeleted, RowGUID, as well as a common "Comments" table etc. In the database they are used to track who did what when. 它们的字段如 - UserUpdateId,UserCreateId,DateUpdated,DateCreated,DateDeleted,RowGUID,以及常见的“评论”表等。在数据库中,它们用于跟踪谁做了什么。 Additionally via the asp.net MVC 4 views they display these attributes to the user using common display templates (popup, mouseover etc.). 此外,通过asp.net MVC 4视图,他们使用常见的显示模板(弹出窗口,鼠标悬停等)向用户显示这些属性。

Currently, I put these properties into a [Serializable()] CommonAttributesBase class. 目前,我将这些属性放入[Serializable()] CommonAttributesBase类中。 Which I then initialize in all the models that should inherit those properties. 然后我在所有应该继承这些属性的模型中初始化。 Admittedly this is a little clunky and inefficient as my CommonAttribute class makes calls to the repository and the initialization seems like more code than necessary. 不可否认,当我的CommonAttribute类调用存储库时,这有点笨拙且效率低下,并且初始化似乎比需要的代码更多。

I would appreciate suggestions on how to implement this in the best way. 我将很感激如何以最佳方式实现这一点。

[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
}

In another class 在另一堂课

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
}

For me, I prefer to use different interfaces for each type (audit or note), and use decorator to retrieve those related data, instead of embedding those in the common class: 对我来说,我更喜欢为每种类型(审计或注释)使用不同的接口,并使用装饰器来检索那些相关数据,而不是将它们嵌入到公共类中:

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
}

Advantages is that the client will be able to choose to load audit/note or not, the logic of audit and note are also separated from the main entity repository. 优点是客户端将能够选择加载审计/注释与否,审计和注释的逻辑也与主实体存储库分离。

What you're doing is basically composition. 你正在做的基本上是作曲。 As others have stated, there's many ways to accomplish what you're looking for, some better than others, but each method depends on the needs of your application, of which only you can speak to. 正如其他人所说的那样,有很多方法可以实现您正在寻找的东西,有些方法比其他方法更好,但每种方法都取决于您的应用程序的需求,只有您可以与之交谈。

Composition 组成

Composition involves objects having other objects. 合成涉及具有其他对象的对象。 For example, if you were going to model a car, you might have something like: 例如,如果您要为汽车建模,您可能会遇到以下情况:

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

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

The benefit to this approach is that your Car ends up with a Horsepower property via Engine , but there's no inheritance chain. 这种方法的好处是你的Car最终通过Engine获得了Horsepower属性,但是没有继承链。 In other words, your Car class is free to inherit from another class while not effecting this property or similar properties. 换句话说,您的Car类可以从另一个类继承而不影响此属性或类似属性。 The problems with this approach is that you have to involve a separate object, which in normally is not too troubling, but when combined when tied back to a database, you're now talking about having a foreign key to another table, which you'll have to join in order to get all the class' properties. 这种方法的问题在于你必须涉及一个单独的对象,这通常不会太麻烦,但是当绑定到数据库时,你现在正在谈论有一个外键到另一个表,你'我必须加入以获得所有类的属性。

Entity Framework allows you to somewhat mitigate this effect by using what it calls "complex types". 实体框架允许您通过使用它所谓的“复杂类型”来减轻这种影响。

[ComplexType]
public class Engine
{
    ...
}

The properties of complex types are mapped onto the table for the main class, so no joins are involved. 复杂类型的属性映射到主类的表,因此不涉及连接。 However, because of this, complex types have certain limitations. 然而,正因为如此,复杂类型具有某些局限性。 Namely, they cannot contain navigation properties -- only scalar properties. 也就是说,它们不能包含导航属性 - 仅包含标量属性。 Also, you need to take care to instantiate the complex type or you can run into problems. 此外,您需要注意实例化复杂类型,否则您可能会遇到问题。 For example, any nulled navigation property is not validated by the modelbinder, but if you have a property on your complex type that is required (which results in a property on your main class' table that is non-nullable), and you save your main class while the complex type property is null, you'll get an insertion error from the database. 例如,模型绑定器不验证任何空的导航属性,但如果您的复杂类型上有属性(这会导致主类'表上的属性不可为空),并保存主类,而复杂类型属性为null,您将从数据库中获得插入错误。 To be safe you should always do something like: 为了安全起见,你应该总是这样做:

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

Or, 要么,

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

Inheritance 遗产

Inheritance involves deriving your class from a base class and thereby getting all the members of that base class. 继承涉及从基类派生您的类,从而获取该基类的所有成员。 It's the most straight-forward approach, but also the most limiting. 这是最直接的方法,但也是最有限的方法。 This is mostly because all of the .NET family of languages only allow single inheritance. 这主要是因为所有.NET系列语言都只允许单继承。 For example: 例如:

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 : ????
{
    ...
}

That's a bit contrived, but the point is that Duck is all of a Flyer , Walker and Swimmer , but it can only inherit from one of these. 这有点人为,但重点是DuckFlyerWalkerSwimmer ,但它只能从其中一个继承。 You have to be careful when using inheritance in languages that only allow single inheritance to make sure that what you inherit from is the most complete base class possible, because you won't be able to easily diverge from this. 在仅允许单继承的语言中使用继承时必须要小心,以确保您继承的内容是可能的最完整的基类,因为您将无法轻易地与此分离。

Interfaces 接口

Using interfaces is somewhat similar to inheritance, but with the added benefit that you can implement multiple interfaces. 使用接口有点类似于继承,但是可以实现多个接口的额外好处。 However, the downside is that the actual implementation is not inherited. 但是,缺点是实际的实现不是继承的。 In the previous example with the duck, you could do: 在前面的鸭子示例中,您可以这样做:

public class Duck : IFlyer, IWalker, ISwimmer

However, you would be responsible for implementing all the members of those interfaces on your Duck class manually, whereas with inheritance they just come through the base class. 但是,您将负责手动在Duck类上实现这些接口的所有成员,而继承它们只是通过基类。

A neat trick with interfaces and .NET's ability to extend things is that you can do interface extensions. 接口和.NET 扩展功能的巧妙之处在于您可以进行接口扩展。 These won't help you with things like properties, but you can move off the implementation of some of the class' methods. 这些对于属性之类的东西没有帮助,但是你可以放弃一些类方法的实现。 For example: 例如:

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

Then, 然后,

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

Just by implementing IFlyer , Duck gets a Fly method, because IFlyer was extended with that method. 只是通过实施IFlyerDuck获得了Fly方法,因为IFlyer使用该方法进行了扩展。 Again, this doesn't solve every problem, but it does allow interfaces to be somewhat more flexible. 同样,这并不能解决所有问题,但它确实允许接口更灵活一些。

There's a couple different ways you could do something like this. 你可以通过几种不同的方式做这样的事情。 I personally haven't worked with EF so I can't speak in regards to how it will work. 我个人没有与EF合作,所以我不能谈论它将如何运作。

Option One: Interfaces 选项一:接口

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

Of course you can change it to get and set if your use cases need that. 当然,如果您的用例需要,您可以更改它以getset

Option Two: Super/base classes 选项二:超级/基类

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.

The problem with this is that if you cannot inherit multiple base classes, but you can implement multiple interfaces. 这个问题是如果你不能继承多个基类,但你可以实现多个接口。

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

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