Get Added Entity State Primary Key Asp.Net Entity Framework - asp.net

I am trying to create Change Tracker by overriding dbcontext SaveChanges() method.
I couldnt get primary key for entity when state of added.
How can we get added entity state when try to save ChangeLog.
DbContext overrided SaveChanges is like this:
public override int SaveChanges()
{
var adddedEntites = ChangeTracker.Entries().Where(p => p.State == EntityState.Added).ToList();
var now = DateTime.UtcNow;
foreach (var added in adddedEntites)
{
var entityName = added.Entity.GetType().Name;
foreach (var prop in added.CurrentValues.PropertyNames)
{
var currentValue = added.CurrentValues[prop].ToString();
var id = new Guid();
ChangeLog log = new ChangeLog()
{
Id = id,
EntityName = entityName,
PrimaryKeyValue = null// how can I get pkey,
PropertyName = prop,
NewValue = currentValue,
DateChanged = now,
ActionType = "A"
};
ChangeLogs.Add(log);
}
}
return base.SaveChanges()
}
Thanks.

As the primary key is set in base.SaveChanges, you can get it after the call to thereof, e.g. (using reflection):
public override int SaveChanges()
{
var addedEntities = ChangeTracker.Entries().Where(p => p.State == EntityState.Added).ToList();
var result = base.SaveChanges();
foreach (var added in addedEntities)
{
var idInfo = added.Entity.GetType().GetProperty("Id");
var id = idInfo.GetValue(added.Entity);
}
return result;
}

Related

DbContext remove parent object Id

I've got quite weird problem... Here is what it is... When I'm comparing data from somoe goverment service and checking that last born child in my base is the same like in data from service, after updating parent lastBornChild is loosing connection to parent id and it's updated in db wiht ParentId as NULL.
I know that this code should be written better but I wonder why ef changing my lastBornChild.ParentId to NULL.
public class MyService : IMyService
{
MyService(DataDbContext context)
{
_context = context;
}
private DateTime FindYoungestChild(int parentId)
{
var lastChild = _context.Childs.Where(i => i.Parent.Id == parentId).OrderByDescending(d => d.BirthDate).FirstOrDefault();
if (lastChild == null)
return DateTime.MinValue;
else
return lastChild .BirthDate;
}
public Parent UpdateFamilyHistory(Parent parent)
{
DateTime lastStatusTransDate = FindYoungestChild(parent.Id);
var childs= new List<Child>();
var updateChildList = <some goverment public service response>(d => d.DateTimeCreated > lastStatusTransDate).ToList();
foreach (var item in updateChildList)
{
var newChild = new Child
{
Name = item.Name,
BirthDate = item.BirthDate;
};
childs.Add(newChild );
}
parent.Childs = childs;
_context.Update(parent);
_context.SaveChanges();
return parent;
}
}
Try this:
var childs = new List<Child>();
foreach (var item in updateChildList)
{
var newChild = new Child
{
ParentId = parent.Id,
Name = item.Name,
BirthDate = item.BirthDate
};
childs.Add(newChild);
}
_context.Childs.AddRange(childs);
_context.SaveChanges();

Use stored procedure for search method

