ef core context set from property type - properties

On my project I am doing soft delete (changing to false isActive property and filtering records using EF Core query filter ) for records and I need a way to delete (also soft delete) navigation properties (which can be a reference property or a collection) that I put a attribute to them.
This is the Attribute I wrote for property :
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
public class DatabaseAttribute : Attribute
{
bool softDelete;
public virtual bool SoftDelete
{
get { return softDelete; }
set { softDelete = value; }
}
}
It used :
public Guid? CargoTransactionId { get; set; }
[Database(SoftDelete = true)]
public CargoTransaction CargoTransaction { get; set; }
The Code at below I can find navigation property which I have flagged for soft delete :
var entries = Context.ChangeTracker.Entries();
foreach (var entry in entries)
{
var entryNavigations = entry.Navigations;
foreach (var navigation in entryNavigations)
{
var dbAttribute = navigation.Metadata.PropertyInfo.CustomAttributes
.Where(p => p.AttributeType == typeof(DatabaseAttribute))
.FirstOrDefault();
}
}
bu I could not able to find DbSet entity type to use in context like with the method DbContext.Set<TEntity>().
Can you help with that?

Related

ASP.NET Core MVC - Is there any way to change the value of specific data on model with session?

Is there any way to change the value of specific data on model?
Like for example my model contains
item_id
item_description
quantity
and you have many records on your data model with the use of session.
on input during changing the quantity i want to update the value on of model with session?
is it recommended? or is there any better way than my approach? thank you.
What I was trying to achieve is to modify the data inside the
session. Is modifying data on session also working like a database?
where you can get the id and modify the data that you want?
From your description, I assume you are using Session to the list of records, and now you want to update the session data, right? If that is the case, you could refer to the following sample:
Refer this article to enable the session middleware in the Startup.cs file.
[Note]: Please remember to set the session expired time via the IdleTimeout property. And, since you are using session to store a list of Objects, remember to add the SessionExtensions.
//required using Microsoft.AspNetCore.Http;
//required using System.Text.Json;
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default : JsonSerializer.Deserialize<T>(value);
}
}
Add an Item View Model:
public class ItemViewModel
{
public int ItemId { get; set; }
public string ItemDescription { get; set; }
public int Quantity { get; set; }
}
Controller: I'm using a repository to set the Initial data. After getting data from session, we could modify the data, and then, call the set method to save the latest data to the session.
private readonly ApplicationDbContext _context;
private readonly IDataRepository _repository;
public TestController(ApplicationDbContext context, IDataRepository repository)
{
_context = context;
_repository = repository;
}
private readonly string SessionKeyName = "itemlist";
public IActionResult ItemIndex()
{
List<ItemViewModel> items = new List<ItemViewModel>();
//check if the session is exist or not.
if (HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName) == default)
{ //get the initial data.
items = _repository.GetItemViewModels();
//set value to the session.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
}
else
{
items = HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName);
}
return View(items);
}
public IActionResult EditItem(int Id)
{
List<ItemViewModel> items = new List<ItemViewModel>();
//check if the session is exist or not.
if (HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName) == default)
{
items = _repository.GetItemViewModels();
//set value to the session.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
}
else
{
items = HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName);
}
return View(items.Where(c=>c.ItemId == Id).FirstOrDefault());
}
[HttpPost]
public IActionResult EditItem(ItemViewModel item)
{
List<ItemViewModel> items = new List<ItemViewModel>();
//check if the session is exist or not.
if (HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName) == default)
{
items = _repository.GetItemViewModels();
//set value to the session.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
}
else
{
items = HttpContext.Session.Get<List<ItemViewModel>>(SessionKeyName);
}
//based on the primary key to find the special item,
var i = items.Where(c => c.ItemId == item.ItemId).FirstOrDefault();
//update the quantity.
i.Quantity = item.Quantity;
//Update the session with the latest data.
HttpContext.Session.Set<List<ItemViewModel>>(SessionKeyName, items);
//redirect to the ItemIndex action and reload the Index page.
return RedirectToAction(nameof(ItemIndex));
}
The result like this:

Asp.Net core ODATA compute property after filter

