Imported Upstream version 4.0.0~alpha1

Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
Jo Shields
2015-04-07 09:35:12 +01:00
parent 283343f570
commit 3c1f479b9d
22469 changed files with 2931443 additions and 869343 deletions

View File

@ -0,0 +1,125 @@
using System.Collections.ObjectModel;
using System.Globalization;
using System.Web.Resources;
namespace System.Web.DynamicData.ModelProviders {
/// <summary>
/// Specifies the association cardinality
/// </summary>
public enum AssociationDirection {
/// <summary>
/// 1-1
/// </summary>
OneToOne,
/// <summary>
/// one to many
/// </summary>
OneToMany,
/// <summary>
/// many to one
/// </summary>
ManyToOne,
/// <summary>
/// many to many
/// </summary>
ManyToMany
}
/// <summary>
/// Base provider class for associations between columns
/// Each provider type (e.g. Linq To Sql, Entity Framework, 3rd party) extends this class.
/// </summary>
public abstract class AssociationProvider {
private TableProvider _toTable;
/// <summary>
/// The type of association
/// </summary>
public virtual AssociationDirection Direction { get; protected set; }
/// <summary>
/// The source column of the association
/// </summary>
public virtual ColumnProvider FromColumn { get; protected set; }
/// <summary>
/// The destination table of the association
/// </summary>
public virtual TableProvider ToTable {
get {
if (_toTable != null) {
return _toTable;
}
if (ToColumn != null) {
return ToColumn.Table;
}
return null;
}
protected set {
_toTable = value;
}
}
/// <summary>
/// The destination column of the association
/// </summary>
public virtual ColumnProvider ToColumn { get; protected set; }
/// <summary>
/// Returns true if the From Column part of the primary key of its table
/// e.g. Order and Product are PKs in the Order_Details table
/// </summary>
public virtual bool IsPrimaryKeyInThisTable { get; protected set; }
/// <summary>
/// The names of the underlying foreign keys that make up this association
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification="It's a readonly collection, so the warning is incorrect")]
public virtual ReadOnlyCollection<string> ForeignKeyNames { get; protected set; }
/// <summary>
/// Returns a string representing the sort expression that would be used for
/// sorting the column represented by this association. The parameter is the
/// property of the strongly typed entity used as the sort key for that entity.
/// For example, assume that this association represents the Category column
/// in the Products table. The sortColumn paramater is "CategoryName",
/// meaning that this method is being asked to return the sort expression for
/// sorting the Category column by the CategoryName property of the Category entity.
/// The result sort expression would be "Category.CategoryName".
/// The result of this method should be affected by whether the underlying data
/// model is capable of sorting the entity by the given sort column (see
/// ColumnProvider.IsSortable). The method can return a null value to indicate
/// that sorting is not supported.
/// </summary>
/// <param name="sortColumn">the column to sort the entity by</param>
/// <returns>the sort expression string, or null if sort is not supported for the
/// given sort column</returns>
public virtual string GetSortExpression(ColumnProvider sortColumn) {
return null;
}
internal string GetSortExpression(ColumnProvider sortColumn, string format) {
if (Direction == AssociationDirection.OneToMany || Direction == AssociationDirection.ManyToMany) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.AssociationProvider_DirectionDoesNotSupportSorting,
Direction));
}
if (sortColumn == null) {
throw new ArgumentNullException("sortColumn");
}
if (!ToTable.Columns.Contains(sortColumn)) {
throw new ArgumentException(DynamicDataResources.AssociationProvider_SortColumnDoesNotBelongToEndTable, "sortColumn");
}
if (sortColumn.IsSortable) {
return String.Format(CultureInfo.InvariantCulture, format, FromColumn.Name, sortColumn.Name);
} else {
return null;
}
}
}
}

View File