I started with ASP.Net Core 2.0, I'm trying to rewrite a method GetAll by search use stored procedure. Here is method search:
public async Task<List<DepartmentTypeDto>> SearchDepartmentType()
{
EnsureConnectionOpen();
using (var command = CreateCommand("CM_DEPT_GROUP_Search", CommandType.StoredProcedure))
{
using (var dataReader = await command.ExecuteReaderAsync())
{
List<DepartmentTypeDto> result = new List<DepartmentTypeDto>();
while (dataReader.Read())
{
DepartmentTypeDto departmentTypeDto = new DepartmentTypeDto
{
GROUP_ID = dataReader["GROUP_ID"].ToString(),
GROUP_CODE = dataReader["GROUP_CODE"].ToString(),
GROUP_NAME = dataReader["GROUP_NAME"].ToString(),
NOTES = dataReader["NOTES"].ToString(),
RECORD_STATUS = dataReader["RECORD_STATUS"].ToString(),
MAKER_ID = dataReader["MAKER_ID"].ToString(),
CREATE_DT = Convert.ToDateTime(dataReader["CREATE_DT"]),
AUTH_STATUS = dataReader["AUTH_STATUS"].ToString(),
CHECKER_ID = dataReader["CHECKER_ID"].ToString(),
APPROVE_DT = Convert.ToDateTime(dataReader["APPROVE_DT"]),
AUTH_STATUS_NAME = dataReader["AUTH_STATUS_NAME"].ToString(),
RECORD_STATUS_NAME = dataReader["RECORD_STATUS_NAME"].ToString()
};
}
return result;
}
}
}
Here is the service:
public async Task<PagedResultDto<GetDepartmentTypeForView>> GetAll(GetAllDepartmentTypesInput input)
{
var filteredDepartmentTypes = _departmentTypeRepository.SearchDepartmentType();
var query = (from o in filteredDepartmentTypes
select new GetDepartmentTypeForView() { DepartmentType = ObjectMapper.Map<DepartmentTypeDto>(o) });
var totalCount = await query.CountAsync();
var departmentTypes = await query
.OrderBy(input.Sorting ?? "departmentType.id asc")
.PageBy(input)
.ToListAsync();
return new PagedResultDto<GetDepartmentTypeForView>(totalCount, departmentTypes);
}
But I get an error:
Task<List<DepartmentTypeDto>> does not contain a definition for Select
Does anyone know what I should do? I work on Asp.Net Zero.
I changed my search method
public IQueryable<DepartmentTypeView> SearchDepartmentType(GetAllDepartmentTypesInput input, int top)
{
try
{
var GROUP_FILTER = input.Filter;
var GROUP_CODE = input.GROUP_CODEFilter;
var GROUP_NAME = input.GROUP_NAMEFilter;
var AUTH_STATUS = input.AUTH_STATUSFilter;
var result = Context.Query<DepartmentTypeView>().FromSql($"EXEC CM_DEPT_GROUP_Search #p_GROUP_FILTER = {GROUP_FILTER}, #p_GROUP_CODE={GROUP_CODE}, #p_GROUP_NAME={GROUP_NAME}, #p_AUTH_STATUS={AUTH_STATUS}, #p_TOP={top}");
return result;
}
catch
{
return null;
}
}
and in the service, I call that function
var filteredDepartmentTypes = _departmentTypeRepository.SearchDepartmentType(input,100);
I also create new class to keep the result and don't forget to map that class with DTO class
configuration.CreateMap<DepartmentTypeView, DepartmentTypeDto>();
It works for me.

Web API Typeless OData Service with OWIN self-hosting returns 406 Not Acceptable

