Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Web.Http;
namespace Microsoft.Web.Http.Data.EntityFramework
{
/// <summary>
/// DbContext extension methods
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class DbContextExtensions
{
/// <summary>
/// Extension method used to attach the specified entity as modified,
/// with the specified original state.
/// </summary>
/// <typeparam name="TEntity">The entity Type</typeparam>
/// <param name="dbSet">The <see cref="DbSet"/> to attach to.</param>
/// <param name="current">The current entity.</param>
/// <param name="original">The original entity.</param>
/// <param name="dbContext">The corresponding <see cref="DbContext"/></param>
public static void AttachAsModified<TEntity>(this DbSet<TEntity> dbSet, TEntity current, TEntity original, DbContext dbContext) where TEntity : class
{
if (dbSet == null)
{
throw Error.ArgumentNull("dbSet");
}
if (current == null)
{
throw Error.ArgumentNull("current");
}
if (original == null)
{
throw Error.ArgumentNull("original");
}
if (dbContext == null)
{
throw Error.ArgumentNull("dbContext");
}
DbEntityEntry<TEntity> entityEntry = dbContext.Entry(current);
if (entityEntry.State == EntityState.Detached)
{
dbSet.Attach(current);
}
else
{
entityEntry.State = EntityState.Modified;
}
ObjectContext objectContext = (dbContext as IObjectContextAdapter).ObjectContext;
ObjectStateEntry stateEntry = ObjectContextUtilities.AttachAsModifiedInternal<TEntity>(current, original, objectContext);
if (stateEntry.State != EntityState.Modified)
{
// Ensure that when we leave this method, the entity is in a
// Modified state. For example, if current and original are the
// same, we still need to force the state transition
entityEntry.State = EntityState.Modified;
}
}
/// <summary>
/// Extension method used to attach the specified entity as modified,
/// with the specified original state. This is a non-generic version.
/// </summary>
/// <param name="dbSet">The <see cref="DbSet"/> to attach to.</param>
/// <param name="current">The current entity.</param>
/// <param name="original">The original entity.</param>
/// <param name="dbContext">The corresponding <see cref="DbContext"/></param>
public static void AttachAsModified(this DbSet dbSet, object current, object original, DbContext dbContext)
{
if (dbSet == null)
{
throw Error.ArgumentNull("dbSet");
}
if (current == null)
{
throw Error.ArgumentNull("current");
}
if (original == null)
{
throw Error.ArgumentNull("original");
}
if (dbContext == null)
{
throw Error.ArgumentNull("dbContext");
}
DbEntityEntry entityEntry = dbContext.Entry(current);
if (entityEntry.State == EntityState.Detached)
{
dbSet.Attach(current);
}
else
{
entityEntry.State = EntityState.Modified;
}
ObjectContext objectContext = (dbContext as IObjectContextAdapter).ObjectContext;
ObjectStateEntry stateEntry = ObjectContextUtilities.AttachAsModifiedInternal(current, original, objectContext);
if (stateEntry.State != EntityState.Modified)
{
// Ensure that when we leave this method, the entity is in a
// Modified state. For example, if current and original are the
// same, we still need to force the state transition
entityEntry.State = EntityState.Modified;
}
}
/// <summary>
/// Extension method used to attach the specified entity as modified. This overload
/// can be used in cases where the entity has a Timestamp member.
/// </summary>
/// <typeparam name="TEntity">The entity type</typeparam>
/// <param name="dbSet">The <see cref="DbSet"/> to attach to</param>
/// <param name="entity">The current entity</param>
/// <param name="dbContext">The coresponding <see cref="DbContext"/></param>
public static void AttachAsModified<TEntity>(this DbSet<TEntity> dbSet, TEntity entity, DbContext dbContext) where TEntity : class
{
if (dbSet == null)
{
throw Error.ArgumentNull("dbSet");
}
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
if (dbContext == null)
{
throw Error.ArgumentNull("dbContext");
}
DbEntityEntry<TEntity> entityEntry = dbContext.Entry(entity);
if (entityEntry.State == EntityState.Detached)
{
// attach the entity
dbSet.Attach(entity);
}
// transition the entity to the modified state
entityEntry.State = EntityState.Modified;
}
/// <summary>
/// Extension method used to attach the specified entity as modified. This overload
/// can be used in cases where the entity has a Timestamp member. This is a non-generic version
/// </summary>
/// <param name="dbSet">The <see cref="DbSet"/> to attach to</param>
/// <param name="entity">The current entity</param>
/// <param name="dbContext">The coresponding <see cref="DbContext"/></param>
public static void AttachAsModified(this DbSet dbSet, object entity, DbContext dbContext)
{
if (dbSet == null)
{
throw Error.ArgumentNull("dbSet");
}
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
if (dbContext == null)
{
throw Error.ArgumentNull("dbContext");
}
DbEntityEntry entityEntry = dbContext.Entry(entity);
if (entityEntry.State == EntityState.Detached)
{
// attach the entity
dbSet.Attach(entity);
}
// transition the entity to the modified state
entityEntry.State = EntityState.Modified;
}
}
}

View File