@ -0,0 +1,157 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Web.DynamicData;
using System.Web.DynamicData.Util;
namespace System.Web.DynamicData.ModelProviders {
/// <summary>
/// Base provider class for columns.
/// Each provider type (e.g. Linq To Sql, Entity Framework, 3rd party) extends this class.
/// </summary>
public abstract class ColumnProvider {
private bool? _isReadOnly;
/// <summary>
/// ctor
/// </summary>
/// <param name="table">the table this column belongs to</param>
protected ColumnProvider(TableProvider table) {
if (table == null) {
throw new ArgumentNullException("table");
}
Table = table;
}
/// <summary>
/// readable representation
/// </summary>
/// <returns></returns>
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
public override string ToString() {
// To help identifying objects in debugger
return Name ?? base.ToString();
}
internal virtual PropertyDescriptor PropertyDescriptor {
get {
return Table.GetTypeDescriptor().GetProperties().Find(Name, true/*ignoreCase*/);
}
}
public virtual AttributeCollection Attributes {
get {
var propertyDescriptor = PropertyDescriptor;
var attributes = propertyDescriptor != null ? propertyDescriptor.Attributes : AttributeCollection.Empty;
return AddDefaultAttributes(this, attributes);
}
}
protected static AttributeCollection AddDefaultAttributes(ColumnProvider columnProvider, AttributeCollection attributes) {
List<Attribute> extraAttributes = new List<Attribute>();
// If there is no required attribute and the Provider says required, add one
var requiredAttribute = attributes.FirstOrDefault<RequiredAttribute>();
if (requiredAttribute == null && !columnProvider.Nullable) {
extraAttributes.Add(new RequiredAttribute());
}
// If there is no StringLength attribute and it's a string, add one
var stringLengthAttribute = attributes.FirstOrDefault<StringLengthAttribute>();
int maxLength = columnProvider.MaxLength;
if (stringLengthAttribute == null && columnProvider.ColumnType == typeof(String) && maxLength > 0) {
extraAttributes.Add(new StringLengthAttribute(maxLength));
}
// If we need any extra attributes, create a new collection
if (extraAttributes.Count > 0) {
attributes = AttributeCollection.FromExisting(attributes, extraAttributes.ToArray());
}
return attributes;
}
/// <summary>
/// The name of the column
/// </summary>
public virtual string Name { get; protected set; }
/// <summary>
/// The CLR type of the column
/// </summary>
public virtual Type ColumnType { get; protected set; }
/// <summary>
/// Is this column a primary key in its table
/// </summary>
public virtual bool IsPrimaryKey { get; protected set; }
/// <summary>
/// Specifies if this column is read only
/// </summary>
public virtual bool IsReadOnly {
get {
if (_isReadOnly == null) {
var propertyDescriptor = PropertyDescriptor;
_isReadOnly = propertyDescriptor != null ? propertyDescriptor.IsReadOnly : false;
}
return _isReadOnly.Value;
}
protected set {
_isReadOnly = value;
}
}
/// <summary>
/// Is it a database generated column
/// </summary>
public virtual bool IsGenerated { get; protected set; }
/// <summary>
/// Returns whether the underlying model supports sorting of the table on this column
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sortable", Justification="It's a valid word")]
public virtual bool IsSortable { get; protected set; }
/// <summary>
/// The maximun length allowed for this column (applies to string columns)
/// </summary>
public virtual int MaxLength { get; protected set; }
/// <summary>
/// Does it allow null values (meaning it is not required)
/// </summary>
public virtual bool Nullable { get; protected set; }
/// <summary>
/// meant to indicate that a member is an extra property that was declared in a partial class
/// </summary>
public virtual bool IsCustomProperty { get; protected set; }
/// <summary>
/// If the column represents and association with anther table, this returns the association information.
/// Otherwise, null is returned.
/// </summary>
public virtual AssociationProvider Association { get; protected set; }
/// <summary>
/// The table that this column belongs to
/// </summary>
public TableProvider Table { get; private set; }
/// <summary>
/// The PropertyInfo of the property that represents this column on the entity type
/// </summary>
public virtual PropertyInfo EntityTypeProperty { get; protected set; }
/// <summary>
/// This is set for columns that are part of a foreign key. Note that it is NOT set for
/// the strongly typed entity ref columns (though those columns 'use' one or more columns
/// where IsForeignKeyComponent is set).
/// </summary>
public virtual bool IsForeignKeyComponent { get; protected set; }
}
}

View File

@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Collections.Specialized;
using System.Collections;
using System.Globalization;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class DLinqAssociationProvider : AssociationProvider {
public DLinqAssociationProvider(DLinqColumnProvider column) {
FromColumn = column;
MetaAssociation association = column.Member.Association;
SetOtherEndOfAssociation(association);
SetDirection(association);
Debug.Assert(Direction != AssociationDirection.ManyToMany, "Many to Many is not supported by Linq to SQL");
SetAssociationKeyInfo(association);
}
private void SetAssociationKeyInfo(MetaAssociation association) {
DLinqColumnProvider column = (DLinqColumnProvider)FromColumn;
List<string> foreignKeyNames = new List<string>();
int count = column.Member.Association.ThisKey.Count;
for (int i = 0; i < count; i++) {
MetaDataMember thisKeyMetaDataMember = column.Member.Association.ThisKey[i];
MetaDataMember otherKeyMetaDataMember = column.Member.Association.OtherKey[i];
DLinqColumnProvider thisEntityMemberComponent = FindColumn(column.Table, thisKeyMetaDataMember.Name);
if (ShouldRemoveThisAssociation(association)) {
column.ShouldRemove = true;
return;
}
foreignKeyNames.Add(thisEntityMemberComponent.Name);
if (thisEntityMemberComponent.IsPrimaryKey) {
IsPrimaryKeyInThisTable = true;
}
if (association.IsForeignKey) {
thisEntityMemberComponent.IsForeignKeyComponent = true;
}
}
ForeignKeyNames = new ReadOnlyCollection<string>(foreignKeyNames);
}
private bool ShouldRemoveThisAssociation(MetaAssociation association) {
if (Direction == AssociationDirection.ManyToOne && !association.OtherKeyIsPrimaryKey) {
return true;
}
if (Direction == AssociationDirection.OneToMany && !association.ThisKeyIsPrimaryKey) {
return true;
}
if (Direction == AssociationDirection.OneToOne) {
if (!association.IsForeignKey && !association.ThisKeyIsPrimaryKey) {
return true;
}
if (association.IsForeignKey && !association.OtherKeyIsPrimaryKey) {
return true;
}
}
return false;
}
private void SetOtherEndOfAssociation(MetaAssociation association) {
DLinqTableProvider entityMemberParentEntity = (DLinqTableProvider)FromColumn.Table;
DLinqDataModelProvider parentEntityDataContext = (DLinqDataModelProvider)entityMemberParentEntity.DataModel;
if (association.OtherMember != null) {
ToColumn = parentEntityDataContext.ColumnLookup[(PropertyInfo)association.OtherMember.Member];
} else {
ToTable = ((DLinqDataModelProvider)FromColumn.Table.DataModel).DLinqTables.Single(tp => tp.EntityType == association.OtherType.Type);
}
}
private static DLinqColumnProvider FindColumn(TableProvider table, String columnName) {
//
return (DLinqColumnProvider)table.Columns.First(member => member.Name.Equals(columnName));
}
private void SetDirection(MetaAssociation association) {
if (association.IsMany) {
Direction = AssociationDirection.OneToMany;
} else if (association.OtherMember == null || association.OtherMember.Association.IsMany) {
// there might not be the other member if this is a one-sided association
Direction = AssociationDirection.ManyToOne;
} else {
Direction = AssociationDirection.OneToOne;
}
}
public override string GetSortExpression(ColumnProvider sortColumn) {
return GetSortExpression(sortColumn, "{0}.{1}");
}
}
}

View File