Is there any way to create a computed property on an ODATA HTTP request, after the ODATA filter has been processed?
I have the following model
class Person
{
public string Name { get; set; }
public string Email { get; set; }
public string ImageLink { get; set; } //This is computed
}
And the following IEdmModel
private static IEdmModel GetEdmModel(IApplicationBuilder app)
{
var builder = new ODataConventionModelBuilder(app.ApplicationServices).EnableLowerCamelCase();
var person = builder.EntitySet<Person>("People").EntityType;
person.Property(x => x.Name);
person.Property(x => x.Email);
person.Property(x => x.ImageLink).IsOptional();
return builder.GetEdmModel();
}
The image link is generated dynamically and is a fairly costly process, as the link is signed. Is there a way to add only add this property when it is explicitly included by a $select and to only calculate after the filter is applied?
Setting the Select(SelectExpandType.Allowed) property does not seem to exclude it from the model.
I've managed to manually exclude the property by checking the query string in the controller. But the property is still included in the model, the value is null, and obviously this runs before any filter has applied.
var people = await _personService.FetchAllPeopleAsync(cancellationToken);
if (Request.Query.TryGetValue("$select", out var values) &&
values.Any(v => v.Contains(nameof(Person.ImageLink))))
{
return _photoService.AttachImageLinks(Url, people);
}
return people;

Entity framework Core Raw SQLQueries with custom model

Using Entity Framework 6, I was able to use execute a Raw SQL Query and use a custom model which was not defined in the DBContext in order to store the output of the query. A simple example is the following:
List<MyModel> data = context.Database.SqlQuery<MyModel>("SELECT Orders.OrderID, Customers.CustomerName FROM Orders INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID;").ToList();
I execute one SQL command and I expect a list of custom models.
I try to do something similar with Entity Framework Core and the closest example that I found will force me to define a property from DBContext. This will not allow me to use a custom model to fill the data that SQL server will return.
var books = context.Books.FromSql("SELECT * FROM Books").ToList();
This query informs Entity Framework Core that the query will return a list of books. Is there a way to implement something like this in Entity Framework Core?
From .NET Core 2.1:
Add modelBuilder.Query<YourModel>() to OnModelCreating(ModelBuilder modelBuilder)
Use context.Query<YourModel>().FromSql(rawSql) to get data
Here's how I was able to get this working (for completeness):
MyModel.cs:
public class MyModel
{
// The columns your SQL will return
public double? A { get; set; }
public double? B { get; set; }
}
Add class that just inherits from your original EF context class (i called mine DbContextBase):
public class DbContext : DbContextBase
{
public virtual DbSet<MyModel> MyModels { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Necessary, since our model isnt a EF model
modelBuilder.Entity<MyModel>(entity =>
{
entity.HasNoKey();
});
}
}
Use that class (instead of your original EF context class):
// Use your new db subclass
using (var db = new DbContext())
{
var models = await db.MyModels.FromSqlRaw(...).ToListAsync(); // E.g.: "SELECT * FROM apple A JOIN banana B ON A.col = B.col"
}
Notes:
If you need to, just use FromSqlInterpolated instead of
FromSqlRaw
The "db context" subclass allows you to update EF models without affecting your "polyfill" code
Works with SQL Server stored procs that return only 1 result set
The question was about .NET Core 2. Now I have a solution and I am going to write it here so that someone else could use it in case he/she needs it.
First of all we add the following method in dbContext class
public List<T> ExecSQL<T>(string query)
{
using (var command = Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
Database.OpenConnection();
List<T> list = new List<T>();
using (var result = command.ExecuteReader())
{
T obj = default(T);
while (result.Read())
{
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties())
{
if (!object.Equals(result[prop.Name], DBNull.Value))
{
prop.SetValue(obj, result[prop.Name], null);
}
}
list.Add(obj);
}
}
Database.CloseConnection();
return list;
}
}
Now we can have the following code.
List<Customer> Customers = _context.ExecSQL<Customer>("SELECT ......");
follow these steps:
Create your model
Probably it could be better if you can reduce it to a model as generic as possible but it's not a must:
public class MyCustomModel
{
public string Text { get; set; }
public int Count { get; set; }
}
Add it to your own DbContext
Create DbSet for your custom model
public virtual DbSet<MyCustomModel> MyCustomModelName { get; set; }
Keep in mind to specify your custom model has no key
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
...
modelBuilder.Entity<MyCustomModel>().HasNoKey();
}
Use it from your dbContext instance
async public Task<List<MyCustomModel>> GetMyCustomData()
{
var rv = new List<MyCustomModel>();
using (var dataContext = new DbContext())
{
var sql = #"
select textField as 'Text', count(1) as 'Count'
from MyTable";
rv = await dataContext.Set<MyCustomModel>().FromSqlRaw(sql).ToListAsync();
}
return rv;
}

Best Practice with MVC4 and EF5 to apply changes