@@ -0,0 +1,322 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Controllers;
using Microsoft.Web.Http.Data.EntityFramework.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework
{
[DbMetadataProvider]
public abstract class DbDataController<TContext> : DataController
where TContext : DbContext, new()
{
private TContext _dbContext;
private ObjectContext _refreshContext;
/// <summary>
/// Protected constructor for the abstract class.
/// </summary>
protected DbDataController()
{
}
/// <summary>
/// Gets the <see cref="ObjectContext"/> used for retrieving store values
/// </summary>
private ObjectContext RefreshContext
{
get
{
if (_refreshContext == null)
{
DbContext dbContext = CreateDbContext();
_refreshContext = (dbContext as IObjectContextAdapter).ObjectContext;
}
return _refreshContext;
}
}
/// <summary>
/// Gets the <see cref="DbContext"/>
/// </summary>
protected TContext DbContext
{
get
{
if (_dbContext == null)
{
_dbContext = CreateDbContext();
}
return _dbContext;
}
}
/// <summary>
/// Initializes the <see cref="DbDataController{T}"/>.
/// </summary>
/// <param name="controllerContext">The <see cref="HttpControllerContext"/> for this <see cref="DataController"/>
/// instance. Overrides must call the base method.</param>
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
ObjectContext objectContext = ((IObjectContextAdapter)DbContext).ObjectContext;
// We turn this off, since our deserializer isn't going to create
// the EF proxy types anyways. Proxies only really work if the entities
// are queried on the server.
objectContext.ContextOptions.ProxyCreationEnabled = false;
// Turn off DbContext validation.
DbContext.Configuration.ValidateOnSaveEnabled = false;
// Turn off AutoDetectChanges.
DbContext.Configuration.AutoDetectChangesEnabled = false;
DbContext.Configuration.LazyLoadingEnabled = false;
}
/// <summary>
/// Returns the DbContext object.
/// </summary>
/// <returns>The created DbContext object.</returns>
protected virtual TContext CreateDbContext()
{
return new TContext();
}
/// <summary>
/// This method is called to finalize changes after all the operations in the specified changeset
/// have been invoked. All changes are committed to the DbContext, and any resulting optimistic
/// concurrency errors are processed.
/// </summary>
/// <returns><c>True</c> if the <see cref="ChangeSet"/> was persisted successfully, <c>false</c> otherwise.</returns>
protected override bool PersistChangeSet()
{
return InvokeSaveChanges(true);
}
/// <summary>
/// This method is called to finalize changes after all the operations in the specified changeset
/// have been invoked. All changes are committed to the DbContext.
/// <remarks>If the submit fails due to concurrency conflicts <see cref="ResolveConflicts"/> will be called.
/// If <see cref="ResolveConflicts"/> returns true a single resubmit will be attempted.
/// </remarks>
/// </summary>
/// <param name="conflicts">The list of concurrency conflicts that occurred</param>
/// <returns>Returns <c>true</c> if the <see cref="ChangeSet"/> was persisted successfully, <c>false</c> otherwise.</returns>
protected virtual bool ResolveConflicts(IEnumerable<DbEntityEntry> conflicts)
{
return false;
}
/// <summary>
/// See <see cref="IDisposable"/>.
/// </summary>
/// <param name="disposing">A <see cref="Boolean"/> indicating whether or not the instance is currently disposing.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (DbContext != null)
{
DbContext.Dispose();
}
if (_refreshContext != null)
{
_refreshContext.Dispose();
}
}
base.Dispose(disposing);
}
/// <summary>
/// Called by PersistChangeSet method to save the changes to the database.
/// </summary>
/// <param name="retryOnConflict">Flag indicating whether to retry after resolving conflicts.</param>
/// <returns><c>true</c> if saved successfully and <c>false</c> otherwise.</returns>
private bool InvokeSaveChanges(bool retryOnConflict)
{
try
{
DbContext.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
// Map the operations that could have caused a conflict to an entity.
Dictionary<DbEntityEntry, ChangeSetEntry> operationConflictMap = new Dictionary<DbEntityEntry, ChangeSetEntry>();
foreach (DbEntityEntry conflict in ex.Entries)
{
ChangeSetEntry entry = ChangeSet.ChangeSetEntries.SingleOrDefault(p => Object.ReferenceEquals(p.Entity, conflict.Entity));
if (entry == null)
{
// If we're unable to find the object in our changeset, propagate
// the original exception
throw;
}
operationConflictMap.Add(conflict, entry);
}
SetChangeSetConflicts(operationConflictMap);
// Call out to any user resolve code and resubmit if all conflicts
// were resolved
if (retryOnConflict && ResolveConflicts(ex.Entries))
{
// clear the conflics from the entries
foreach (ChangeSetEntry entry in ChangeSet.ChangeSetEntries)
{
entry.StoreEntity = null;
entry.ConflictMembers = null;
entry.IsDeleteConflict = false;
}
// If all conflicts were resolved attempt a resubmit
return InvokeSaveChanges(retryOnConflict: false);
}
// if there was a conflict but no conflict information was
// extracted to the individual entries, we need to ensure the
// error makes it back to the client
if (!ChangeSet.HasError)
{
throw;
}
return false;
}
return true;
}
/// <summary>
/// Updates each entry in the ChangeSet with its corresponding conflict info.
/// </summary>
/// <param name="operationConflictMap">Map of conflicts to their corresponding operations entries.</param>
private void SetChangeSetConflicts(Dictionary<DbEntityEntry, ChangeSetEntry> operationConflictMap)
{
object storeValue;
EntityKey refreshEntityKey;
ObjectContext objectContext = ((IObjectContextAdapter)DbContext).ObjectContext;
ObjectStateManager objectStateManager = objectContext.ObjectStateManager;
if (objectStateManager == null)
{
throw Error.InvalidOperation(Resource.ObjectStateManagerNotFoundException, DbContext.GetType().Name);
}
foreach (var conflictEntry in operationConflictMap)
{
DbEntityEntry entityEntry = conflictEntry.Key;
ObjectStateEntry stateEntry = objectStateManager.GetObjectStateEntry(entityEntry.Entity);
if (stateEntry.State == EntityState.Unchanged)
{
continue;
}
// Note: we cannot call Refresh StoreWins since this will overwrite Current entity and remove the optimistic concurrency ex.
ChangeSetEntry operationInConflict = conflictEntry.Value;
refreshEntityKey = RefreshContext.CreateEntityKey(stateEntry.EntitySet.Name, stateEntry.Entity);
RefreshContext.TryGetObjectByKey(refreshEntityKey, out storeValue);
operationInConflict.StoreEntity = storeValue;
// StoreEntity will be null if the entity has been deleted in the store (i.e. Delete/Delete conflict)
bool isDeleted = (operationInConflict.StoreEntity == null);
if (isDeleted)
{
operationInConflict.IsDeleteConflict = true;
}
else
{
// Determine which members are in conflict by comparing original values to the current DB values
PropertyDescriptorCollection propDescriptors = TypeDescriptor.GetProperties(operationInConflict.Entity.GetType());
List<string> membersInConflict = new List<string>();
object originalValue;
PropertyDescriptor pd;
for (int i = 0; i < stateEntry.OriginalValues.FieldCount; i++)
{
originalValue = stateEntry.OriginalValues.GetValue(i);
if (originalValue is DBNull)
{
originalValue = null;
}
string propertyName = stateEntry.OriginalValues.GetName(i);
pd = propDescriptors[propertyName];
if (pd == null)
{
// This might happen in the case of a private model
// member that isn't mapped
continue;
}
if (!Object.Equals(originalValue, pd.GetValue(operationInConflict.StoreEntity)))
{
membersInConflict.Add(pd.Name);
}
}
operationInConflict.ConflictMembers = membersInConflict;
}
}
}
/// <summary>
/// Insert an entity into the <see cref="DbContext" />, ensuring its <see cref="EntityState" /> is <see cref="EntityState.Added" />
/// </summary>
/// <param name="entity">The entity to be inserted</param>
protected virtual void InsertEntity(object entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbContext.Set(entity.GetType()).Add(entity);
}
}
/// <summary>
/// Update an entity in the <see cref="DbContext" />, ensuring it is treated as a modified entity
/// </summary>
/// <param name="entity">The entity to be updated</param>
protected virtual void UpdateEntity(object entity)
{
object original = ChangeSet.GetOriginal(entity);
DbSet dbSet = DbContext.Set(entity.GetType());
if (original == null)
{
dbSet.AttachAsModified(entity, DbContext);
}
else
{
dbSet.AttachAsModified(entity, original, DbContext);
}
}
/// <summary>
/// Delete an entity from the <see cref="DbContext" />, ensuring that its <see cref="EntityState" /> is <see cref="EntityState.Deleted" />
/// </summary>
/// <param name="entity">The entity to be deleted</param>
protected virtual void DeleteEntity(object entity)
{
DbEntityEntry entityEntry = DbContext.Entry(entity);
if (entityEntry.State != EntityState.Deleted)
{
entityEntry.State = EntityState.Deleted;
}
else
{
DbContext.Set(entity.GetType()).Attach(entity);
DbContext.Set(entity.GetType()).Remove(entity);
}
}
}
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Error List, point to "Suppress Message(s)", and click
// "In Project Suppression File".
// You do not need to add suppressions to this file manually.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay signed")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Web.Http.Data.EntityFramework.Metadata", Justification = "These types are in their own namespace to match folder structure.")]

View File