@ -0,0 +1,158 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class DLinqColumnProvider : ColumnProvider {
private static Regex s_varCharRegEx = new Regex(@"N?(?:Var)?Char\(([0-9]+)\)", RegexOptions.IgnoreCase); // accepts char, nchar, varchar, and nvarchar
private AttributeCollection _attributes;
private AssociationProvider _association;
private bool _isAssociation;
public DLinqColumnProvider(DLinqTableProvider table, MetaDataMember member)
: base(table) {
Member = member;
Name = member.Name;
ColumnType = GetMemberType(member);
IsPrimaryKey = member.IsPrimaryKey;
IsGenerated = member.IsDbGenerated;
_isAssociation = member.IsAssociation;
IsCustomProperty = !member.IsAssociation && Member.DbType == null;
Nullable = Member.IsAssociation ? Member.Association.IsNullable : Member.CanBeNull;
MaxLength = ProcessMaxLength(ColumnType, Member.DbType);
IsSortable = ProcessIsSortable(ColumnType, Member.DbType);
}
public override AttributeCollection Attributes {
get {
if (!Member.IsDiscriminator)
return base.Attributes;
if (_attributes == null) {
List<Attribute> newAttributes = new List<Attribute>();
bool foundScaffoldAttribute = false;
foreach (Attribute attr in base.Attributes) {
if (attr is ScaffoldColumnAttribute) {
foundScaffoldAttribute = true;
break;
}
newAttributes.Add(attr);
}
if (foundScaffoldAttribute)
_attributes = base.Attributes;
else {
newAttributes.Add(new ScaffoldColumnAttribute(false));
_attributes = new AttributeCollection(newAttributes.ToArray());
}
}
return _attributes;
}
}
// internal to facilitate unit testing
internal static int ProcessMaxLength(Type memberType, String dbType) {
// Only strings and chars that come in from a database have max lengths
if (dbType == null || (memberType != typeof(string) && Misc.RemoveNullableFromType(memberType) != typeof(char)))
return 0;
if (dbType.StartsWith("NText", StringComparison.OrdinalIgnoreCase)) {
return Int32.MaxValue >> 1; // see sql server 2005 spec for ntext
}
if (dbType.StartsWith("Text", StringComparison.OrdinalIgnoreCase)) {
return Int32.MaxValue; // see sql server 2005 spec for text
}
if (dbType.StartsWith("NVarChar(MAX)", StringComparison.OrdinalIgnoreCase)) {
return (Int32.MaxValue >> 1) - 2; // see sql server 2005 spec for nvarchar
}
if (dbType.StartsWith("VarChar(MAX)", StringComparison.OrdinalIgnoreCase)) {
return Int32.MaxValue - 2; // see sql server 2005 spec for varchar
}
Match m = s_varCharRegEx.Match(dbType);
if (m.Success) {
return Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
}
return 0;
}
internal static bool ProcessIsSortable(Type memberType, String dbType) {
if (dbType == null)
return false;
if (memberType == typeof(string) &&
(dbType.StartsWith("Text", StringComparison.OrdinalIgnoreCase)
|| dbType.StartsWith("NText", StringComparison.OrdinalIgnoreCase))) {
return false;
}
if (memberType == typeof(Binary) && dbType.StartsWith("Image", StringComparison.OrdinalIgnoreCase)) {
return false;
}
if (memberType == typeof(XElement)) {
return false;
}
return true;
}
internal MetaDataMember Member {
get;
private set;
}
internal void Initialize() {
if (_isAssociation && _association == null) {
_association = new DLinqAssociationProvider(this);
}
}
internal bool ShouldRemove { get; set; }
private static Type GetMemberType(MetaDataMember member) {
Type type = member.Type;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(EntitySet<>)) {
return type.GetGenericArguments()[0];
}
else {
return type;
}
}
#region IEntityMember Members
public override PropertyInfo EntityTypeProperty {
get { return (PropertyInfo)Member.Member; }
}
public override AssociationProvider Association {
get {
Initialize();
return _association;
}
}
internal new bool IsForeignKeyComponent {
set {
base.IsForeignKeyComponent = value;
}
}
#endregion
}
}

View File

@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Linq;
using System.Linq;
using System.Reflection;
using LinqMetaTable = System.Data.Linq.Mapping.MetaTable;
using LinqMetaType = System.Data.Linq.Mapping.MetaType;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class DLinqDataModelProvider : DataModelProvider {
private ReadOnlyCollection<TableProvider> _roTables;
private Dictionary<PropertyInfo, DLinqColumnProvider> _columnLookup = new Dictionary<PropertyInfo, DLinqColumnProvider>();
private Func<object> ContextFactory { get; set; }
public DLinqDataModelProvider(object contextInstance, Func<object> contextFactory) {
ContextFactory = contextFactory;
DataContext context = (DataContext)contextInstance ?? (DataContext)CreateContext();
ContextType = context.GetType();
DLinqTables = new List<TableProvider>();
foreach (PropertyInfo prop in ContextType.GetProperties()) {
Type entityType = GetEntityType(prop);
if (entityType != null) {
LinqMetaTable table = GetLinqTable(context, entityType);
ProcessTable(table, table.RowType, prop.Name, prop);
}
}
DLinqTables.ForEach(t => ((DLinqTableProvider)t).Initialize());
_roTables = new ReadOnlyCollection<TableProvider>(DLinqTables);
}
private LinqMetaTable GetLinqTable(DataContext context, Type entityType) {
return context.Mapping.GetTables().First(t => t.RowType.Type == entityType);
}
private Type GetEntityType(PropertyInfo prop) {
//
if (prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(Table<>))
return prop.PropertyType.GetGenericArguments()[0];
return null;
}
private void ProcessTable(LinqMetaTable table, LinqMetaType rowType, string name, PropertyInfo prop) {
DLinqTables.Add(new DLinqTableProvider(this, rowType, name, prop));
foreach (LinqMetaType derivedType in rowType.DerivedTypes)
ProcessTable(table, derivedType, derivedType.Name, prop);
}
internal Dictionary<PropertyInfo, DLinqColumnProvider> ColumnLookup {
get {
return _columnLookup;
}
}
internal List<TableProvider> DLinqTables { get; private set; }
public override object CreateContext() {
return ContextFactory();
}
public override ReadOnlyCollection<TableProvider> Tables {
get {
return _roTables;
}
}
}
}

