在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
}
对我来说,我更喜欢为每种类型(审计或注释)使用不同的接口,并使用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 : ????
{
...
}
这有点做作,但关键是Duck
是Flyer
、Walker
和Swimmer
的全部,但它只能继承其中的一个。在只允许单一继承的语言中使用继承时,必须小心,以确保从中继承的是尽可能完整的基类,因为您无法轻易偏离这一点。
接口
使用接口在某种程度上类似于继承,但还可以实现多个接口。然而,缺点是实际的实现没有被继承。在前面的鸭子示例中,您可以执行以下操作:
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());
仅仅通过实现IFlyer
,Duck
就得到了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; }
}
当然,如果您的用例需要,您可以将其更改为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.
这样做的问题是,如果不能继承多个基类,但可以实现多个接口。