@@ -0,0 +1,312 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Web.Http.Controllers;
using Microsoft.Web.Http.Data.EntityFramework.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework
{
/// <summary>
/// Base class for DataControllers operating on LINQ To Entities data models
/// </summary>
/// <typeparam name="TContext">The Type of the LINQ To Entities ObjectContext</typeparam>
[LinqToEntitiesMetadataProvider]
public abstract class LinqToEntitiesDataController<TContext> : DataController where TContext : ObjectContext, new()
{
private TContext _objectContext;
private TContext _refreshContext;
/// <summary>
/// Protected constructor because this is an abstract class
/// </summary>
protected LinqToEntitiesDataController()
{
}
/// <summary>
/// Gets the <see cref="ObjectContext"/>
/// </summary>
protected internal TContext ObjectContext
{
get
{
if (_objectContext == null)
{
_objectContext = CreateObjectContext();
}
return _objectContext;
}
}
/// <summary>
/// Gets the <see cref="ObjectContext"/> used by retrieving store values
/// </summary>
private ObjectContext RefreshContext
{
get
{
if (_refreshContext == null)
{
_refreshContext = CreateObjectContext();
}
return _refreshContext;
}
}
/// <summary>
/// Initializes this <see cref="DataController"/>.
/// </summary>
/// <param name="controllerContext">The <see cref="HttpControllerContext"/> for this <see cref="DataController"/>
/// instance. Overrides must call the base method.</param>
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
// TODO: should we be turning this off categorically? Can we do this only
// for queries?
ObjectContext.ContextOptions.LazyLoadingEnabled = false;
// We turn this off, since our deserializer isn't going to create
// the EF proxy types anyways. Proxies only really work if the entities
// are queried on the server.
ObjectContext.ContextOptions.ProxyCreationEnabled = false;
}
/// <summary>
/// Creates and returns the <see cref="ObjectContext"/> instance that will
/// be used by this provider.
/// </summary>
/// <returns>The ObjectContext</returns>
protected virtual TContext CreateObjectContext()
{
return new TContext();
}
/// <summary>
/// See <see cref="IDisposable"/>.
/// </summary>
/// <param name="disposing">A <see cref="Boolean"/> indicating whether or not the instance is currently disposing.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_objectContext != null)
{
_objectContext.Dispose();
}
if (_refreshContext != null)
{
_refreshContext.Dispose();
}
}
base.Dispose(disposing);
}
/// <summary>
/// This method is called to finalize changes after all the operations in the specified changeset
/// have been invoked. All changes are committed to the ObjectContext, and any resulting optimistic
/// concurrency errors are processed.
/// </summary>
/// <returns>True if the <see cref="ChangeSet"/> was persisted successfully, false otherwise.</returns>
protected override bool PersistChangeSet()
{
return InvokeSaveChanges(true);
}
private bool InvokeSaveChanges(bool retryOnConflict)
{
try
{
ObjectContext.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
// Map the operations that could have caused a conflict to an entity.
Dictionary<ObjectStateEntry, ChangeSetEntry> operationConflictMap = new Dictionary<ObjectStateEntry, ChangeSetEntry>();
foreach (ObjectStateEntry conflict in ex.StateEntries)
{
ChangeSetEntry entry = ChangeSet.ChangeSetEntries.SingleOrDefault(p => Object.ReferenceEquals(p.Entity, conflict.Entity));
if (entry == null)
{
// If we're unable to find the object in our changeset, propagate
// the original exception
throw;
}
operationConflictMap.Add(conflict, entry);
}
SetChangeSetConflicts(operationConflictMap);
// Call out to any user resolve code and resubmit if all conflicts
// were resolved
if (retryOnConflict && ResolveConflicts(ex.StateEntries))
{
// clear the conflics from the entries
foreach (ChangeSetEntry entry in ChangeSet.ChangeSetEntries)
{
entry.StoreEntity = null;
entry.ConflictMembers = null;
entry.IsDeleteConflict = false;
}
// If all conflicts were resolved attempt a resubmit
return InvokeSaveChanges(retryOnConflict: false);
}
// if there was a conflict but no conflict information was
// extracted to the individual entries, we need to ensure the
// error makes it back to the client
if (!ChangeSet.HasError)
{
throw;
}
return false;
}
return true;
}
/// <summary>
/// This method is called to finalize changes after all the operations in the specified changeset
/// have been invoked. All changes are committed to the ObjectContext.
/// <remarks>If the submit fails due to concurrency conflicts <see cref="ResolveConflicts"/> will be called.
/// If <see cref="ResolveConflicts"/> returns true a single resubmit will be attempted.
/// </remarks>
/// </summary>
/// <param name="conflicts">The list of concurrency conflicts that occurred</param>
/// <returns>Returns <c>true</c> if the <see cref="ChangeSet"/> was persisted successfully, <c>false</c> otherwise.</returns>
protected virtual bool ResolveConflicts(IEnumerable<ObjectStateEntry> conflicts)
{
return false;
}
/// <summary>
/// Insert an entity into the <see cref="ObjectContext" />, ensuring its <see cref="EntityState" /> is <see cref="EntityState.Added" />
/// </summary>
/// <typeparam name="TEntity">The entity type</typeparam>
/// <param name="entity">The entity to be inserted</param>
protected virtual void InsertEntity<TEntity>(TEntity entity) where TEntity : class
{
ObjectStateEntry stateEntry;
if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry) &&
stateEntry.State != EntityState.Added)
{
ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Added);
}
else
{
ObjectContext.CreateObjectSet<TEntity>().AddObject(entity);
}
}
/// <summary>
/// Update an entity in the <see cref="ObjectContext" />, ensuring it is treated as a modified entity
/// </summary>
/// <typeparam name="TEntity">The entity type</typeparam>
/// <param name="entity">The entity to be updated</param>
protected virtual void UpdateEntity<TEntity>(TEntity entity) where TEntity : class
{
TEntity original = ChangeSet.GetOriginal(entity);
ObjectSet<TEntity> objectSet = ObjectContext.CreateObjectSet<TEntity>();
if (original == null)
{
objectSet.AttachAsModified(entity);
}
else
{
objectSet.AttachAsModified(entity, original);
}
}
/// <summary>
/// Delete an entity from the <see cref="ObjectContext" />, ensuring that its <see cref="EntityState" /> is <see cref="EntityState.Deleted" />
/// </summary>
/// <typeparam name="TEntity">The entity type</typeparam>
/// <param name="entity">The entity to be deleted</param>
protected virtual void DeleteEntity<TEntity>(TEntity entity) where TEntity : class
{
ObjectStateEntry stateEntry;
if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry) &&
stateEntry.State != EntityState.Deleted)
{
ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
}
else
{
ObjectSet<TEntity> objectSet = ObjectContext.CreateObjectSet<TEntity>();
objectSet.Attach(entity);
objectSet.DeleteObject(entity);
}
}
/// <summary>
/// Updates each entry in the ChangeSet with its corresponding conflict info.
/// </summary>
/// <param name="operationConflictMap">Map of conflicts to their corresponding operations entries.</param>
private void SetChangeSetConflicts(Dictionary<ObjectStateEntry, ChangeSetEntry> operationConflictMap)
{
object storeValue;
EntityKey refreshEntityKey;
foreach (var conflictEntry in operationConflictMap)
{
ObjectStateEntry stateEntry = conflictEntry.Key;
if (stateEntry.State == EntityState.Unchanged)
{
continue;
}
// Note: we cannot call Refresh StoreWins since this will overwrite Current entity and remove the optimistic concurrency ex.
ChangeSetEntry operationInConflict = conflictEntry.Value;
refreshEntityKey = RefreshContext.CreateEntityKey(stateEntry.EntitySet.Name, stateEntry.Entity);
RefreshContext.TryGetObjectByKey(refreshEntityKey, out storeValue);
operationInConflict.StoreEntity = storeValue;
// StoreEntity will be null if the entity has been deleted in the store (i.e. Delete/Delete conflict)
bool isDeleted = (operationInConflict.StoreEntity == null);
if (isDeleted)
{
operationInConflict.IsDeleteConflict = true;
}
else
{
// Determine which members are in conflict by comparing original values to the current DB values
PropertyDescriptorCollection propDescriptors = TypeDescriptor.GetProperties(operationInConflict.Entity.GetType());
List<string> membersInConflict = new List<string>();
object originalValue;
PropertyDescriptor pd;
for (int i = 0; i < stateEntry.OriginalValues.FieldCount; i++)
{
originalValue = stateEntry.OriginalValues.GetValue(i);
if (originalValue is DBNull)
{
originalValue = null;
}
string propertyName = stateEntry.OriginalValues.GetName(i);
pd = propDescriptors[propertyName];
if (pd == null)
{
// This might happen in the case of a private model
// member that isn't mapped
continue;
}
if (!Object.Equals(originalValue, pd.GetValue(operationInConflict.StoreEntity)))
{
membersInConflict.Add(pd.Name);
}
}
operationInConflict.ConflictMembers = membersInConflict;
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Information about an Association
/// </summary>
internal sealed class AssociationInfo
{
/// <summary>
/// The name of the association
/// </summary>
public string Name { get; set; }
/// <summary>
/// The key members on the FK side of the association
/// </summary>
public string[] ThisKey { get; set; }
/// <summary>
/// The key members on the non-FK side of the association
/// </summary>
public string[] OtherKey { get; set; }
/// <summary>
/// The foreign key role name for this association
/// </summary>
public string FKRole { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this association can have a
/// multiplicity of zero
/// </summary>
public bool IsRequired { get; set; }
}
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Data.Entity;
using System.Web.Http;
using Microsoft.Web.Http.Data.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Attribute applied to a <see cref="DbDataController{DbContext}"/> that exposes LINQ to Entities mapped
/// Types.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class DbMetadataProviderAttribute : MetadataProviderAttribute
{
private Type _dbContextType;
/// <summary>
/// Default constructor. Using this constructor, the Type of the LINQ To Entities
/// DbContext will be inferred from the <see cref="DataController"/> the
/// attribute is applied to.
/// </summary>
public DbMetadataProviderAttribute()
: base(typeof(LinqToEntitiesMetadataProvider))
{
}
/// <summary>
/// Constructs an attribute for the specified LINQ To Entities
/// DbContext Type.
/// </summary>
/// <param name="dbContextType">The LINQ To Entities ObjectContext Type.</param>
public DbMetadataProviderAttribute(Type dbContextType)
: base(typeof(LinqToEntitiesMetadataProvider))
{
_dbContextType = dbContextType;
}
/// <summary>
/// The Linq To Entities DbContext Type.
/// </summary>
public Type DbContextType
{
get { return _dbContextType; }
}
/// <summary>
/// This method creates an instance of the <see cref="MetadataProvider"/>.
/// </summary>
/// <param name="controllerType">The <see cref="DataController"/> Type to create a metadata provider for.</param>
/// <param name="parent">The existing parent metadata provider.</param>
/// <returns>The metadata provider.</returns>
public override MetadataProvider CreateProvider(Type controllerType, MetadataProvider parent)
{
if (controllerType == null)
{
throw Error.ArgumentNull("controllerType");
}
if (_dbContextType == null)
{
_dbContextType = GetContextType(controllerType);
}
if (!typeof(DbContext).IsAssignableFrom(_dbContextType))
{
throw Error.InvalidOperation(Resource.InvalidDbMetadataProviderSpecification, _dbContextType);
}
return new LinqToEntitiesMetadataProvider(_dbContextType, parent, true);
}
/// <summary>
/// Extracts the context type from the specified <paramref name="dataControllerType"/>.
/// </summary>
/// <param name="dataControllerType">A LINQ to Entities data controller type.</param>
/// <returns>The type of the object context.</returns>
private static Type GetContextType(Type dataControllerType)
{
Type efDataControllerType = dataControllerType.BaseType;
while (!efDataControllerType.IsGenericType || efDataControllerType.GetGenericTypeDefinition() != typeof(DbDataController<>))
{
if (efDataControllerType == typeof(object))
{
throw Error.InvalidOperation(Resource.InvalidMetadataProviderSpecification, typeof(DbMetadataProviderAttribute).Name, dataControllerType.Name, typeof(DbDataController<>).Name);
}
efDataControllerType = efDataControllerType.BaseType;
}
return efDataControllerType.GetGenericArguments()[0];
}
}
}

View File

@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Metadata.Edm;
using Microsoft.Web.Http.Data.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
internal class LinqToEntitiesMetadataProvider : MetadataProvider
{
private static ConcurrentDictionary<Type, LinqToEntitiesTypeDescriptionContext> _tdpContextMap = new ConcurrentDictionary<Type, LinqToEntitiesTypeDescriptionContext>();
private readonly LinqToEntitiesTypeDescriptionContext _typeDescriptionContext;
private readonly bool _isDbContext;
private Dictionary<Type, ICustomTypeDescriptor> _descriptors = new Dictionary<Type, ICustomTypeDescriptor>();
public LinqToEntitiesMetadataProvider(Type contextType, MetadataProvider parent, bool isDbContext)
: base(parent)
{
_isDbContext = isDbContext;
_typeDescriptionContext = _tdpContextMap.GetOrAdd(contextType, type =>
{
// create and cache a context for this provider type
return new LinqToEntitiesTypeDescriptionContext(contextType, _isDbContext);
});
}
/// <summary>
/// Returns a custom type descriptor for the specified type (either an entity or complex type).
/// </summary>
/// <param name="objectType">Type of object for which we need the descriptor</param>
/// <param name="parent">The parent type descriptor</param>
/// <returns>Custom type description for the specified type</returns>
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, ICustomTypeDescriptor parent)
{
// No need to deal with concurrency... Worst case scenario we have multiple
// instances of this thing.
ICustomTypeDescriptor td = null;
if (!_descriptors.TryGetValue(objectType, out td))
{
// call into base so the TDs are chained
parent = base.GetTypeDescriptor(objectType, parent);
StructuralType edmType = _typeDescriptionContext.GetEdmType(objectType);
if (edmType != null &&
(edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType || edmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType))
{
// only add an LTE TypeDescriptor if the type is an EF Entity or ComplexType
td = new LinqToEntitiesTypeDescriptor(_typeDescriptionContext, edmType, parent);
}
else
{
td = parent;
}
_descriptors[objectType] = td;
}
return td;
}
public override bool LookUpIsEntityType(Type type)
{
StructuralType edmType = _typeDescriptionContext.GetEdmType(type);
if (edmType != null && edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
{
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Data.Objects;
using System.Web.Http;
using Microsoft.Web.Http.Data.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Attribute applied to a <see cref="DataController"/> that exposes LINQ to Entities mapped
/// Types.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class LinqToEntitiesMetadataProviderAttribute : MetadataProviderAttribute
{
private Type _objectContextType;
/// <summary>
/// Default constructor. Using this constructor, the Type of the LINQ To Entities
/// ObjectContext will be inferred from the <see cref="DataController"/> the
/// attribute is applied to.
/// </summary>
public LinqToEntitiesMetadataProviderAttribute()
: base(typeof(LinqToEntitiesMetadataProvider))
{
}
/// <summary>
/// Constructs an attribute for the specified LINQ To Entities
/// ObjectContext Type.
/// </summary>
/// <param name="objectContextType">The LINQ To Entities ObjectContext Type.</param>
public LinqToEntitiesMetadataProviderAttribute(Type objectContextType)
: base(typeof(LinqToEntitiesMetadataProvider))
{
_objectContextType = objectContextType;
}
/// <summary>
/// The Linq To Entities ObjectContext Type.
/// </summary>
public Type ObjectContextType
{
get { return _objectContextType; }
}
/// <summary>
/// This method creates an instance of the <see cref="MetadataProvider"/>.
/// </summary>
/// <param name="controllerType">The <see cref="DataController"/> Type to create a metadata provider for.</param>
/// <param name="parent">The existing parent metadata provider.</param>
/// <returns>The metadata provider.</returns>
public override MetadataProvider CreateProvider(Type controllerType, MetadataProvider parent)
{
if (controllerType == null)
{
throw Error.ArgumentNull("controllerType");
}
if (_objectContextType == null)
{
_objectContextType = GetContextType(controllerType);
}
if (!typeof(ObjectContext).IsAssignableFrom(_objectContextType))
{
throw Error.InvalidOperation(Resource.InvalidLinqToEntitiesMetadataProviderSpecification, _objectContextType);
}
return new LinqToEntitiesMetadataProvider(_objectContextType, parent, false);
}
/// <summary>
/// Extracts the context type from the specified <paramref name="dataControllerType"/>.
/// </summary>
/// <param name="dataControllerType">A LINQ to Entities data controller type.</param>
/// <returns>The type of the object context.</returns>
private static Type GetContextType(Type dataControllerType)
{
Type efDataControllerType = dataControllerType.BaseType;
while (!efDataControllerType.IsGenericType || efDataControllerType.GetGenericTypeDefinition() != typeof(LinqToEntitiesDataController<>))
{
if (efDataControllerType == typeof(object))
{
throw Error.InvalidOperation(Resource.InvalidMetadataProviderSpecification, typeof(LinqToEntitiesMetadataProviderAttribute).Name, dataControllerType.Name, typeof(LinqToEntitiesDataController<>).Name);
}
efDataControllerType = efDataControllerType.BaseType;
}
return efDataControllerType.GetGenericArguments()[0];
}
}
}

View File

@@ -0,0 +1,150 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Data.Metadata.Edm;
using System.Globalization;
using System.Linq;
using System.Web.Http;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Metadata context for LINQ To Entities controllers
/// </summary>
internal class LinqToEntitiesTypeDescriptionContext : TypeDescriptionContextBase
{
private readonly Type _contextType;
private readonly bool _isDbContext;
private ConcurrentDictionary<string, AssociationInfo> _associationMap = new ConcurrentDictionary<string, AssociationInfo>();
private MetadataWorkspace _metadataWorkspace;
/// <summary>
/// Constructor that accepts a LINQ To Entities context type
/// </summary>
/// <param name="contextType">The ObjectContext Type</param>
/// <param name="isDbContext">Set to <c>true</c> if context is a database context.</param>
public LinqToEntitiesTypeDescriptionContext(Type contextType, bool isDbContext)
{
if (contextType == null)
{
throw Error.ArgumentNull("contextType");
}
_contextType = contextType;
_isDbContext = isDbContext;
}
/// <summary>
/// Gets the MetadataWorkspace for the context
/// </summary>
public MetadataWorkspace MetadataWorkspace
{
get
{
if (_metadataWorkspace == null)
{
// we only support embedded mappings
_metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspace(_contextType, _isDbContext);
}
return _metadataWorkspace;
}
}
/// <summary>
/// Returns the <see cref="StructuralType"/> that corresponds to the given CLR type
/// </summary>
/// <param name="clrType">The CLR type</param>
/// <returns>The StructuralType that corresponds to the given CLR type</returns>
public StructuralType GetEdmType(Type clrType)
{
return ObjectContextUtilities.GetEdmType(MetadataWorkspace, clrType);
}
/// <summary>
/// Returns the association information for the specified navigation property.
/// </summary>
/// <param name="navigationProperty">The navigation property to return association information for</param>
/// <returns>The association info</returns>
internal AssociationInfo GetAssociationInfo(NavigationProperty navigationProperty)
{
return _associationMap.GetOrAdd(navigationProperty.RelationshipType.FullName, associationName =>
{
AssociationType associationType = (AssociationType)navigationProperty.RelationshipType;
if (!associationType.ReferentialConstraints.Any())
{
// We only support EF models where FK info is part of the model.
throw Error.NotSupported(Resource.LinqToEntitiesProvider_UnableToRetrieveAssociationInfo, associationName);
}
string toRoleName = associationType.ReferentialConstraints[0].ToRole.Name;
AssociationInfo associationInfo = new AssociationInfo()
{
FKRole = toRoleName,
Name = GetAssociationName(navigationProperty, toRoleName),
ThisKey = associationType.ReferentialConstraints[0].ToProperties.Select(p => p.Name).ToArray(),
OtherKey = associationType.ReferentialConstraints[0].FromProperties.Select(p => p.Name).ToArray(),
IsRequired = associationType.RelationshipEndMembers[0].RelationshipMultiplicity == RelationshipMultiplicity.One
};
return associationInfo;
});
}
/// <summary>
/// Creates an AssociationAttribute for the specified navigation property
/// </summary>
/// <param name="navigationProperty">The navigation property that corresponds to the association (it identifies the end points)</param>
/// <returns>A new AssociationAttribute that describes the given navigation property association</returns>
internal AssociationAttribute CreateAssociationAttribute(NavigationProperty navigationProperty)
{
AssociationInfo assocInfo = GetAssociationInfo(navigationProperty);
bool isForeignKey = navigationProperty.FromEndMember.Name == assocInfo.FKRole;
string thisKey;
string otherKey;
if (isForeignKey)
{
thisKey = String.Join(",", assocInfo.ThisKey);
otherKey = String.Join(",", assocInfo.OtherKey);
}
else
{
otherKey = String.Join(",", assocInfo.ThisKey);
thisKey = String.Join(",", assocInfo.OtherKey);
}
AssociationAttribute assocAttrib = new AssociationAttribute(assocInfo.Name, thisKey, otherKey);
assocAttrib.IsForeignKey = isForeignKey;
return assocAttrib;
}
/// <summary>
/// Returns a unique association name for the specified navigation property.
/// </summary>
/// <param name="navigationProperty">The navigation property</param>
/// <param name="foreignKeyRoleName">The foreign key role name for the property's association</param>
/// <returns>A unique association name for the specified navigation property.</returns>
private string GetAssociationName(NavigationProperty navigationProperty, string foreignKeyRoleName)
{
RelationshipEndMember fromMember = navigationProperty.FromEndMember;
RelationshipEndMember toMember = navigationProperty.ToEndMember;
RefType toRefType = toMember.TypeUsage.EdmType as RefType;
EntityType toEntityType = toRefType.ElementType as EntityType;
RefType fromRefType = fromMember.TypeUsage.EdmType as RefType;
EntityType fromEntityType = fromRefType.ElementType as EntityType;
bool isForeignKey = navigationProperty.FromEndMember.Name == foreignKeyRoleName;
string fromTypeName = isForeignKey ? fromEntityType.Name : toEntityType.Name;
string toTypeName = isForeignKey ? toEntityType.Name : fromEntityType.Name;
// names are always formatted non-FK side type name followed by FK side type name
string associationName = String.Format(CultureInfo.InvariantCulture, "{0}_{1}", toTypeName, fromTypeName);
associationName = MakeUniqueName(associationName, _associationMap.Values.Select(p => p.Name));
return associationName;
}
}
}

View File

@@ -0,0 +1,274 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// CustomTypeDescriptor for LINQ To Entities
/// </summary>
internal class LinqToEntitiesTypeDescriptor : TypeDescriptorBase
{
private readonly LinqToEntitiesTypeDescriptionContext _typeDescriptionContext;
private readonly StructuralType _edmType;
private readonly EdmMember _timestampMember;
private readonly HashSet<EdmMember> _foreignKeyMembers;
private readonly bool _keyIsEditable;
/// <summary>
/// Constructor taking a metadata context, an structural type, and a parent custom type descriptor
/// </summary>
/// <param name="typeDescriptionContext">The <see cref="LinqToEntitiesTypeDescriptionContext"/> context.</param>
/// <param name="edmType">The <see cref="StructuralType"/> type (can be an entity or complex type).</param>
/// <param name="parent">The parent custom type descriptor.</param>
public LinqToEntitiesTypeDescriptor(LinqToEntitiesTypeDescriptionContext typeDescriptionContext, StructuralType edmType, ICustomTypeDescriptor parent)
: base(parent)
{
_typeDescriptionContext = typeDescriptionContext;
_edmType = edmType;
EdmMember[] timestampMembers = _edmType.Members.Where(p => ObjectContextUtilities.IsConcurrencyTimestamp(p)).ToArray();
if (timestampMembers.Length == 1)
{
_timestampMember = timestampMembers[0];
}
if (edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
{
// if any FK member of any association is also part of the primary key, then the key cannot be marked
// Editable(false)
EntityType entityType = (EntityType)edmType;
_foreignKeyMembers = new HashSet<EdmMember>(entityType.NavigationProperties.SelectMany(p => p.GetDependentProperties()));
foreach (EdmProperty foreignKeyMember in _foreignKeyMembers)
{
if (entityType.KeyMembers.Contains(foreignKeyMember))
{
_keyIsEditable = true;
break;
}
}
}
}
/// <summary>
/// Gets the metadata context
/// </summary>
public LinqToEntitiesTypeDescriptionContext TypeDescriptionContext
{
get { return _typeDescriptionContext; }
}
/// <summary>
/// Gets the Edm type
/// </summary>
private StructuralType EdmType
{
get { return _edmType; }
}
/// <summary>
/// Returns a collection of all the <see cref="Attribute"/>s we infer from the metadata associated
/// with the metadata member corresponding to the given property descriptor
/// </summary>
/// <param name="pd">A <see cref="PropertyDescriptor"/> to examine</param>
/// <returns>A collection of attributes inferred from the metadata in the given descriptor.</returns>
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "TODO refactor")]
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "TODO refactor")]
protected override IEnumerable<Attribute> GetMemberAttributes(PropertyDescriptor pd)
{
List<Attribute> attributes = new List<Attribute>();
// Exclude any EntityState, EntityReference, etc. members
if (ShouldExcludeEntityMember(pd))
{
// for these members, we don't want to do any attribute inference
return attributes.ToArray();
}
EditableAttribute editableAttribute = null;
bool inferRoundtripOriginalAttribute = false;
bool hasKeyAttribute = (pd.Attributes[typeof(KeyAttribute)] != null);
bool isEntity = EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
if (isEntity)
{
EntityType entityType = (EntityType)EdmType;
EdmMember keyMember = entityType.KeyMembers.SingleOrDefault(k => k.Name == pd.Name);
if (keyMember != null && !hasKeyAttribute)
{
attributes.Add(new KeyAttribute());
hasKeyAttribute = true;
}
}
EdmProperty member = EdmType.Members.SingleOrDefault(p => p.Name == pd.Name) as EdmProperty;
if (member != null)
{
if (hasKeyAttribute)
{
// key members must always be roundtripped
inferRoundtripOriginalAttribute = true;
// key members that aren't also FK members are non-editable (but allow an initial value)
if (!_keyIsEditable)
{
editableAttribute = new EditableAttribute(false) { AllowInitialValue = true };
}
}
// Check if the member is DB generated and add the DatabaseGeneratedAttribute to it if not already present.
if (pd.Attributes[typeof(DatabaseGeneratedAttribute)] == null)
{
MetadataProperty md = ObjectContextUtilities.GetStoreGeneratedPattern(member);
if (md != null)
{
if ((string)md.Value == "Computed")
{
attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Computed));
}
else if ((string)md.Value == "Identity")
{
attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity));
}
}
}
// Add implicit ConcurrencyCheck attribute to metadata if ConcurrencyMode is anything other than ConcurrencyMode.None
Facet facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "ConcurrencyMode");
if (facet != null && facet.Value != null && (ConcurrencyMode)facet.Value != ConcurrencyMode.None &&
pd.Attributes[typeof(ConcurrencyCheckAttribute)] == null)
{
attributes.Add(new ConcurrencyCheckAttribute());
inferRoundtripOriginalAttribute = true;
}
bool isStringType = pd.PropertyType == typeof(string) || pd.PropertyType == typeof(char[]);
// Add Required attribute to metdata if the member cannot be null and it is either a reference type or a Nullable<T>
if (!member.Nullable && (!pd.PropertyType.IsValueType || IsNullableType(pd.PropertyType)) &&
pd.Attributes[typeof(RequiredAttribute)] == null)
{
attributes.Add(new RequiredAttribute());
}
if (isStringType &&
pd.Attributes[typeof(StringLengthAttribute)] == null)
{
facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "MaxLength");
if (facet != null && facet.Value != null && facet.Value.GetType() == typeof(int))
{
// need to test for Type int, since the value can also be of type
// System.Data.Metadata.Edm.EdmConstants.Unbounded
int maxLength = (int)facet.Value;
attributes.Add(new StringLengthAttribute(maxLength));
}
}
bool hasTimestampAttribute = (pd.Attributes[typeof(TimestampAttribute)] != null);
if (_timestampMember == member && !hasTimestampAttribute)
{
attributes.Add(new TimestampAttribute());
hasTimestampAttribute = true;
}
// All members marked with TimestampAttribute (inferred or explicit) need to
// have [Editable(false)] and [RoundtripOriginal] applied
if (hasTimestampAttribute)
{
inferRoundtripOriginalAttribute = true;
if (editableAttribute == null)
{
editableAttribute = new EditableAttribute(false);
}
}
// Add RTO to this member if required. If this type has a timestamp
// member that member should be the ONLY member we apply RTO to.
// Dont apply RTO if it is an association member.
bool isForeignKeyMember = _foreignKeyMembers != null && _foreignKeyMembers.Contains(member);
if ((_timestampMember == null || _timestampMember == member) &&
(inferRoundtripOriginalAttribute || isForeignKeyMember) &&
pd.Attributes[typeof(AssociationAttribute)] == null)
{
if (pd.Attributes[typeof(RoundtripOriginalAttribute)] == null)
{
attributes.Add(new RoundtripOriginalAttribute());
}
}
}
// Add the Editable attribute if required
if (editableAttribute != null && pd.Attributes[typeof(EditableAttribute)] == null)
{
attributes.Add(editableAttribute);
}
if (isEntity)
{
AddAssociationAttributes(pd, attributes);
}
return attributes.ToArray();
}
/// <summary>
/// Determines whether the specified property is an Entity member that
/// should be excluded.
/// </summary>
/// <param name="pd">The property to check.</param>
/// <returns>True if the property should be excluded, false otherwise.</returns>
internal static bool ShouldExcludeEntityMember(PropertyDescriptor pd)
{
// exclude EntityState members
if (pd.PropertyType == typeof(EntityState) &&
(pd.ComponentType == typeof(EntityObject) || typeof(IEntityChangeTracker).IsAssignableFrom(pd.ComponentType)))
{
return true;
}
// exclude entity reference properties
if (typeof(EntityReference).IsAssignableFrom(pd.PropertyType))
{
return true;
}
return false;
}
/// <summary>
/// Add AssociationAttribute if required for the specified property
/// </summary>
/// <param name="pd">The property</param>
/// <param name="attributes">The list of attributes to append to</param>
private void AddAssociationAttributes(PropertyDescriptor pd, List<Attribute> attributes)
{
EntityType entityType = (EntityType)EdmType;
NavigationProperty navProperty = entityType.NavigationProperties.Where(n => n.Name == pd.Name).SingleOrDefault();
if (navProperty != null)
{
bool isManyToMany = navProperty.RelationshipType.RelationshipEndMembers[0].RelationshipMultiplicity == RelationshipMultiplicity.Many &&
navProperty.RelationshipType.RelationshipEndMembers[1].RelationshipMultiplicity == RelationshipMultiplicity.Many;
if (!isManyToMany)
{
AssociationAttribute assocAttrib = (AssociationAttribute)pd.Attributes[typeof(System.ComponentModel.DataAnnotations.AssociationAttribute)];
if (assocAttrib == null)
{
assocAttrib = TypeDescriptionContext.CreateAssociationAttribute(navProperty);
attributes.Add(assocAttrib);
}
}
}
}
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
internal class MetadataPropertyDescriptorWrapper : PropertyDescriptor
{
private readonly PropertyDescriptor _descriptor;
public MetadataPropertyDescriptorWrapper(PropertyDescriptor descriptor, Attribute[] attrs)
: base(descriptor, attrs)
{
_descriptor = descriptor;
}
public override Type ComponentType
{
get { return _descriptor.ComponentType; }
}
public override bool IsReadOnly
{
get { return _descriptor.IsReadOnly; }
}
public override Type PropertyType
{
get { return _descriptor.PropertyType; }
}
public override bool SupportsChangeEvents
{
get { return _descriptor.SupportsChangeEvents; }
}
public override void AddValueChanged(object component, EventHandler handler)
{
_descriptor.AddValueChanged(component, handler);
}
public override bool CanResetValue(object component)
{
return _descriptor.CanResetValue(component);
}
public override object GetValue(object component)
{
return _descriptor.GetValue(component);
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
_descriptor.RemoveValueChanged(component, handler);
}
public override void ResetValue(object component)
{
_descriptor.ResetValue(component);
}
public override void SetValue(object component, object value)
{
_descriptor.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return _descriptor.ShouldSerializeValue(component);
}
}
}