View File

@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class DLinqTableProvider : TableProvider {
private ReadOnlyCollection<ColumnProvider> _roColumns;
private List<ColumnProvider> _columns;
private MetaType _rowType;
private PropertyInfo _prop;
public DLinqTableProvider(DLinqDataModelProvider dataModel, MetaType rowType, string name, PropertyInfo prop)
: base(dataModel) {
_prop = prop;
_rowType = rowType;
Name = name;
DataContextPropertyName = prop.Name;
EntityType = rowType.Type;
ParentEntityType = rowType.InheritanceBase != null ? rowType.InheritanceBase.Type : null;
RootEntityType = rowType.Table.RowType.Type;
_columns = new List<ColumnProvider>();
var members = new List<MetaDataMember>(rowType.DataMembers);
// Add in base-class-first order (not the typical derived-class-first order)
foreach (PropertyInfo propInfo in GetOrderedProperties(rowType.Type)) {
MetaDataMember member = members.FirstOrDefault(m => m.Member.Name == propInfo.Name);
if (member != null) {
AddColumn(dataModel, member, propInfo);
members.Remove(member);
}
}
// Anything we might've missed, tack it onto the end
foreach (MetaDataMember member in members) {
AddColumn(dataModel, member, (PropertyInfo)member.Member);
}
_roColumns = new ReadOnlyCollection<ColumnProvider>(_columns);
}
private void AddColumn(DLinqDataModelProvider dataModel, MetaDataMember member, PropertyInfo propInfo) {
var publicGetAccessor = propInfo.GetGetMethod();
if (publicGetAccessor == null) {
// the property at least needs to have a public getter, otherwise databinding will not work
return;
}
DLinqColumnProvider column = new DLinqColumnProvider(this, member);
_columns.Add(column);
if (!dataModel.ColumnLookup.ContainsKey(propInfo))
dataModel.ColumnLookup[propInfo] = column;
}
private IEnumerable<PropertyInfo> GetOrderedProperties(Type type) {
if (type == null)
return new PropertyInfo[0];
PropertyInfo[] props = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
return GetOrderedProperties(type.BaseType).Concat(props);
}
internal void Initialize() {
_columns.ForEach(c => ((DLinqColumnProvider)c).Initialize());
_columns.RemoveAll(c => ((DLinqColumnProvider)c).ShouldRemove);
}
#region IEntity Members
public override IQueryable GetQuery(object context) {
return (IQueryable)_prop.GetValue(context, null);
}
public override ReadOnlyCollection<ColumnProvider> Columns {
get {
return _roColumns;
}
}
#endregion
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.ObjectModel;
namespace System.Web.DynamicData.ModelProviders {
/// <summary>
/// Base data model provider class
/// Each provider type (e.g. Linq To Sql, Entity Framework, 3rd party) extends this class.
/// </summary>
public abstract class DataModelProvider {
/// <summary>
/// The list of tables exposed by this data model
/// </summary>
public abstract ReadOnlyCollection<TableProvider> Tables { get; }
/// <summary>
/// The type of the data context
/// </summary>
public virtual Type ContextType { get; protected set; }
/// <summary>
/// Create an instance of the data context
/// </summary>
public abstract object CreateContext();
}
}

View File

@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class EFAssociationProvider : AssociationProvider {
public EFAssociationProvider(EFColumnProvider column, NavigationProperty navigationProperty) {
FromColumn = column;
var entityMemberParentEntity = (EFTableProvider)column.Table;
var parentEntityModel = (EFDataModelProvider)entityMemberParentEntity.DataModel;
EFColumnProvider columnProvider;
EntityType otherEntityType = navigationProperty.ToEndMember.GetEntityType();
// If we can get to the entityType of the ToMember side of the relaionship then build a relationship key and try to lookup the column provider.
if (otherEntityType != null) {
long key = BuildRelationshipKey(otherEntityType, navigationProperty.ToEndMember);
if (parentEntityModel.RelationshipEndLookup.TryGetValue(key, out columnProvider)) {
ToColumn = columnProvider;
}
else {
// Otherwise just lookup the entityType in the table lookup
ToTable = parentEntityModel.TableEndLookup[otherEntityType];
}
}
else {
EntityType value = (EntityType)navigationProperty.ToEndMember.TypeUsage.EdmType.MetadataProperties.Single(prop => prop.Name == "ElementType").Value;
ToTable = parentEntityModel.TableEndLookup[value];
}
if (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) {
if (navigationProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) {
Direction = AssociationDirection.ManyToMany;
}
else {
Direction = AssociationDirection.OneToMany;
}
}
else {
if (navigationProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) {
Direction = AssociationDirection.ManyToOne;
}
else {
Direction = AssociationDirection.OneToOne;
}
}
// If it's a foreign key reference (as opposed to a entity set), figure out the foreign keys
if (IsForeignKeyReference) {
var foreignKeyNames = new List<string>();
var primaryKeyNames = FromColumn.Table.Columns.Where(c => c.IsPrimaryKey).Select(c => c.Name);
// Add the foreign keys for this association.
foreignKeyNames.AddRange(GetDependentPropertyNames(navigationProperty));
if (IsZeroOrOne(navigationProperty)) {
// Assume this is true for 1 to 0..1 relationships on both sides
IsPrimaryKeyInThisTable = true;
}
else {
// If any of the foreign keys are also PKs, set the flag
IsPrimaryKeyInThisTable = foreignKeyNames.Any(fkName => primaryKeyNames.Contains(fkName, StringComparer.OrdinalIgnoreCase));
}
if (!foreignKeyNames.Any()) {
// If we couldn't find any dependent properties, we're dealing with a model that doesn't
// have FKs, and requires the use of flattened FK names (e.g. Category.CategoryId)
foreach (ColumnProvider toEntityColumn in ToTable.Columns.Where(c => c.IsPrimaryKey)) {
foreignKeyNames.Add(FromColumn.Name + "." + toEntityColumn.Name);
}
}
ForeignKeyNames = foreignKeyNames.AsReadOnly();
}
}
private static bool IsZeroOrOne(NavigationProperty navigationProperty) {
return (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne &&
navigationProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One) ||
(navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One &&
navigationProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
}
private bool IsForeignKeyReference {
get {
return Direction == AssociationDirection.OneToOne || Direction == AssociationDirection.ManyToOne;
}
}
internal static long BuildRelationshipKey(EntityType entityType, RelationshipEndMember member) {
return Misc.CombineHashCodes(entityType.GetHashCode(), member.GetHashCode());
}
internal static IEnumerable<string> GetDependentPropertyNames(NavigationProperty navigationProperty) {
return GetDependentPropertyNames(navigationProperty, true /*checkRelationshipType*/);
}
internal static IEnumerable<string> GetDependentPropertyNames(NavigationProperty navigationProperty, bool checkRelationshipType) {
if (checkRelationshipType) {
if (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne &&
navigationProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One) {
// Get constraint when this association is on the "parent" (aka "from") side. This means
// that the navProperty represents a Children association in a 1-1 relationship. For example,
// this could be a Item-ItemDetail scenario where the ItemDetail table has a PK that is also an FK
// into the Item table (thus ensuring the 1-1 cardinality). We need to special case this situation because normally we would try
// to build a foreign key name of the form "Item.ItemID", but we want just "ItemID".
AssociationType relationshipType = (AssociationType)navigationProperty.RelationshipType;
ReferentialConstraint constraint = relationshipType.ReferentialConstraints.FirstOrDefault(c => c.ToRole == navigationProperty.ToEndMember);
if (constraint != null) {
return constraint.FromProperties.Select(p => p.Name);
}
// Fall back on the primary keys if no constraints were found but only if we are on the parent side. i.e the 1 side Item side in an Item-ItemDetail
// Get the primary keys on the "from" side of the relationship. i.e Product.Category -> ProductID
return navigationProperty.FromEndMember.GetEntityType().KeyMembers.Select(m => m.Name);
}
}
return navigationProperty.GetDependentProperties().Select(m => m.Name);
}
public override string GetSortExpression(ColumnProvider sortColumn) {
return GetSortExpression(sortColumn, "{0}.{1}");
}
}
}

View File

@ -0,0 +1,150 @@
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
using System.Globalization;
using System.Reflection;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class EFColumnProvider : ColumnProvider {
private EFTableProvider _table;
private EFAssociationProvider _association;
private bool _isAssociation;
private bool _isSortableProcessed;
private const string StoreGeneratedMetadata = "http://schemas.microsoft.com/ado/2009/02/edm/annotation:StoreGeneratedPattern";
public EFColumnProvider(EntityType entityType, EFTableProvider table, EdmMember m, bool isPrimaryKey)
: base(table) {
EdmMember = m;
IsPrimaryKey = isPrimaryKey;
_table = table;
MaxLength = 0;
Name = EdmMember.Name;
//
IsCustomProperty = false;
//
var property = EdmMember as EdmProperty;
if (property != null) {
IsForeignKeyComponent = DetermineIsForeignKeyComponent(property);
IsGenerated = IsServerGenerated(property);
}
ProcessFacets();
var navProp = m as NavigationProperty;
if (navProp != null) {
_isAssociation = true;
long key = EFAssociationProvider.BuildRelationshipKey(entityType, navProp.FromEndMember);
((EFDataModelProvider)table.DataModel).RelationshipEndLookup[key] = this;
}
}
private bool DetermineIsForeignKeyComponent(EdmProperty property) {
var navigationProperties = property.DeclaringType.Members.OfType<NavigationProperty>();
// Look at all NavigationProperties (i.e. strongly-type relationship columns) of the table this column belong to and
// see if there is a foreign key that matches this property
// If this is a 1 to 0..1 relationship and we are processing the more primary side. i.e in the Student in Student-StudentDetail
// and this is the primary key we don't want to check the relationship type since if there are no constraints we will treat the primary key as a foreign key.
return navigationProperties.Any(n => EFAssociationProvider.GetDependentPropertyNames(n, !IsPrimaryKey /* checkRelationshipType */).Contains(property.Name));
}
private static bool IsServerGenerated(EdmProperty property) {
MetadataProperty generated;
if (property.MetadataProperties.TryGetValue(StoreGeneratedMetadata, false, out generated)) {
return "Identity" == (string)generated.Value || "Computed" == (string)generated.Value;
}
return false;
}
private void ProcessFacets() {
foreach (Facet facet in EdmMember.TypeUsage.Facets) {
switch (facet.Name) {
case "MaxLength":
if (facet.IsUnbounded) {
// If it's marked as unbounded, treat it as max int
MaxLength = Int32.MaxValue;
}
else if (facet.Value != null && facet.Value is int) {
MaxLength = (int)facet.Value;
}
break;
case "Nullable":
Nullable = (bool)facet.Value;
break;
}
}
}
internal EdmMember EdmMember {
get;
private set;
}
#region IEntityMember Members
public override PropertyInfo EntityTypeProperty {
//
get { return _table.EntityType.GetProperty(Name); }
}
public override Type ColumnType {
get {
if (base.ColumnType == null) {
//
var edmType = EdmMember.TypeUsage.EdmType;
if (edmType is EntityType) {
base.ColumnType = ((EFDataModelProvider)this.Table.DataModel).GetClrType(edmType);
}
else if (edmType is CollectionType) {
// get the EdmType that this CollectionType is wrapping
base.ColumnType = ((EFDataModelProvider)this.Table.DataModel).GetClrType(((CollectionType)edmType).TypeUsage.EdmType);
}
else if (edmType is PrimitiveType) {
base.ColumnType = ((PrimitiveType)edmType).ClrEquivalentType;
}
else if (edmType is EnumType) {
base.ColumnType = ((EFDataModelProvider)this.Table.DataModel).GetClrType((EnumType)edmType);
}
else {
Debug.Assert(false, String.Format(CultureInfo.CurrentCulture, "Unknown EdmType {0}.", edmType.GetType().FullName));
}
}
return base.ColumnType;
}
}
public override bool IsSortable {
get {
if (!_isSortableProcessed) {
base.IsSortable = (ColumnType != typeof(byte[]));
_isSortableProcessed = true;
}
return base.IsSortable;
}
}
public override AssociationProvider Association {
get {
if (!_isAssociation) {
return null;
}
if (_association == null) {
_association = new EFAssociationProvider(this, (NavigationProperty)EdmMember);
}
return _association;
}
}
#endregion
internal static bool IsSupportedEdmMemberType(EdmMember member) {
var edmType = member.TypeUsage.EdmType;
return edmType is EntityType || edmType is CollectionType || edmType is PrimitiveType || edmType is EnumType;
}
}
}