I'm trying to set up a Web API Typeless OData Service with OWIN self-hosting... =)
But why not working? :~(
This is some code I have partially extracted from all kinds of examples out there...
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
appBuilder.UseWebApi(config);
}
}
public class Program
{
public static IEdmModel Model = GetEdmModel();
static void Main(string[] args)
{
using (WebApp.Start<Startup>("http://localhost:8080"))
{
Console.WriteLine("Running...");
Console.ReadLine();
}
}
public static IEdmModel GetEdmModel()
{
var model = new EdmModel();
// Create and add product entity type.
var product = new EdmEntityType("NS", "Product");
product.AddKeys(product.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));
product.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String);
product.AddStructuralProperty("Price", EdmPrimitiveTypeKind.Double);
model.AddElement(product);
// Create and add category entity type.
var category = new EdmEntityType("NS", "Category");
category.AddKeys(category.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));
category.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String);
model.AddElement(category);
// Set navigation from product to category.
var propertyInfo = new EdmNavigationPropertyInfo();
propertyInfo.Name = "Category";
propertyInfo.TargetMultiplicity = EdmMultiplicity.One;
propertyInfo.Target = category;
var productCategory = product.AddUnidirectionalNavigation(propertyInfo);
// Create and add entity container.
var container = new EdmEntityContainer("NS", "DefaultContainer");
model.AddElement(container);
// Create and add entity set for product and category.
var products = container.AddEntitySet("Products", product);
var categories = container.AddEntitySet("Categories", category);
products.AddNavigationTarget(productCategory, categories);
return model;
}
}
public class ProductsController : ODataController
{
private static readonly IQueryable<IEdmEntityObject> Products = Enumerable.Range(0, 10).Select(i =>
{
var productType = (IEdmEntityType)Program.Model.FindType("NS.Product");
var categoryType = (IEdmEntityTypeReference)productType.FindProperty("Category").Type;
var product = new EdmEntityObject(productType);
product.TrySetPropertyValue("Id", i);
product.TrySetPropertyValue("Name", "Product " + i);
product.TrySetPropertyValue("Price", i + 0.01);
var category = new EdmEntityObject(categoryType);
category.TrySetPropertyValue("Id", i % 5);
category.TrySetPropertyValue("Name", "Category " + (i % 5));
product.TrySetPropertyValue("Category", category);
return product;
}).AsQueryable();
public EdmEntityObjectCollection Get()
{
// Get Edm type from request.
var path = this.Request.GetODataPath();
var edmType = path.EdmType;
Contract.Assert(edmType.TypeKind == EdmTypeKind.Collection);
var collectionType = edmType as IEdmCollectionType;
var entityType = collectionType.ElementType.Definition as IEdmEntityType;
var model = Request.GetEdmModel();
var queryContext = new ODataQueryContext(model, entityType);
var queryOptions = new ODataQueryOptions(queryContext, Request);
// Apply the query option on the IQueryable here.
return new EdmEntityObjectCollection(new EdmCollectionTypeReference(collectionType, false), Products.ToList());
}
public IEdmEntityObject GetProduct(int key)
{
object id;
var product = Products.Single(p => HasId(p, key));
return product;
}
public IEdmEntityObject GetCategoryFromProduct(int key)
{
object id;
var product = Products.Single(p => HasId(p, key));
object category;
if (product.TryGetPropertyValue("Category", out category))
{
return (IEdmEntityObject)category;
}
return null;
}
public IEdmEntityObject Post(IEdmEntityObject entity)
{
// Get Edm type from request.
var path = Request.GetODataPath();
var edmType = path.EdmType;
Contract.Assert(edmType.TypeKind == EdmTypeKind.Collection);
var entityType = (edmType as IEdmCollectionType).ElementType.AsEntity();
// Do something with the entity object here.
return entity;
}
private bool HasId(IEdmEntityObject product, int key)
{
object id;
return product.TryGetPropertyValue("Id", out id) && (int)id == key;
}
}
The result I get is:
{StatusCode: 406, ReasonPhrase: 'Not Acceptable', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Date: Mon, 12 May 2014 18:08:25 GMT
Server: Microsoft-HTTPAPI/2.0
Content-Length: 0
}}
From running this:
var client = new HttpClient();
var response = client.GetAsync("http://localhost:8080/api/Products").Result;
If you are using OData V4, you need to make a change in your controller:
Old:
using System.Web.Http.OData;
New:
using System.Web.OData;

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { n.AuthorId.ToString(), n.Name.ToString(), n.Location.ToString() }
}).ToList()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
I am writting ToList or Toarray is it not working the error comes :
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { n.AuthorId.ToString(), n.Name.ToString(), n.Location.ToString() }
}).ToList()
};
return Json(jsonData,JsonRequestBehavior.AllowGet);
}
From your code I assume your adding a custom property cell for display/storage purposes on the client-side. I would avoid this as your essentially coupling your API call to one particular client. I would suggest you simply return the data required & deal with it at the client-side specifically e.g.
Server
...
select new
{
Id = n.AuthorId,
Name = n.Name,
Location = n.Location
}).ToList();
...
Client
var response = ...
foreach (var author in response)
{
var cell = new string[] { author.Id.ToString(), author.Name, author.Location };
// do something with cell
}
You should try SqlFunctions.StringConvert to convert this, There is no overload for int so you should cast your number to a double or a decimal.
public ActionResult PopulateFromDB(string sidx, string sord, int page, int rows)
{
var context = new NerdDinnerEntities();
var jsonData = new
{
total = 1,
page = page,
sord =sord,
records = context.Authors.Count(),
rows = (from n in context.Authors
select new
{ AuthorId = n.AuthorId ,
cell = new string[] { SqlFunctions.StringConvert((double)n.AuthorId), n.Name, n.Location }
}).ToList()
};
return Json(jsonData,JsonRequestBehavior.AllowGet);
}
You are not using LinqToSql Classes, if you were using that your code should work, but as you mention that you are using LinqToEntity then You should use SqlFunctions.StringConvert to convert to string.

How to map lists with ValueInjector