View File

@@ -0,0 +1,210 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Data.Mapping;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web.Http;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// EF metadata utilities class.
/// </summary>
internal static class MetadataWorkspaceUtilities
{
/// <summary>
/// Creates a metadata workspace for the specified context.
/// </summary>
/// <param name="contextType">The type of the object context.</param>
/// <param name="isDbContext">Set to <c>true</c> if context is a database context.</param>
/// <returns>The metadata workspace.</returns>
public static MetadataWorkspace CreateMetadataWorkspace(Type contextType, bool isDbContext)
{
MetadataWorkspace metadataWorkspace = null;
if (!isDbContext)
{
metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(ObjectContext));
}
else
{
metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(System.Data.Entity.DbContext));
if (metadataWorkspace == null && typeof(System.Data.Entity.DbContext).IsAssignableFrom(contextType))
{
if (contextType.GetConstructor(Type.EmptyTypes) == null)
{
throw Error.InvalidOperation(Resource.DefaultCtorNotFound, contextType.FullName);
}
try
{
System.Data.Entity.DbContext dbContext = Activator.CreateInstance(contextType) as System.Data.Entity.DbContext;
ObjectContext objectContext = (dbContext as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext;
metadataWorkspace = objectContext.MetadataWorkspace;
}
catch (Exception efException)
{
throw Error.InvalidOperation(efException, Resource.MetadataWorkspaceNotFound, contextType.FullName);
}
}
}
if (metadataWorkspace == null)
{
throw Error.InvalidOperation(Resource.LinqToEntitiesProvider_UnableToRetrieveMetadata, contextType.Name);
}
else
{
return metadataWorkspace;
}
}
/// <summary>
/// Creates the MetadataWorkspace for the given context type and base context type.
/// </summary>
/// <param name="contextType">The type of the context.</param>
/// <param name="baseContextType">The base context type (DbContext or ObjectContext).</param>
/// <returns>The generated <see cref="MetadataWorkspace"/></returns>
public static MetadataWorkspace CreateMetadataWorkspaceFromResources(Type contextType, Type baseContextType)
{
// get the set of embedded mapping resources for the target assembly and create
// a metadata workspace info for each group
IEnumerable<string> metadataResourcePaths = FindMetadataResources(contextType.Assembly);
IEnumerable<MetadataWorkspaceInfo> workspaceInfos = GetMetadataWorkspaceInfos(metadataResourcePaths);
// Search for the correct EntityContainer by name and if found, create
// a comlete MetadataWorkspace and return it
foreach (var workspaceInfo in workspaceInfos)
{
EdmItemCollection edmItemCollection = new EdmItemCollection(workspaceInfo.Csdl);
Type currentType = contextType;
while (currentType != baseContextType && currentType != typeof(object))
{
EntityContainer container;
if (edmItemCollection.TryGetEntityContainer(currentType.Name, out container))
{
StoreItemCollection store = new StoreItemCollection(workspaceInfo.Ssdl);
StorageMappingItemCollection mapping = new StorageMappingItemCollection(edmItemCollection, store, workspaceInfo.Msl);
MetadataWorkspace workspace = new MetadataWorkspace();
workspace.RegisterItemCollection(edmItemCollection);
workspace.RegisterItemCollection(store);
workspace.RegisterItemCollection(mapping);
workspace.RegisterItemCollection(new ObjectItemCollection());
return workspace;
}
currentType = currentType.BaseType;
}
}
return null;
}
/// <summary>
/// Gets the specified resource paths as metadata workspace info objects.
/// </summary>
/// <param name="resourcePaths">The metadata resource paths.</param>
/// <returns>The metadata workspace info objects.</returns>
private static IEnumerable<MetadataWorkspaceInfo> GetMetadataWorkspaceInfos(IEnumerable<string> resourcePaths)
{
// for file paths, you would want to group without the path or the extension like Path.GetFileNameWithoutExtension, but resource names can contain
// forbidden path chars, so don't use it on resource names
foreach (var group in resourcePaths.GroupBy(p => p.Substring(0, p.LastIndexOf('.')), StringComparer.InvariantCultureIgnoreCase))
{
yield return MetadataWorkspaceInfo.Create(group);
}
}
/// <summary>
/// Find all the EF metadata resources.
/// </summary>
/// <param name="assembly">The assembly to find the metadata resources in.</param>
/// <returns>The metadata paths that were found.</returns>
private static IEnumerable<string> FindMetadataResources(Assembly assembly)
{
List<string> result = new List<string>();
foreach (string name in assembly.GetManifestResourceNames())
{
if (MetadataWorkspaceInfo.IsMetadata(name))
{
result.Add(String.Format(CultureInfo.InvariantCulture, "res://{0}/{1}", assembly.FullName, name));
}
}
return result;
}
/// <summary>
/// Represents the paths for a single metadata workspace.
/// </summary>
private class MetadataWorkspaceInfo
{
private const string CsdlExtension = ".csdl";
private const string MslExtension = ".msl";
private const string SsdlExtension = ".ssdl";
public MetadataWorkspaceInfo(string csdlPath, string mslPath, string ssdlPath)
{
if (csdlPath == null)
{
throw Error.ArgumentNull("csdlPath");
}
if (mslPath == null)
{
throw Error.ArgumentNull("mslPath");
}
if (ssdlPath == null)
{
throw Error.ArgumentNull("ssdlPath");
}
Csdl = csdlPath;
Msl = mslPath;
Ssdl = ssdlPath;
}
public string Csdl { get; private set; }
public string Msl { get; private set; }
public string Ssdl { get; private set; }
public static MetadataWorkspaceInfo Create(IEnumerable<string> paths)
{
string csdlPath = null;
string mslPath = null;
string ssdlPath = null;
foreach (string path in paths)
{
if (path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase))
{
csdlPath = path;
}
else if (path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase))
{
mslPath = path;
}
else if (path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase))
{
ssdlPath = path;
}
}
return new MetadataWorkspaceInfo(csdlPath, mslPath, ssdlPath);
}
public static bool IsMetadata(string path)
{
return path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase);
}
}
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Base class for LTS and EF type description contexts
/// </summary>
internal abstract class TypeDescriptionContextBase
{
/// <summary>
/// Given a suggested name and a collection of existing names, this method
/// creates a unique name by appending a numerix suffix as required.
/// </summary>
/// <param name="suggested">The desired name</param>
/// <param name="existing">Collection of existing names</param>
/// <returns>The unique name</returns>
protected static string MakeUniqueName(string suggested, IEnumerable<string> existing)
{
int i = 1;
string currSuggestion = suggested;
while (existing.Contains(currSuggestion))
{
currSuggestion = suggested + (i++).ToString(CultureInfo.InvariantCulture);
}
return currSuggestion;
}
}
}