View File

@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class EFDataModelProvider : DataModelProvider {
private ReadOnlyCollection<TableProvider> _tables;
internal Dictionary<long, EFColumnProvider> RelationshipEndLookup { get; private set; }
internal Dictionary<EntityType, EFTableProvider> TableEndLookup { get; private set; }
private Func<object> ContextFactory { get; set; }
private Dictionary<EdmType, Type> _entityTypeToClrType = new Dictionary<EdmType, Type>();
private ObjectContext _context;
private ObjectItemCollection _objectSpaceItems;
public EFDataModelProvider(object contextInstance, Func<object> contextFactory) {
ContextFactory = contextFactory;
RelationshipEndLookup = new Dictionary<long, EFColumnProvider>();
TableEndLookup = new Dictionary<EntityType, EFTableProvider>();
_context = (ObjectContext)contextInstance ?? (ObjectContext)CreateContext();
ContextType = _context.GetType();
// get a "container" (a scope at the instance level)
EntityContainer container = _context.MetadataWorkspace.GetEntityContainer(_context.DefaultContainerName, DataSpace.CSpace);
// load object space metadata
_context.MetadataWorkspace.LoadFromAssembly(ContextType.Assembly);
_objectSpaceItems = (ObjectItemCollection)_context.MetadataWorkspace.GetItemCollection(DataSpace.OSpace);
var tables = new List<TableProvider>();
// Create a dictionary from entity type to entity set. The entity type should be at the root of any inheritance chain.
IDictionary<EntityType, EntitySet> entitySetLookup = container.BaseEntitySets.OfType<EntitySet>().ToDictionary(e => e.ElementType);
// Create a lookup from parent entity to entity
ILookup<EntityType, EntityType> derivedTypesLookup = _context.MetadataWorkspace.GetItems<EntityType>(DataSpace.CSpace).ToLookup(e => (EntityType)e.BaseType);
// Keeps track of the current entity set being processed
EntitySet currentEntitySet = null;
// Do a DFS to get the inheritance hierarchy in order
// i.e. Consider the hierarchy
// null -> Person
// Person -> Employee, Contact
// Employee -> SalesPerson, Programmer
// We'll walk the children in a depth first order -> Person, Employee, SalesPerson, Programmer, Contact.
var objectStack = new Stack<EntityType>();
// Start will null (the root of the hierarchy)
objectStack.Push(null);
while (objectStack.Any()) {
EntityType entityType = objectStack.Pop();
if (entityType != null) {
// Update the entity set when we are at another root type (a type without a base type).
if (entityType.BaseType == null) {
currentEntitySet = entitySetLookup[entityType];
}
var table = CreateTableProvider(currentEntitySet, entityType);
tables.Add(table);
}
foreach (EntityType derivedEntityType in derivedTypesLookup[entityType]) {
// Push the derived entity types on the stack
objectStack.Push(derivedEntityType);
}
}
_tables = tables.AsReadOnly();
}
public override object CreateContext() {
return ContextFactory();
}
public override ReadOnlyCollection<TableProvider> Tables {
get {
return _tables;
}
}
internal Type GetClrType(EdmType entityType) {
var result = _entityTypeToClrType[entityType];
Debug.Assert(result != null, String.Format(CultureInfo.CurrentCulture, "Cannot map EdmType '{0}' to matching CLR Type", entityType));
return result;
}
internal Type GetClrType(EnumType enumType) {
var objectSpaceType = (EnumType)_context.MetadataWorkspace.GetObjectSpaceType(enumType);
return _objectSpaceItems.GetClrType(objectSpaceType);
}
private Type GetClrType(EntityType entityType) {
var objectSpaceType = (EntityType)_context.MetadataWorkspace.GetObjectSpaceType(entityType);
return _objectSpaceItems.GetClrType(objectSpaceType);
}
private TableProvider CreateTableProvider(EntitySet entitySet, EntityType entityType) {
// Get the parent clr type
Type parentClrType = null;
EntityType parentEntityType = entityType.BaseType as EntityType;
if (parentEntityType != null) {
parentClrType = GetClrType(parentEntityType);
}
Type rootClrType = GetClrType(entitySet.ElementType);
Type clrType = GetClrType(entityType);
_entityTypeToClrType[entityType] = clrType;
// Normally, use the entity set name as the table name
string tableName = entitySet.Name;
// But in inheritance scenarios where all types in the hierarchy share the same entity set,
// we need to use the type name instead for the table name.
if (parentClrType != null) {
tableName = entityType.Name;
}
EFTableProvider table = new EFTableProvider(this, entitySet, entityType, clrType, parentClrType, rootClrType, tableName);
TableEndLookup[entityType] = table;
return table;
}
}
}

