all
I was create IDbCommandTreeInterceptor and got the problem: EF provider wrong sql generation. As a result, I want to get this SQL
UPDATE [dbo].[Devices] SET [DeletedDate] = CASE
WHEN [DeletedDate] IS NULL THEN GETUTCDATE()
ELSE [DeletedDate] END
Code for testing. Interseptor class for fake delettion.
public class SoftDeleteInterseptor : IDbCommandTreeInterceptor
{
private const string DELETED_DATE_COLUMN = "DeletedDate";
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
{
return;
}
var deleteCommand = interceptionContext.OriginalResult as DbDeleteCommandTree;
if (deleteCommand != null)
{
interceptionContext.Result = HandleDeleteCommand(deleteCommand);
return;
}
}
private DbCommandTree HandleDeleteCommand(DbDeleteCommandTree deleteCommand)
{
if (!IsPropertyExists(deleteCommand, DELETED_DATE_COLUMN))
{
return deleteCommand;
}
var deletedProperty = DbExpressionBuilder.Property(
DbExpressionBuilder.Variable(deleteCommand.Target.VariableType, deleteCommand.Target.VariableName),
DELETED_DATE_COLUMN
);
var caseValue = DbExpressionBuilder.Case(
new DbExpression[] { deletedProperty.IsNull() },
new DbExpression[] { EdmFunctions.CurrentUtcDateTime() },
deletedProperty);
var setClauses = new List<DbModificationClause> { DbExpressionBuilder.SetClause(deletedProperty, caseValue) };
return new DbUpdateCommandTree(
deleteCommand.MetadataWorkspace,
deleteCommand.DataSpace,
deleteCommand.Target,
deleteCommand.Predicate,
setClauses.AsReadOnly(), null);
}
private bool IsPropertyExists(DbModificationCommandTree command, string property)
{
var table = (EntityType)command.Target.VariableType.EdmType;
return table.Properties.Any(p => p.Name == property);
}
}
Create configuration class for register DbInterseptor.
public class CustomDbConfiguration : DbConfiguration
{
public CustomDbConfiguration()
{
AddInterceptor(new SoftDeleteInterseptor());
}
}
public partial class CustomDbContext : DbContext
{
static IDCompleteDbContext()
{
DbConfiguration.SetConfiguration(new CustomDbConfiguration());
}
public virtual DbSet<CommandEntity> CommandEntities { get; set; }
}
public class CommandEntity
{
public int Id {get; set;}
public DateTime? DeletedDate {get; set;}
public string Name {get; set;}
}
When delete entity, entity did not deleted
var context = new CustomDbContext();
var entity = context.CommandEntities.First();
context.CommandEntities.Remove(entity);
context.SubmitChanges();
Did not work. EF provider generate wrong SQL: UPDATE [dbo].[Devices] SET [DeletedDate] = [DeletedDate] IS NULL#0[DeletedDate] WHERE ([Id] = #1) #0: '01.05.2018 7:45:22' (Type = DateTime2) #1: '20' (Type = Int32)
Related
please I have these entities to be sent to database
MatchingChildtoApplicant
{
[Key]
public int MatchId { get; set; }
public int ChildId {get; set;}
public Child Children { get; set; }
public int ApplyId {get; set;}
public ICollection<Application> Applications { get; set; }
public string MatchingBy { get; set; }
}
this is the post request to send to the database
[HttpPost]
public async Task<ActionResult<MatchingChildtoApplicant>> AddNewMatching( int applyid, int chd, [FromForm]MatchingChildtoApplicant matchingChildtoApplicant)
{
//getting the application to match
var gettheapplicantion = await _unitOfWork.ApplicationRepository.GetApplicantByIdAsync(applyid);
if(gettheapplicantion == null){
return NotFound("The Application to match was not found");
}
var appid = gettheapplicantion.ApplyId;
//getting the child to match with applicant
var childtomatch = await _unitOfWork.ChildRepository.GetChildByIdAsync(chd);
if(childtomatch == null){
return NotFound("The child to match was not found");
}
var childtoid = childtomatch.Cld;
//getting the login user doing the matching
var userdoingmatch = await _userManager.FindByEmailFromClaimsPrinciple(User);
if (userdoingmatch == null)
{
return NotFound("The user doing the matching details is missing");
}
var nameofuser = userdoingmatch.DisplayName;
//declaring new instance of MatchingChildtoApplicant
var newmatching = new MatchingChildtoApplicant
{
ApplyId = appid,
ChildId = childtoid,
MatchingBy = nameofuser,
Comment = matchingChildtoApplicant.Comment,
TheApplicationAcepted = matchingChildtoApplicant.TheApplicationAcepted,
MatchedDate = matchingChildtoApplicant.MatchedDate,
};
This is where I get the error sending to the database
// var result = await _context.MatchingChildtoApplicant.AddAsync(matchingChildtoApplicant);
// var result = await _context.MatchingChildtoApplicant.AddAsync(matchingChildtoApplicant);
// gettheapplicantion.MatchingChildtoApplicants.Children.Add(matchingChildtoApplicant);
await _context.SaveChangesAsync();
return new MatchingChildtoApplicant
{
ApplyId = appid,
ChildId = childtoid,
MatchingBy = nameofuser,
Comment = matchingChildtoApplicant.Comment,
TheApplicationAcepted = matchingChildtoApplicant.TheApplicationAcepted,
MatchedDate = matchingChildtoApplicant.MatchedDate,
};
The error I get is this
The type arguments for method 'ValueTransformerConfigurationExtensions.Add(List, Expression<Func<TValue, TValue>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly. [API]
I'm implementing an application with Breezesharp. I ran into a issue when insert the entity in the EntityManager. The error is:
There are no KeyProperties yet defined on EntityType: 'TransportReceipt:#Business.DomainModels'
I already faced this error with my first entity type "Customer" and implement a mismatching approach as suggested here. In that case I made the get operation against my WebApi with success. But now I'm creating the TransportReceipt entity inside my application.
Mapping mismatch fix
public static class ExtendMap
{
private static bool? executed;
public static void Execute(MetadataStore metadataStore) {
if (ExtendMap.executed == true)
{
return;
}
var customerBuilder = new EntityTypeBuilder<Customer>(metadataStore);
customerBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptBuilder = new EntityTypeBuilder<TransportReceipt>(metadataStore);
transportReceiptBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptAttachmentBuilder = new EntityTypeBuilder<TransportReceiptAttachment>(metadataStore);
transportReceiptAttachmentBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var uploadedFileBuilder = new EntityTypeBuilder<UploadedFile>(metadataStore);
uploadedFileBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
ExtendMap.executed = true;
}
}
My base dataservice core code
public abstract class SimpleBaseDataService
{
public static string Metadata { get; protected set; }
public static MetadataStore MetadataStore { get; protected set; }
public string EntityName { get; protected set; }
public string EntityResourceName { get; protected set; }
public EntityManager EntityManager { get; set; }
public string DefaultTargetMethod { get; protected set; }
static SimpleBaseDataService()
{
try
{
var metadata = GetMetadata();
metadata.Wait();
Metadata = metadata.Result;
MetadataStore = BuildMetadataStore();
}
catch (Exception ex)
{
var b = 0;
}
}
public SimpleBaseDataService(Type entityType, string resourceName, string targetMethod = null)
{
var modelType = typeof(Customer);
Configuration.Instance.ProbeAssemblies(ConstantsFactory.BusinessAssembly);
try
{
this.EntityName = entityType.FullName;
this.EntityResourceName = resourceName;
this.DefaultTargetMethod = (string.IsNullOrWhiteSpace(targetMethod) ? "GetAllMobile" : targetMethod);
var dataService = new DataService($"{ConstantsFactory.Get.BreezeHostUrl}{this.EntityResourceName}", new CustomHttpClient());
dataService.HasServerMetadata = false;
this.EntityManager = new EntityManager(dataService, SimpleBaseDataService.MetadataStore);
this.EntityManager.MetadataStore.AllowedMetadataMismatchTypes = MetadataMismatchTypes.AllAllowable;
// Attach an anonymous handler to the MetadataMismatch event
this.EntityManager.MetadataStore.MetadataMismatch += (s, e) =>
{
// Log the mismatch
var message = string.Format("{0} : Type = {1}, Property = {2}, Allow = {3}",
e.MetadataMismatchType, e.StructuralTypeName, e.PropertyName, e.Allow);
// Disallow missing navigation properties on the TodoItem entity type
if (e.MetadataMismatchType == MetadataMismatchTypes.MissingCLRNavigationProperty &&
e.StructuralTypeName.StartsWith("TodoItem"))
{
e.Allow = false;
}
};
}
catch (Exception ex)
{
var b = 0;
}
}
}
This is who I'm trying to add the new entity
//DataService snippet
public void AttachEntity(T entity)
{
this.EntityManager.AttachEntity(entity, EntityState.Added);
}
//Business
this.TransportReceipt = new TransportReceipt { id = Guid.NewGuid(), date = DateTime.Now, customerId = Customer.id/*, customer = this.Customer*/ };
this.Attachments = new List<TransportReceiptAttachment>();
this.TransportReceipt.attachments = this.Attachments;
TransportReceiptDataService.AttachEntity(this.TransportReceipt);
When I try to add add the entity to the EntityManager, I can see the custom mapping for all my entity classes.
So my question is what I'm doing wrong.
Ok. That was weird.
I changed the mapping for a new fake int property and works. I'll test the entire save flow soon and I'll share the result here.
Update
I moved on and start removing Breezesharp. The Breezesharp project is no up-to-date and doesn't have good integration with Xamarin. I'll appreciate any comment with your experience.
I am working on online users list. My code is:
public class User
{
public string id;
public string name;
public string dpExtension;
}
public class OnlineUsers : Hub
{
private Entities db = new Entities();
public static ConcurrentDictionary<string, User> users = new ConcurrentDictionary<string, User>();
public override System.Threading.Tasks.Task OnConnected()
{
User u = new User();
u.id = "visitor";
u.name = "Visitor";
u.dpExtension = "";
if (Context.User.Identity.IsAuthenticated)
{
u.id = Context.User.Identity.GetUserId();
var da = db.AspNetUsers.Find(u.id);
u.name = da.Email;
u.dpExtension = da.dpExtension;
}
User abc;
var data = users.TryGetValue(Context.ConnectionId, out abc);
if (!data)
{
users.TryAdd(Context.ConnectionId, u);
}
Clients.All.showConnected(users);
return base.OnConnected();
}
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
User abc;
users.TryRemove(Context.ConnectionId, out abc);
Clients.All.showConnected(users);
return base.OnDisconnected(stopCalled);
}
}
Now if the user has opened one browser tab, it is shown once in list and that's fine. But if user opens two tabs it is shown in list twice. How can I show one user only once in list?
You need to map ConnectionIds to one User. You can read a lot in this article - http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections .
I've added a few things in your code, you could see what was going on in this article:
public class User
{
public string dpExtension;
public string id;
public string name;
}
public class Map
{
public string UserId { get; set; }
public User User { get; set; }
public List<string> Connections { get; set; }
}
public class OnlineUsers : Hub
{
public static readonly List<Map> maps = new List<Map>();
private readonly Entities db = new Entities();
public override Task OnConnected()
{
var user = new User { id = "visitor", name = "Visitor", dpExtension = "" };
if (Context.User.Identity.IsAuthenticated)
{
user.id = Context.User.Identity.GetUserId();
var da = db.AspNetUsers.Find(user.id);
user.name = da.Email;
user.dpExtension = da.dpExtension;
}
Map data = maps.FirstOrDefault(t => t.UserId == user.id);
if (data == null)
{
maps.Add(new Map() {
UserId = user.id,
User = user,
Connections = new List<string>() { Context.ConnectionId }
});
}
else
{
data.Connections.Add(Context.ConnectionId);
}
Clients.All.showConnected(maps.Select(m => m.User));
return base.OnConnected();
}
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
var map = maps.FirstOrDefault(t => t.Connections.Contains(Context.ConnectionId));
if (map != null)
{
map.Connections.Remove(Context.ConnectionId);
if (map.Connections.Count <= 0)
{
maps.Remove(map);
}
}
Clients.All.showConnected(maps.Select(m => m.User));
return base.OnDisconnected(stopCalled);
}
}
public partial class ClaimEntities : DbContext
{
public ClaimEntities()
: base("name=ClaimEntities")
{
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<ClaimInformation> ClaimInformations { get; set; }
public DbSet<ClaimInformation_HealthCareCode> ClaimInformation_HealthCareCode { get; set; }
}
}
public partial class ClaimInformation
{
public List<ClaimInformation_HealthCareCode> OtherDiagnosisCodes
{
get
{
return this.ClaimInformation_HealthCareCode.Where(c => c.CodeQualifier == "BF" || c.CodeQualifier == "ABF").ToList();
}
}
}
public partial class ClaimInformation
{
public ClaimInformation()
{
this.ClaimInformation_HealthCareCode = new ObservableListSource<ClaimInformation_HealthCareCode>();
}
public virtual ObservableListSource<ClaimInformation_HealthCareCode> ClaimInformation_HealthCareCode { get; set; }
}
ClaimInformation_HealthcareCodes is a navigation property on the entity ClaimInformation. 1-M between ClaimInformation and ClaimInformation_healthcareCodes. One Claim can have many ClaimHealthcareCodes.
This is how it is loaded in the context
_context.ClaimInformations.Include(h => h.ClaimInformation_HealthCareCode)
How do I make the context detect a change in the navigation property. In the code I am deleting a healthcareCode entry. This is a list in the ClaimInformationClass and is virtual. 1-M
this.claiminformation.claiminfo_healthcarecodes[i].remove(); This line is connected to the context.
private string GetTableName(DbEntityEntry dbEntry)
{
string entryName = dbEntry.Entity.GetType().Name;
int length = entryName.IndexOf('_');
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute),
false).SingleOrDefault() as TableAttribute;
string tableName = tableAttr != null ? tableAttr.Name : entryName.Substring(0,length);
return tableName;
}
This code returns the changed entry as 'ClaimInformation' which is partly true but it has to go a little deeper to to the navigation property which has a deleted entry.
private string GetTableName(DbEntityEntry ent)
{
ObjectContext objectContext = ((IObjectContextAdapter)this.context).ObjectContext;
System.Type entityType = ent.Entity.GetType();
if (entityType.BaseType != null && entityType.Namespace == "System.Data.Entity.DynamicProxies")
entityType = entityType.BaseType;
string entityTypeName = entityType.Name;
EntityContainer container =
objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
string entitySetName = (from meta in container.BaseEntitySets
where meta.ElementType.Name == entityTypeName
select meta.Name).First();
return entitySetName;
}
Changed it to get the entity name instead of table name
I'm using the System.ComponentModel.DataAnnotations namespace to validate my domain classes. How can I create a custom attribute to validate the uniqueness of a property regardless of the database (through some interface, for example)?
This is the solution I came up with for this situation, it simply checks the table for a record with a different id that has the same value for the property being validated. It assumes that you will be using LinqToSQL, and that any table on which this kind of validation is required has a single ID column.
I'd also put a unique constraint on the underlying table in the database. This attribute allows me to put a nice error message on the form and associate it with the appropriate property.
public class UniqueAttribute : ValidationAttribute
{
public Func<DataContext> GetDataContext { get; private set; }
public string IDProperty { get; private set; }
public string Message { get; private set; }
public UniqueAttribute(Type dataContextType, string idProperty, string message)
{
IDProperty = idProperty;
Message = message;
GetDataContext = () => (DataContext)Activator.CreateInstance(dataContextType);
}
public UniqueAttribute(Type dataContextType, string idProperty, string message, string connectionString)
{
IDProperty = idProperty;
Message = message;
GetDataContext = () => (DataContext)Activator.CreateInstance(dataContextType, new object[] { connectionString });
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var idProperty = validationContext.ObjectType.GetProperty(IDProperty);
var idType = idProperty.PropertyType;
var id = idProperty.GetValue(validationContext.ObjectInstance, null);
// Unsightly hack due to validationContext.MemberName being null :(
var memberName = validationContext.ObjectType.GetProperties()
.Where(p => p.GetCustomAttributes(false).OfType<DisplayAttribute>().Any(a => a.Name == validationContext.DisplayName))
.Select(p => p.Name)
.FirstOrDefault();
if (string.IsNullOrEmpty(memberName))
{
memberName = validationContext.DisplayName;
}
// End of hack
var validateeProperty = validationContext.ObjectType.GetProperty(memberName);
var validateeType = validateeProperty.PropertyType;
var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null);
var idParameter = Expression.Constant(id, idType);
var validateeParameter = Expression.Constant(validatee, validateeType);
var objectParameter = Expression.Parameter(validationContext.ObjectType, "o");
var objectIDProperty = Expression.Property(objectParameter, idProperty);
var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty);
var idCheck = Expression.NotEqual(objectIDProperty, idParameter);
var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter);
var compositeCheck = Expression.And(idCheck, validateeCheck);
var lambda = Expression.Lambda(compositeCheck, objectParameter);
var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2);
var genericCountMethod = countMethod.MakeGenericMethod(validationContext.ObjectType);
using (var context = GetDataContext())
{
var table = context.GetTable(validationContext.ObjectType) as IQueryable<Models.Group>;
var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda });
if (count > 0)
{
return new ValidationResult(Message);
}
}
return null;
}
}
Example usage:
[MetadataType(typeof(UserMetadata))]
public partial class Group : IDatabaseRecord
{
public class UserMetadata
{
[Required(ErrorMessage = "Name is required")]
[StringLength(255, ErrorMessage = "Name must be under 255 characters")]
[Unique(typeof(MyDataContext), "GroupID", "Name must be unique")]
public string Name { get; set; }
}
}
just do something like this on your model
[StringLength(100)]
[Index("IX_EntidadCodigoHabilitacion", IsUnique = true)]
public string CodigoHabilitacion { get; set; }
If I am understanding you properly, you should be able to create a custom ValidationAttribute and get a context to your repository through a custom factory.
Validator:
using System.ComponentModel.DataAnnotations;
public class DBUniqueAttribute : ValidationAttribute
{
private IRepository Repository{ get; set;}
public DBUniqueAttribute()
{
this.Repository = MyRepositoryFactory.Create();
}
public override bool IsValid(object value)
{
string stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
return Repository.IsUnique(stringValue);
}
}
You would have an IRepository interface with an IsUnique() method. The MyRepositoryFactory would have a static method called Create() which would create the concrete Repository necessary for your database. If the database type changes, you only need to update the Factory to return a new Repository for your new database.
I love #daveb's solution. Unfortunately, three years later it required some pretty heavy modification for me. Here's his solution updated for EF6. Hopefully will save someone an hour or so of fiddling.
public class UniqueAttribute : ValidationAttribute
{
public UniqueAttribute(string idProperty, string message)
{
IdProperty = idProperty;
Message = message;
}
[Inject]
public DataContext DataContext { get; set; }
private string IdProperty { get; set; }
private string Message { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var objectType = validationContext.ObjectType;
if (objectType.Namespace == "System.Data.Entity.DynamicProxies")
{
objectType = objectType.BaseType;
}
var idProperty = objectType.GetProperty(IdProperty);
var idType = idProperty.PropertyType;
var id = idProperty.GetValue(validationContext.ObjectInstance, null);
var memberName = validationContext.MemberName;
var validateeProperty = objectType.GetProperty(memberName);
var validateeType = validateeProperty.PropertyType;
var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null);
var idParameter = Expression.Constant(id, idType);
var validateeParameter = Expression.Constant(validatee, validateeType);
var objectParameter = Expression.Parameter(objectType, "o");
var objectIdProperty = Expression.Property(objectParameter, idProperty);
var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty);
var idCheck = Expression.NotEqual(objectIdProperty, idParameter);
var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter);
var compositeCheck = Expression.And(idCheck, validateeCheck);
var lambda = Expression.Lambda(compositeCheck, objectParameter);
var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2);
var genericCountMethod = countMethod.MakeGenericMethod(objectType);
var table = DataContext.Set(objectType);
var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda });
if (count > 0)
{
return new ValidationResult(Message);
}
return null;
}
}