I'm using Guids as primary key for the entities in my database, using the model binding feature of asp.net 4.5 web forms when im inserting a record in the database using entity framework 5 im doing something like
public void onInsert([Control("ControlID")] int? countryID){
if(countryID.hasValue){
var DbEntityToInsert = new DbEntity(); //where DbEntity is the class generated by the EF
TryUpdateModel(DbEntityToInsert);
DbEntityToInsert.GuidPK = Guid.NewGuid();
if(Page.ModelState.IsValid){
using(var db = new DatabaseContext()){
db.Add(DbEntityToInsert);
db.Save();
}//using ends
}//modelstate.isvalid if ends
}//countryid.hasvalue ends
}//main method ends
now i wanted to ask is there a way i can tell EF to generate a Guid for the PK while inserting a new record so i dont have to write the line
DbEntityToInsert.GuidPK = Guid.NewGuid();
You can try to override SaveChanges in your derived context. The main task is to find out if an entity has a GuidPK property as primary key. Here is an attempt using reflection:
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges();
var addedEntities = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added)
.Select(e => new
{
Entity = e.Entity,
PropertyInfo = e.Entity.GetType().GetProperty("GuidPK")
})
.Where(x => x.PropertyInfo != null && x.PropertyInfo.CanWrite);
foreach (var x in addedEntities)
x.PropertyInfo.SetValue(x.Entity, Guid.NewGuid());
return base.SaveChanges();
}
To avoid reflection here you could have a common interface that is implemented by all your entities that use a GuidPK property as PK:
public interface IEntityWithGuidPK
{
Guid GuidPK { get; set; }
}
public class DbEntity : IEntityWithGuidPK
{
public Guid GuidPK { get; set; }
// ...
}
Then the code in SaveChanges could be:
//...
var addedEntities = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added &&
e.Entity is IEntityWithGuidPK)
.Select(e => e.Entity as IEntityWithGuidPK);
foreach (var e in addedEntities)
e.GuidPK = Guid.NewGuid();
//...
Related
I am going to update schooltypeid which is in the intermediates table, here is my repository class.
public async Task UpdateSchoolsAsync(SchoolUpdateVm schoolUpdateVm)
{
if (_GpsContext != null)
{
var schoolsObj = _GpsContext.School.FirstOrDefault(x => x.ID == Guid.Parse(schoolUpdateVm.id));
var schoolTypeObj = _GpsContext.SchoolsSchoolTypes.FirstOrDefault(x => x.SchoolTypeId == Guid.Parse(schoolUpdateVm.schoolTypeId));
Schools schools = new Schools();
{
schoolsObj.Name = schoolUpdateVm.name;
schoolsObj.Email = schoolUpdateVm.email;
schoolsObj.Phone = schoolUpdateVm.phone;
schoolsObj.Description = schoolUpdateVm.description;
schoolsObj.StateID = Guid.Parse(schoolUpdateVm.stateID);
schoolsObj.CountryId = Guid.Parse(schoolUpdateVm.countryId);
schoolTypeObj.SchoolTypeId = Guid.Parse(schoolUpdateVm.schoolTypeId); //here i can`t update schoolYype
}
_GpsContext.School.Update(schoolsObj);
await _GpsContext.SaveChangesAsync();
}
}
This is my School table in entity framework:
public partial class Schools
{
public Guid ID { get; set; }
public string Name { get; set; }
// Navigations
public ICollection<SchoolsSchoolType> SchoolsSchoolTypes { get; set; }// this is my junction table
}
This is my SchoolsSchoolTypes table:(This is intermediates table)
I think your model in the question is not complete. It lacks the "joining" entity.
That said, if you have a "pure" joining entity, with no additional attributes besides the key (made up of foreign keys), you should add the type to the "SchoolsSchoolTypes" collection in the "Schools" class. The code to add the entity should be something like this:
var schoolsObj = _GpsContext.School.Include(s => s.SchoolsSchoolTypes ).FirstOrDefault(x => x.ID == Guid.Parse(schoolUpdateVm.id)); //Include types to verify isn't already added
if (schoolsObj == null) throw new Exception("School not found");
if(schoolsObj.SchoolsSchoolTypes.Any(st=>st.SchoolTypeId == schoolUpdateVm.schoolTypeId) throw new Exception("School already has this type");
var schoolTypeObj = _GpsContext.SchoolsSchoolTypes.FirstOrDefault(x => x.SchoolTypeId == Guid.Parse(schoolUpdateVm.schoolTypeId));
if (schoolsObj == null) throw new Exception("School type not found");
schoolsObj.SchoolsSchoolTypes.Add(schoolTypeObj);
await _GpsContext.SaveChangesAsync();
If the "joining entity" has additional attributes (I suspect this is the case), then you have to create the new joining entity before adding it to the collection:
var schoolsObj = _GpsContext.School.Include(s => s.SchoolsSchoolTypes ).FirstOrDefault(x => x.ID == Guid.Parse(schoolUpdateVm.id)); //Include types to verify isn't already added
if (schoolsObj == null) throw new Exception("School not found");
if(schoolsObj.SchoolsSchoolTypes.Any(st=>st.SchoolTypeId == schoolUpdateVm.schoolTypeId) throw new Exception("School already has this type");
var schoolTypeObj = _GpsContext.SchoolTypes.FirstOrDefault(x => x.SchoolTypeId == Guid.Parse(schoolUpdateVm.schoolTypeId));
if (schoolsObj == null) throw new Exception("School type not found");
var newSchollType = new SchoolsSchoolTypes()
{
SchollId = Guid.Parse(schoolUpdateVm.id),
SchoolTypeId = Guid.Parse(schoolUpdateVm.schoolTypeId),
OtherProperty = "OtherPropertyValue"
}
schoolsObj.SchoolsSchoolTypes.Add(newSchollType);
await _GpsContext.SaveChangesAsync();
I have this class as a part of EF Model:
class Person {
public int Id { get; set; }
[MaxLength(100, ErrorMessage="Name cannot be more than 100 characters")]
public string Name { get; set; }
}
And I have this method in my controller:
public IActionResult ChangeName(int id, string name) {
var person = db.Persons.Find(id);
if(person == null) return NotFound();
person.Name = name;
db.SaveChanges();
return Json(new {result = "Saved Successfully"});
}
Is there any way to validate person after changing the Name property using the annotation MaxLength rather than manually check for it. Becuase sometimes I might have more than one validation and I don't want to examine each one of them. Also, I might change these parameters in the future (e.g. make the max length 200), and that means I have to change it everywhere else.
So is it possible?
Your method works as long as there is one validation error per property. Also, it's quite elaborate. You can use db.GetValidationErrors() to get the same result. One difference is that errors are collected in a collection per property name:
var errors = db.GetValidationErrors()
.SelectMany(devr => devr.ValidationErrors)
.GroupBy(ve => ve.PropertyName)
.ToDictionary(ve => ve.Key, ve => ve.Select(v => v.ErrorMessage));
Okay, I found a solution to my problem, I created a method that takes the model and checks for errors:
private IDictionary<string, string> ValidateModel(Person model)
{
var errors = new Dictionary<string, string>();
foreach (var property in model.GetType().GetProperties())
{
foreach (var attribute in property.GetCustomAttributes())
{
var validationAttribute = attribute as ValidationAttribute;
if(validationAttribute == null) continue;
var value = property.GetValue(model);
if (!validationAttribute.IsValid(value))
{
errors.Add(property.Name, validationAttribute.ErrorMessage);
}
}
}
return errors;
}
UPDATE:
As stated by #Gert Arnold, the method above returns only one validation per property. Below is the fixed version which returns a list of errors for each property
public static IDictionary<string, IList<string>> ValidateModel(Person model)
{
var errors = new Dictionary<string, IList<string>>();
foreach (var property in model.GetType().GetProperties())
{
foreach (var attribute in property.GetCustomAttributes())
{
var validationAttribute = attribute as ValidationAttribute;
if (validationAttribute == null) continue;
var value = property.GetValue(model);
if (validationAttribute.IsValid(value)) continue;
if (!errors.ContainsKey(property.Name))
errors[property.Name] = new List<string>();
errors[property.Name].Add(validationAttribute.ErrorMessage);
}
}
return errors;
}
I want to invoke method durning mapping my domain class to DTO class but after many tries with LINQ to Entities or LINQ to objects i have failed and i'm getting weird different errors. Actulal error is just a "LINQ to Entities does not recognize the method 'System.String ResizeToLogoImage(System.String)' method, and this method cannot be translated into a store expression.".
Mapping method:
public async Task<SingleCategory> SingleCategoryMapping(EventerApiContext context, int id)
{
var category = await context.Category.Select(c => new SingleCategory
{
CategoryId = c.CategoryId,
CategoryName = c.CategoryName,
CityId = c.CityId,
Events = context.Event.ToList().Where(e=>e.CategoryId == id).Select(e=> new EventForSingleCategory
{
EventId = e.EventId,
EventName = e.EventName,
EventLogo = ImageProcessor.ResizeToLogoImage(e.EventDetail.EventImage.EventImageBase64)
}).ToList()
}).SingleOrDefaultAsync(c => c.CategoryId == id);
return category;
}
Method to be invoked.
public static string ResizeToLogoImage(string base64String)
{
if (base64String == null)
{
return "NULL";
}
var imageToResize = Base64ToImage(base64String);
var resizedImage = ScaleImage(imageToResize, 50, 50);
return ImageToBase64(resizedImage, imageToResize.RawFormat);
}
I know error is appearing during EventLogo property mapping but i have no more idea what to do.
Try to get the data first, before you do the Select statement. I suspect that it is trying to execute ResizeToLogoImage on the database :)
I have 3 tables of same structure so i have created the following entity using ServiceStack
public class GenericEntity
{
[Alias("COL_A")]
public string ColumnA { get; set; }
}
For retriving the results I use the following line of code. In it I pass the table name like "TableA"/"TableB" so that i can pull the appropriate results
db.Select<GenericEntity>(w => w.Where(whereExperssion).OrderBy(o => o.ColumnA).From("TableA"));
For delete i use the following code
db.Delete<GenericEntity>(w => w.Where(q => q.ColumnA == "A").From("TableA"));
With From() I can pass table name for SELECT & DELETE operations. Is there a similar way for Inserting and updating? The below is the snippet code I am using for update and insert
Insert
db.Insert(new GenericEntity() {});
Update
db.Update<GenericEntity>(new GenericEntity { ColumnA = "ModifiedData"},p => p.ColumnA == "OriginalData");
As you're wanting to this for multiple API's I've added a test showing how to achieve the desired behavior by extending OrmLite's API's with your own custom extension methods that modifies OrmLite's table metadata at runtime to add new API's that allow specifying the table name at runtime, i.e:
var tableName = "TableA"'
db.DropAndCreateTable<GenericEntity>(tableName);
db.Insert(tableName, new GenericEntity { Id = 1, ColumnA = "A" });
var rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "A"));
rows.PrintDump();
db.Update(tableName, new GenericEntity { ColumnA = "B" },
where: q => q.ColumnA == "A");
rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "B"));
rows.PrintDump();
With these extension methods:
public static class GenericTableExtensions
{
static object ExecWithAlias<T>(string table, Func<object> fn)
{
var modelDef = typeof(T).GetModelMetadata();
lock (modelDef) {
var hold = modelDef.Alias;
try {
modelDef.Alias = table;
return fn();
}
finally {
modelDef.Alias = hold;
}
}
}
public static void DropAndCreateTable<T>(this IDbConnection db, string table) {
ExecWithAlias<T>(table, () => { db.DropAndCreateTable<T>(); return null; });
}
public static long Insert<T>(this IDbConnection db, string table, T obj, bool selectIdentity = false) {
return (long)ExecWithAlias<T>(table, () => db.Insert(obj, selectIdentity));
}
public static List<T> Select<T>(this IDbConnection db, string table, Func<SqlExpression<T>, SqlExpression<T>> expression) {
return (List<T>)ExecWithAlias<T>(table, () => db.Select(expression));
}
public static int Update<T>(this IDbConnection db, string table, T item, Expression<Func<T, bool>> where) {
return (int)ExecWithAlias<T>(table, () => db.Update(item, where));
}
}
Adding your own extension methods in this way allows you to extend OrmLite with your own idiomatic API's given that OrmLite is itself just a suite of extension methods over ADO.NET's IDbConnection.
e.g i have Teachers and Students tables in both tables have ,created by & updated by fields
which i want to fill in override saveChanges() method,when ever i add any new object of these tables but how i suppose to know which object call "override saveChanges()" please help me
Upto now i hard codded enter table name
public override int SaveChanges()
{
AppConstants._Teacher.CreatedBy = "waleed";
return base.SaveChanges();
}
You should create interface like this:
public interface ICreatedBy
{
string CreatedBy { get; set; }
}
and make your entities implement it.
Then try to do something like this:
public override int SaveChanges()
{
foreach (var entry in this.ChangeTracker.Entries()
.Where(e=>e.Entity is ICreatedBy &&
(e.State == EntityState.Added ||
e.State == EntityState.Modified)))
{
ICreatedBy e = (ICreatedBy) entry.Entity;
e.CreatedOn = "waleed";
}
return base.SaveChanges();
}