View File

@ -0,0 +1,73 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Linq;
using System.Reflection;
namespace System.Web.DynamicData.ModelProviders {
internal sealed class EFTableProvider : TableProvider {
private ReadOnlyCollection<ColumnProvider> _roColumns;
public EFTableProvider(EFDataModelProvider dataModel, EntitySet entitySet, EntityType entityType,
Type entityClrType, Type parentEntityClrType, Type rootEntityClrType, string name)
: base(dataModel) {
EntityType = entityClrType;
Name = name;
DataContextPropertyName = entitySet.Name;
ParentEntityType = parentEntityClrType;
RootEntityType = rootEntityClrType;
var genericMethod = DataModel.ContextType.GetMethod("CreateQuery");
CreateQueryMethod = genericMethod.MakeGenericMethod(EntityType);
CreateQueryString = CreateEntitySqlQueryString(entitySet);
var keyMembers = entityType.KeyMembers;
// columns (entity properties)
// note 1: keys are also available through es.ElementType.KeyMembers
// note 2: this includes "nav properties", kind of fancy, two-way relationship objects
var columns = new List<ColumnProvider>();
foreach (EdmMember m in entityType.Members) {
if (EFColumnProvider.IsSupportedEdmMemberType(m) && IsPublicProperty(entityClrType, m.Name)) {
EFColumnProvider entityMember = new EFColumnProvider(entityType, this, m, keyMembers.Contains(m));
columns.Add(entityMember);
}
}
_roColumns = new ReadOnlyCollection<ColumnProvider>(columns);
}
private static bool IsPublicProperty(Type entityClrType, string propertyName) {
var property = entityClrType.GetProperty(propertyName);
return property != null && property.GetGetMethod() != null;
}
private MethodInfo CreateQueryMethod { get; set; }
private string CreateQueryString { get; set; }
private static string CreateEntitySqlQueryString(EntitySet entitySet) {
// Qualify the entity set name with the container name (in case the ObjectContext's default
// container name is not set or has an unexpected value)
return QuoteEntitySqlIdentifier(entitySet.EntityContainer.Name) + "." + QuoteEntitySqlIdentifier(entitySet.Name);
}
private static string QuoteEntitySqlIdentifier(string identifier) {
// Enclose in square brackets and escape the one reserved character (])
return "[" + identifier.Replace("]", "]]") + "]";
}
public override ReadOnlyCollection<ColumnProvider> Columns {
get {
return _roColumns;
}
}
public override IQueryable GetQuery(object context) {
return (IQueryable)CreateQueryMethod.Invoke(context,
new object[] { CreateQueryString, new ObjectParameter[0] });
}
}
}