I am using ASP.NET MVC 3.
Can someone please help me clarify what's happening here:
var person = new PersonRepository().Get();
var personViewModel = new PersonViewModel();
personViewModel.InjectFrom<LoopValueInjection>(person)
.InjectFrom<CountryToLookup>(person);
I have a grid on my Index view. Each row is an instance of a CategoryViewModel. So what I do is to get a list of all the categories and then map each Category to a CategoryViewModel, and then pass this list of CategoryViewModels to the view. Hou would I do a mapping like that?
IEnumerable<Category> categoryList = categoryService.GetAll();
I thought the following would work but it doesn't:
// Mapping
IList<CategoryViewModel> viewModelList = new List<CategoryViewModel>();
viewModelList.InjectFrom(categoryList);
AFAIK value injecter doesn't support automatic collection mapping like AutoMapper but you could use a simple LINQ expression and operate on each element:
IEnumerable<Category> categoryList = categoryService.GetAll();
IList<CategoryViewModel> viewModelList = categoryList
.Select(x => new CategoryViewModel().InjectFrom(x)).Cast<CategoryViewModel>()
.ToList();
//source list
IEnumerable<string> items = new string[] { "1", "2" };
// target list
List<int> converted = new List<int>();
// inject all
converted.InjectFrom(items);
And the extension method:
public static ICollection<TTo> InjectFrom<TFrom, TTo>(this ICollection<TTo> to, IEnumerable<TFrom> from) where TTo : new()
{
foreach (var source in from)
{
var target = new TTo();
target.InjectFrom(source);
to.Add(target);
}
return to;
}
ICollection<T> is the interface that got least features but a Add method.
Update
An example using more proper models:
var persons = new PersonRepository().GetAll();
var personViewModels = new List<PersonViewModel>();
personViewModels.InjectFrom(persons);
Update - Inject from different sources
public static ICollection<TTo> InjectFrom<TFrom, TTo>(this ICollection<TTo> to, params IEnumerable<TFrom>[] sources) where TTo : new()
{
foreach (var from in sources)
{
foreach (var source in from)
{
var target = new TTo();
target.InjectFrom(source);
to.Add(target);
}
}
return to;
}
Usage:
var activeUsers = new PersonRepository().GetActive();
var lockedUsers = new PersonRepository().GetLocked();
var personViewModels = new List<PersonViewModel>();
personViewModels.InjectFrom(activeUsers, lockedUsers);
Use this function definition
public static object InjectCompleteFrom(this object target, object source)
{
if (target.GetType().IsGenericType &&
target.GetType().GetGenericTypeDefinition() != null &&
target.GetType().GetGenericTypeDefinition().GetInterfaces() != null &&
target.GetType().GetGenericTypeDefinition().GetInterfaces()
.Contains(typeof(IEnumerable)) &&
source.GetType().IsGenericType &&
source.GetType().GetGenericTypeDefinition() != null &&
source.GetType().GetGenericTypeDefinition().GetInterfaces() != null &&
source.GetType().GetGenericTypeDefinition().GetInterfaces()
.Contains(typeof(IEnumerable)))
{
var t = target.GetType().GetGenericArguments()[0];
var tlist = typeof(List<>).MakeGenericType(t);
var addMethod = tlist.GetMethod("Add");
foreach (var sourceItem in source as IEnumerable)
{
var e = Activator.CreateInstance(t).InjectFrom<CloneInjection>(sourceItem);
addMethod.Invoke(target, new[] { e });
}
return target;
}
else
{
return target.InjectFrom(source);
}
}
For those like me who prefer shortest notations possible
public static ICollection<TTarget> InjectFromList<TTarget, TOrig>(this ICollection<TTarget> target, ICollection<TOrig> source) where TTarget : new()
{
source.Select(r => new TTarget().InjectFrom(r))
.Cast<TTarget>().ToList().ForEach(e => target.Add(e));
return target;
}
public static ICollection<TTarget> InjectFromList<TTarget, TOrig>(this ICollection<TTarget> target, params ICollection<TOrig>[] sources) where TTarget : new()
{
sources.ToList().ForEach(s => s.ToList().Select(r => new TTarget().InjectFrom(r))
.Cast<TTarget>().ToList().ForEach(e => target.Add(e)));
return target;
}
Create a generic list mapper:
public class ValueMapper
{
public static TResult Map<TResult>(object item) where TResult : class
{
return item == null ? null : Mapper.Map<TResult>(item);
}
public static IEnumerable<TResult> MapList<TResult>(IEnumerable<object> items) where TResult : class
{
return items?.Select(i => Mapper.Map<TResult>(i));
}
}
Now you can reference the ValueMapper class wherever you want, and call both Map and MapList
var mydtos = ValueMapper.MapList<MyDto>(dtos);
var mydto = ValueMapper.Map<MyDto>(dto);

Resources