I have a CustomerOrder-view where I would like to change an existing CustomerOrder.
I have a viewmodel that very simpliefied looks something like this:
public class CustomerOrderViewModel
{
public int ID { get; set; }
public Customer Customer { get; set; }
public DateTime Date { get; set; }
public IEnumerable<OrderRow> OrderRows { get; set; }
}
public class OrderRow
{
public int id { get; set; }
public int price { get; set; }
}
public class Customer
{
public string Name { get; set; }
}
I also have a database with mapping tables / fields.
In my GET Action Method I load the Order with the help of Automapper like this:
var customerOrder = using (var ctx = new My_Entities()) {
return ctx.CustomerOrders.
Include("Orderrows").
Include("Customer").
Single(o => o.CustomerOrderID == id);
}
var model= AutoMapper.Mapper.DynamicMap<DataAccessLayer.CustomerOrder, CustomerOrderViewModel>(customerOrder);
In the View I use Knockout to bind to a viewmodel there, where the user can update the CustomerOrder. That includes editing Customer information and adding new orderrows etc.
Then in the post back a map the ViewModel back to the ObjectContext CustomerOrder:
var customerOrderToBeSaved =
AutoMapper.Mapper.DynamicMap<CustomerOrderViewModel, CustomerOrder>(
customerOrderViewModel);
try
{
using (var ctx = new MyEntities())
{
ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.CustomerOrders.ApplyCurrentValues(customerOrderToBeSaved);
...
ctx.SaveChanges();
}
}
I get the error message: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
OK, that I can understand. But how should I go about this? Can I get the existing object and apply Changes to that one, because that is really what I'd like. I've tried to look up the old one and detach it but I haven't got it to wrok.Perhaps I'm doing this in a completely wrong way. Please advice.
You should not attach customerOrderToBeSaved, see MSDN about the argument of ApplyCurrentValues.
The detached object that has property updates to apply to the original object.
So you've got to load the entity from the database into the context and then ApplyCurrentValues with the detached object that has the new values.
You don't have to load the row from the database to update it.
You can do something like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.Entry( entity ).State = EntityState.Modified;
ctx.SaveChanges();
This will tell EF to issue an UPDATE SQL statement that overwrites all the columns in the record.
You can select which columns you want to update like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
var entry = ctx.Entry( entity );
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
...
ctx.SaveChanges();
If you do this, EF will only include the columns you've marked as modified in the UPDATE statement.

How to automap a collection of components with Fluent NHibernate?

All of my entities and value objects implement marker interfaces IEntity and IValueObject. I have set them up to be treated as components like so:
public override bool IsComponent(Type type)
{
return typeof(IValueObject).IsAssignableFrom(type);
}
public override bool ShouldMap(Type type)
{
return typeof(IEntity).IsAssignableFrom(type) || typeof(IValueObject).IsAssignableFrom(type);
}
Unfortunately, this does not seem to allow entities that have collections of value objects to be automapped as component collections. For example:
public class MyEntity : IEntity
{
public IList<MyValueObject> Objects { get; set; }
}
public class MyValueObject : IValueObject
{
public string Name { get; set; }
public string Value { get; set; }
}
Is there any way to define a convention such that, any time an IEntity has an IList of a type that implements IValueObject, it gets mapped as if I had specified:
HasMany(x => x.Objects)
.Component(x => {
x.Map(m => m.Name);
x.Map(m => m.Value);
});
What I don't want to do is have to manually do these overrides for every class and write out each property for the value object again and again.
Create a new class that inherits from HasManyStep (FluentNHibernate.Automapping.Steps).
Override the ShouldMap() method with something like :
return base.ShouldMap(member) && IsCollectionOfComponents(member)
Add your logic to :
public void Map(ClassMappingBase classMap, Member member)
{ ... }
Replace the default step with your new one :
public class MyMappingConfiguration : DefaultAutomappingConfiguration
{
public override IEnumerable<IAutomappingStep> GetMappingSteps(AutoMapper mapper, IConventionFinder conventionFinder)
{
var steps = base.GetMappingSteps(mapper, conventionFinder);
var finalSteps = steps.Where(c => c.GetType() != typeof(FluentNHibernate.Automapping.Steps.HasManyToManyStep)).ToList();
var idx = finalSteps.IndexOf(steps.Where(c => c.GetType() == typeof(PropertyStep)).First());
finalSteps.Insert(idx + 1, new MyCustomHasManyStep(this));
return finalSteps;
}
}
Note : You could also get the original source code of HasManyStep.cs and copy it to your project to introduce your custom logic.