View File

@ -0,0 +1,44 @@
using System.Data.Linq;
using System.Data.Objects;
using System.Globalization;
using System.Web.Resources;
namespace System.Web.DynamicData.ModelProviders {
internal class SchemaCreator {
private static SchemaCreator s_instance = new SchemaCreator();
public static SchemaCreator Instance {
get {
return s_instance;
}
}
public virtual DataModelProvider CreateDataModel(object contextInstance, Func<object> contextFactory) {
if (IsDataContext(contextInstance.GetType())) {
return new DLinqDataModelProvider(contextInstance, contextFactory);
}
if (IsObjectContext(contextInstance.GetType())) {
return new EFDataModelProvider(contextInstance, contextFactory);
}
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.SchemaCreator_UnknownModel, contextInstance.GetType().FullName));
}
public virtual bool ValidDataContextType(Type contextType) {
//
return IsDataContext(contextType) || IsObjectContext(contextType);
}
internal static bool IsDataContext(Type contextType) {
return IsValidType<DataContext>(contextType);
}
internal static bool IsObjectContext(Type contextType) {
return IsValidType<ObjectContext>(contextType);
}
private static bool IsValidType<T>(Type contextType) where T : class {
return contextType != null && typeof(T).IsAssignableFrom(contextType);
}
}
}

View File

@ -0,0 +1,33 @@
namespace System.Web.DynamicData.ModelProviders {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Resources;
internal sealed class SimpleColumnProvider : ColumnProvider {
public SimpleColumnProvider(TableProvider tableProvider, PropertyDescriptor propertyDescriptor)
: base(tableProvider) {
if (propertyDescriptor.PropertyType == null) {
throw new ArgumentNullException(DynamicDataResources.SimpleColumnProvider_ColumnTypeRequired);
}
Name = propertyDescriptor.Name;
ColumnType = propertyDescriptor.PropertyType;
IsPrimaryKey = propertyDescriptor.Attributes.OfType<KeyAttribute>().Any();
Nullable = Misc.TypeAllowsNull(ColumnType);
IsReadOnly = propertyDescriptor.IsReadOnly;
IsSortable = true;
}
public override AttributeCollection Attributes {
get {
if (!System.Web.UI.DataBinder.IsBindableType(ColumnType)) {
return AttributeCollection.FromExisting(base.Attributes, new ScaffoldColumnAttribute(false));
}
return base.Attributes;
}
}
}
}

View File

@ -0,0 +1,30 @@
namespace System.Web.DynamicData.ModelProviders {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
internal class SimpleDataModelProvider : DataModelProvider {
private List<TableProvider> _tables = new List<TableProvider>();
public SimpleDataModelProvider(Type entityType) {
_tables.Add(new SimpleTableProvider(this, entityType));
}
public SimpleDataModelProvider(ICustomTypeDescriptor typeDescriptor) {
_tables.Add(new SimpleTableProvider(this, typeDescriptor));
}
public override ReadOnlyCollection<TableProvider> Tables {
get {
return _tables.AsReadOnly();
}
}
public override object CreateContext() {
throw new NotSupportedException();
}
}
}