View File

@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// CustomTypeDescriptor base type shared by LINQ To SQL and LINQ To Entities
/// </summary>
internal abstract class TypeDescriptorBase : CustomTypeDescriptor
{
private PropertyDescriptorCollection _properties;
/// <summary>
/// Main constructor that accepts the parent custom type descriptor
/// </summary>
/// <param name="parent">The parent custom type descriptor.</param>
public TypeDescriptorBase(ICustomTypeDescriptor parent)
: base(parent)
{
}
/// <summary>
/// Override of the <see cref="CustomTypeDescriptor.GetProperties()"/> to obtain the list
/// of properties for this type.
/// </summary>
/// <remarks>
/// This method is overridden so that it can merge this class's parent attributes with those
/// it infers from the DAL-specific attributes.
/// </remarks>
/// <returns>A list of properties for this type</returns>
public sealed override PropertyDescriptorCollection GetProperties()
{
// No need to lock anything... Worst case scenario we create the properties multiple times.
if (_properties == null)
{
// Get properties from our parent
PropertyDescriptorCollection originalCollection = base.GetProperties();
bool customDescriptorsCreated = false;
List<PropertyDescriptor> tempPropertyDescriptors = new List<PropertyDescriptor>();
// for every property exposed by our parent, see if we have additional metadata to add
foreach (PropertyDescriptor propDescriptor in originalCollection)
{
Attribute[] newMetadata = GetMemberAttributes(propDescriptor).ToArray();
if (newMetadata.Length > 0)
{
tempPropertyDescriptors.Add(new MetadataPropertyDescriptorWrapper(propDescriptor, newMetadata));
customDescriptorsCreated = true;
}
else
{
tempPropertyDescriptors.Add(propDescriptor);
}
}
if (customDescriptorsCreated)
{
_properties = new PropertyDescriptorCollection(tempPropertyDescriptors.ToArray(), true);
}
else
{
_properties = originalCollection;
}
}
return _properties;
}
/// <summary>
/// Abstract method specific DAL implementations must override to return the
/// list of RIA <see cref="Attribute"/>s implied by their DAL-specific attributes
/// </summary>
/// <param name="pd">A <see cref="PropertyDescriptor"/> to examine.</param>
/// <returns>A list of RIA attributes implied by the DAL specific attributes</returns>
protected abstract IEnumerable<Attribute> GetMemberAttributes(PropertyDescriptor pd);
/// <summary>
/// Returns <c>true</c> if the given type is a <see cref="Nullable"/>
/// </summary>
/// <param name="type">The type to test</param>
/// <returns><c>true</c> if the given type is a nullable type</returns>
public static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
}
}

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<CodeAnalysis Condition=" '$(CodeAnalysis)' == '' ">false</CodeAnalysis>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{653F3946-541C-42D3-BBC1-CE89B392BDA9}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.Web.Http.Data.EntityFramework</RootNamespace>
<AssemblyName>Microsoft.Web.Http.Data.EntityFramework</AssemblyName>
<FileAlignment>512</FileAlignment>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;ASPNETMVC</DefineConstants>
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\bin\Release\</OutputPath>
<DefineConstants>TRACE;ASPNETMVC</DefineConstants>
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'CodeCoverage|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\..\bin\CodeCoverage\</OutputPath>
<DefineConstants>TRACE;DEBUG;CODE_COVERAGE;ASPNETMVC</DefineConstants>
<DebugType>full</DebugType>
<CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework">
<HintPath>..\..\packages\EntityFramework.5.0.0-beta2\lib\net40\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20326.1\lib\net40\System.Net.Http.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20326.1\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\CommonAssemblyInfo.cs">
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="..\Common\DictionaryExtensions.cs">
<Link>Common\DictionaryExtensions.cs</Link>
</Compile>
<Compile Include="..\Common\Error.cs">
<Link>Common\Error.cs</Link>
</Compile>
<Compile Include="DbContextExtensions.cs" />
<Compile Include="DbDataController.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="LinqToEntitiesDataController.cs" />
<Compile Include="Metadata\AssociationInfo.cs" />
<Compile Include="Metadata\DbMetadataProviderAttribute.cs" />
<Compile Include="Metadata\LinqToEntitiesMetadataProvider.cs" />
<Compile Include="Metadata\LinqToEntitiesMetadataProviderAttribute.cs" />
<Compile Include="Metadata\LinqToEntitiesTypeDescriptionContext.cs" />
<Compile Include="Metadata\LinqToEntitiesTypeDescriptor.cs" />
<Compile Include="Metadata\MetadataPropertyDescriptorWrapper.cs" />
<Compile Include="Metadata\MetadataWorkspaceUtilities.cs" />
<Compile Include="Metadata\TypeDescriptionContextBase.cs" />
<Compile Include="Metadata\TypeDescriptorBase.cs" />
<Compile Include="ObjectContextExtensions.cs" />
<Compile Include="ObjectContextUtilities.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\AptcaCommonAssemblyInfo.cs">
<Link>Properties\AptcaCommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Resource.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="..\CodeAnalysisDictionary.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\System.Net.Http.Formatting\System.Net.Http.Formatting.csproj">
<Project>{668E9021-CE84-49D9-98FB-DF125A9FCDB0}</Project>
<Name>System.Net.Http.Formatting</Name>
</ProjectReference>
<ProjectReference Include="..\Microsoft.Web.Http.Data\Microsoft.Web.Http.Data.csproj">
<Project>{ACE91549-D86E-4EB6-8C2A-5FF51386BB68}</Project>
<Name>Microsoft.Web.Http.Data</Name>
</ProjectReference>
<ProjectReference Include="..\System.Web.Http\System.Web.Http.csproj">
<Project>{DDC1CE0C-486E-4E35-BB3B-EAB61F8F9440}</Project>
<Name>System.Web.Http</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Common\CommonWebApiResources.Designer.cs">
<Link>Properties\CommonWebApiResources.Designer.cs</Link>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>CommonWebApiResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\Common\CommonWebApiResources.resx">
<Link>Properties\CommonWebApiResources.resx</Link>
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>CommonWebApiResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.ComponentModel;
using System.Data;
using System.Data.Objects;
using System.Web.Http;
namespace Microsoft.Web.Http.Data.EntityFramework
{
/// <summary>
/// ObjectContext extension methods
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ObjectContextExtensions
{
/// <summary>
/// Extension method used to attach the specified entity as modified,
/// with the specified original state.
/// </summary>
/// <typeparam name="TEntity">The entity Type</typeparam>
/// <param name="objectSet">The ObjectSet to attach to</param>
/// <param name="current">The current entity state</param>
/// <param name="original">The original entity state</param>
public static void AttachAsModified<TEntity>(this ObjectSet<TEntity> objectSet, TEntity current, TEntity original) where TEntity : class
{
if (objectSet == null)
{
throw Error.ArgumentNull("objectSet");
}
if (current == null)
{
throw Error.ArgumentNull("current");
}
if (original == null)
{
throw Error.ArgumentNull("original");
}
// Attach the entity if it is not already attached, or if it is already
// attached, transition to Modified
EntityState currState = ObjectContextUtilities.GetEntityState(objectSet.Context, current);
if (currState == EntityState.Detached)
{
objectSet.Attach(current);
}
else
{
objectSet.Context.ObjectStateManager.ChangeObjectState(current, EntityState.Modified);
}
ObjectStateEntry stateEntry = ObjectContextUtilities.AttachAsModifiedInternal<TEntity>(current, original, objectSet.Context);
if (stateEntry.State != EntityState.Modified)
{
// Ensure that when we leave this method, the entity is in a
// Modified state. For example, if current and original are the
// same, we still need to force the state transition
objectSet.Context.ObjectStateManager.ChangeObjectState(current, EntityState.Modified);
}
}
/// <summary>
/// Extension method used to attach the specified entity as modified. This overload
/// can be used in cases where the entity has a Timestamp member.
/// </summary>
/// <typeparam name="TEntity">The entity Type</typeparam>
/// <param name="objectSet">The ObjectSet to attach to</param>
/// <param name="entity">The current entity state</param>
public static void AttachAsModified<TEntity>(this ObjectSet<TEntity> objectSet, TEntity entity) where TEntity : class
{
if (objectSet == null)
{
throw Error.ArgumentNull("objectSet");
}
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
ObjectContext context = objectSet.Context;
EntityState currState = ObjectContextUtilities.GetEntityState(context, entity);
if (currState == EntityState.Detached)
{
// attach the entity
objectSet.Attach(entity);
}
// transition the entity to the modified state
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
}
}

View File

@@ -0,0 +1,180 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Linq;
using System.Web.Http;
namespace Microsoft.Web.Http.Data.EntityFramework
{
/// <summary>
/// Internal utility functions for dealing with EF types and metadata
/// </summary>
internal static class ObjectContextUtilities
{
/// <summary>
/// Retrieves the <see cref="StructuralType"/> corresponding to the given CLR type (where the
/// type is an entity or complex type).
/// </summary>
/// <remarks>
/// If no mapping exists for <paramref name="clrType"/>, but one does exist for one of its base
/// types, we will return the mapping for the base type.
/// </remarks>
/// <param name="workspace">The <see cref="MetadataWorkspace"/></param>
/// <param name="clrType">The CLR type</param>
/// <returns>The <see cref="StructuralType"/> corresponding to that CLR type, or <c>null</c> if the Type
/// is not mapped.</returns>
public static StructuralType GetEdmType(MetadataWorkspace workspace, Type clrType)
{
if (workspace == null)
{
throw Error.ArgumentNull("workspace");
}
if (clrType == null)
{
throw Error.ArgumentNull("clrType");
}
if (clrType.IsPrimitive || clrType == typeof(object))
{
// want to avoid loading searching system assemblies for
// types we know aren't entity or complex types
return null;
}
// We first locate the EdmType in "OSpace", which matches the name and namespace of the CLR type
EdmType edmType = null;
do
{
if (!workspace.TryGetType(clrType.Name, clrType.Namespace, DataSpace.OSpace, out edmType))
{
// If EF could not find this type, it could be because it is not loaded into
// its current workspace. In this case, we explicitly load the assembly containing
// the CLR type and try again.
workspace.LoadFromAssembly(clrType.Assembly);
workspace.TryGetType(clrType.Name, clrType.Namespace, DataSpace.OSpace, out edmType);
}
}
while (edmType == null && (clrType = clrType.BaseType) != typeof(object) && clrType != null);
// Next we locate the StructuralType from the EdmType.
// This 2-step process is necessary when the types CLR namespace does not match Edm namespace.
// Look at the EdmEntityTypeAttribute on the generated entity classes to see this Edm namespace.
StructuralType structuralType = null;
if (edmType != null &&
(edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType || edmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType))
{
workspace.TryGetEdmSpaceType((StructuralType)edmType, out structuralType);
}
return structuralType;
}
/// <summary>
/// Method used to return the current <see cref="EntityState"/> of the specified
/// entity.
/// </summary>
/// <param name="context">The <see cref="ObjectContext"/></param>
/// <param name="entity">The entity to return the <see cref="EntityState"/> for</param>
/// <returns>The current <see cref="EntityState"/> of the specified entity</returns>
public static EntityState GetEntityState(ObjectContext context, object entity)
{
if (context == null)
{
throw Error.ArgumentNull("context");
}
if (entity == null)
{
throw Error.ArgumentNull("entity");
}
ObjectStateEntry stateEntry = null;
if (!context.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry))
{
return EntityState.Detached;
}
return stateEntry.State;
}
/// <summary>
/// Determines if the specified EdmMember is a concurrency timestamp.
/// </summary>
/// <remarks>Since EF doesn't expose "timestamp" as a first class
/// concept, we use the below criteria to infer this for ourselves.
/// </remarks>
/// <param name="member">The member to check.</param>
/// <returns>True or false.</returns>
public static bool IsConcurrencyTimestamp(EdmMember member)
{
Facet facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "ConcurrencyMode");
if (facet == null || facet.Value == null || (ConcurrencyMode)facet.Value != ConcurrencyMode.Fixed)
{
return false;
}
facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "FixedLength");
if (facet == null || facet.Value == null || !((bool)facet.Value))
{
return false;
}
facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "MaxLength");
if (facet == null || facet.Value == null || (int)facet.Value != 8)
{
return false;
}
MetadataProperty md = ObjectContextUtilities.GetStoreGeneratedPattern(member);
if (md == null || facet.Value == null || (string)md.Value != "Computed")
{
return false;
}
return true;
}
/// <summary>
/// Gets the <see cref="StoreGeneratedPattern"/> property value from the edm member.
/// </summary>
/// <param name="member">The EdmMember from which to get the StoreGeneratedPattern value.</param>
/// <returns>The <see cref="StoreGeneratedPattern"/> value.</returns>
public static MetadataProperty GetStoreGeneratedPattern(EdmMember member)
{
MetadataProperty md;
member.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2009/02/edm/annotation:StoreGeneratedPattern", ignoreCase: true, item: out md);
return md;
}
public static ObjectStateEntry AttachAsModifiedInternal<TEntity>(TEntity current, TEntity original, ObjectContext objectContext)
{
ObjectStateEntry stateEntry = objectContext.ObjectStateManager.GetObjectStateEntry(current);
stateEntry.ApplyOriginalValues(original);
// For any members that don't have RoundtripOriginal applied, EF can't determine modification
// state by doing value comparisons. To avoid losing updates in these cases, we must explicitly
// mark such members as modified.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(TEntity));
AttributeCollection attributes = TypeDescriptor.GetAttributes(typeof(TEntity));
bool isRoundtripType = attributes[typeof(RoundtripOriginalAttribute)] != null;
foreach (var fieldMetadata in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata)
{
string memberName = stateEntry.CurrentValues.GetName(fieldMetadata.Ordinal);
PropertyDescriptor property = properties[memberName];
// TODO: below we need to replace ExcludeAttribute logic with corresponding
// DataContractMember/IgnoreDataMember logic
if (property != null &&
(property.Attributes[typeof(RoundtripOriginalAttribute)] == null && !isRoundtripType)
/* && property.Attributes[typeof(ExcludeAttribute)] == null */)
{
stateEntry.SetModifiedProperty(memberName);
}
}
return stateEntry;
}
}
}