View File

@ -0,0 +1,57 @@
namespace System.Web.DynamicData.ModelProviders {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
internal sealed class SimpleTableProvider : TableProvider {
private List<ColumnProvider> _columns;
private ICustomTypeDescriptor _descriptor;
public SimpleTableProvider(DataModelProvider modelProvider, Type entityType)
: base(modelProvider) {
if (entityType == null) {
throw new ArgumentNullException("entityType");
}
EntityType = entityType;
Name = entityType.Name;
DataContextPropertyName = String.Empty;
InitializeColumns(TypeDescriptor.GetProperties(entityType));
}
public SimpleTableProvider(DataModelProvider modelProvider, ICustomTypeDescriptor descriptor)
: base(modelProvider) {
if (descriptor == null) {
throw new ArgumentNullException("descriptor");
}
_descriptor = descriptor;
Name = descriptor.GetClassName();
DataContextPropertyName = String.Empty;
InitializeColumns(descriptor.GetProperties());
}
public override ReadOnlyCollection<ColumnProvider> Columns {
get {
return _columns.AsReadOnly();
}
}
public override ICustomTypeDescriptor GetTypeDescriptor() {
return _descriptor ?? base.GetTypeDescriptor();
}
public override IQueryable GetQuery(object context) {
throw new NotSupportedException();
}
private void InitializeColumns(PropertyDescriptorCollection columnDescriptors) {
_columns = columnDescriptors.OfType<PropertyDescriptor>().Select(p => new SimpleColumnProvider(this, p)).OfType<ColumnProvider>().ToList();
}
}
}

View File

@ -0,0 +1,159 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Security.Principal;
namespace System.Web.DynamicData.ModelProviders {
/// <summary>
/// Base provider class for tables.
/// Each provider type (e.g. Linq To Sql, Entity Framework, 3rd party) extends this class.
/// </summary>
public abstract class TableProvider {
private Type _rootEntityType;
private string _dataContextPropertyName;
internal TableProvider() {
// for unit testing
}
/// <summary>
/// ctor
/// </summary>
/// <param name="model">the model this table belongs to</param>
protected TableProvider(DataModelProvider model) {
if (model == null) {
throw new ArgumentNullException("model");
}
DataModel = model;
}
/// <summary>
/// readable representation
/// </summary>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
public override string ToString() {
// To help identifying objects in debugger
return Name ?? base.ToString();
}
/// <summary>
/// Provides access to attributes defined for the table represented by this provider.
/// </summary>
public virtual AttributeCollection Attributes {
get {
return GetTypeDescriptor().GetAttributes();
}
}
public virtual ICustomTypeDescriptor GetTypeDescriptor() {
return TypeDescriptor.GetProvider(EntityType).GetTypeDescriptor(EntityType);
}
/// <summary>
/// The name of the table. Typically, this is the name of the property in the data context class
/// </summary>
public virtual string Name { get; protected set; }
/// <summary>
/// The CLR type that represents this table
/// </summary>
public virtual Type EntityType { get; protected set; }
/// <summary>
/// The collection of columns in this table
/// </summary>
public abstract ReadOnlyCollection<ColumnProvider> Columns { get; }
/// <summary>
/// The IQueryable that returns the elements of this table
/// </summary>
public abstract IQueryable GetQuery(object context);
/// <summary>
/// The data model provider that this table is part of
/// </summary>
public DataModelProvider DataModel { get; internal set; }
/// <summary>
/// Get the value of a foreign key for a given row. By default, it just looks up a property by that name
/// </summary>
public virtual object EvaluateForeignKey(object row, string foreignKeyName) {
return System.Web.UI.DataBinder.GetPropertyValue(row, foreignKeyName);
}
/// <summary>
/// Return the parent type of this entity's inheritance hierarchy; if the type is at the top
/// of an inheritance hierarchy or does not have any inheritance, will return null.
/// </summary>
public virtual Type ParentEntityType { get; protected set; }
/// <summary>
/// Return the root type of this entity's inheritance hierarchy; if the type is at the top
/// of an inheritance hierarchy or does not have any inheritance, will return EntityType.
/// </summary>
public virtual Type RootEntityType {
get {
return _rootEntityType ?? EntityType;
}
protected set {
_rootEntityType = value;
}
}
/// <summary>
/// Name of table coming from the property on the data context. E.g. the value is "Products" for
/// a table that is part of the NorthwindDataContext.Products collection. If this value has not
/// been set, it will return the value of the Name property.
/// </summary>
public virtual string DataContextPropertyName {
get {
return _dataContextPropertyName ?? Name;
}
protected set {
_dataContextPropertyName = value;
}
}
/// <summary>
/// Returns whether the passed in user is allowed to delete items from the table
/// </summary>
public virtual bool CanDelete(IPrincipal principal) {
if (principal == null) {
throw new ArgumentNullException("principal");
}
return true;
}
/// <summary>
/// Returns whether the passed in user is allowed to insert into the table
/// </summary>
public virtual bool CanInsert(IPrincipal principal) {
if (principal == null) {
throw new ArgumentNullException("principal");
}
return true;
}
/// <summary>
/// Returns whether the passed in user is allowed to read from the table
/// </summary>
public virtual bool CanRead(IPrincipal principal) {
if (principal == null) {
throw new ArgumentNullException("principal");
}
return true;
}
/// <summary>
/// Returns whether the passed in user is allowed to make changes tothe table
/// </summary>
public virtual bool CanUpdate(IPrincipal principal) {
if (principal == null) {
throw new ArgumentNullException("principal");
}
return true;
}
}
}