View File

@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("Microsoft.Web.Http.Data.EntityFramework")]
[assembly: AssemblyDescription("Microsoft.Web.Http.Data.EntityFramework")]
[assembly: InternalsVisibleTo("Microsoft.Web.Http.Data.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]

View File

@@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.239
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.Web.Http.Data.EntityFramework {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Web.Http.Data.EntityFramework.Resource", typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The DbContext type &apos;{0}&apos; does not contain a parameterless constructor. A parameterless constructor is required to use EntityFramework in the Code-First mode with a DataController..
/// </summary>
internal static string DefaultCtorNotFound {
get {
return ResourceManager.GetString("DefaultCtorNotFound", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; is not a valid DbMetadataProviderAttribute parameter because it does not derive from DbContext..
/// </summary>
internal static string InvalidDbMetadataProviderSpecification {
get {
return ResourceManager.GetString("InvalidDbMetadataProviderSpecification", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; is not a valid LinqToEntitiesMetadataProviderAttribute parameter because it does not derive from ObjectContext..
/// </summary>
internal static string InvalidLinqToEntitiesMetadataProviderSpecification {
get {
return ResourceManager.GetString("InvalidLinqToEntitiesMetadataProviderSpecification", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &apos;{0}&apos; cannot be applied to DataController type &apos;{1}&apos; because &apos;{1}&apos; does not derive from &apos;{2}&apos;..
/// </summary>
internal static string InvalidMetadataProviderSpecification {
get {
return ResourceManager.GetString("InvalidMetadataProviderSpecification", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to retrieve association information for association &apos;{0}&apos;. Only models that include foreign key information are supported. See Entity Framework documentation for details on creating models that include foreign key information..
/// </summary>
internal static string LinqToEntitiesProvider_UnableToRetrieveAssociationInfo {
get {
return ResourceManager.GetString("LinqToEntitiesProvider_UnableToRetrieveAssociationInfo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to find metadata for &apos;{0}&apos;..
/// </summary>
internal static string LinqToEntitiesProvider_UnableToRetrieveMetadata {
get {
return ResourceManager.GetString("LinqToEntitiesProvider_UnableToRetrieveMetadata", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to get the MetadataWorkspace for the DbContext type &apos;{0}&apos;..
/// </summary>
internal static string MetadataWorkspaceNotFound {
get {
return ResourceManager.GetString("MetadataWorkspaceNotFound", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ObjectStateManager not initialized for the DbContext type &apos;{0}&apos;..
/// </summary>
internal static string ObjectStateManagerNotFoundException {
get {
return ResourceManager.GetString("ObjectStateManagerNotFoundException", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DefaultCtorNotFound" xml:space="preserve">
<value>The DbContext type '{0}' does not contain a parameterless constructor. A parameterless constructor is required to use EntityFramework in the Code-First mode with a DataController.</value>
</data>
<data name="InvalidDbMetadataProviderSpecification" xml:space="preserve">
<value>Type '{0}' is not a valid DbMetadataProviderAttribute parameter because it does not derive from DbContext.</value>
</data>
<data name="InvalidLinqToEntitiesMetadataProviderSpecification" xml:space="preserve">
<value>Type '{0}' is not a valid LinqToEntitiesMetadataProviderAttribute parameter because it does not derive from ObjectContext.</value>
</data>
<data name="InvalidMetadataProviderSpecification" xml:space="preserve">
<value>'{0}' cannot be applied to DataController type '{1}' because '{1}' does not derive from '{2}'.</value>
</data>
<data name="LinqToEntitiesProvider_UnableToRetrieveAssociationInfo" xml:space="preserve">
<value>Unable to retrieve association information for association '{0}'. Only models that include foreign key information are supported. See Entity Framework documentation for details on creating models that include foreign key information.</value>
</data>
<data name="LinqToEntitiesProvider_UnableToRetrieveMetadata" xml:space="preserve">
<value>Unable to find metadata for '{0}'.</value>
</data>
<data name="MetadataWorkspaceNotFound" xml:space="preserve">
<value>Failed to get the MetadataWorkspace for the DbContext type '{0}'.</value>
</data>
<data name="ObjectStateManagerNotFoundException" xml:space="preserve">
<value>ObjectStateManager not initialized for the DbContext type '{0}'.</value>
</data>
</root>

Some files were not shown because too many files have changed in this diff Show More