Most of my entities (not all) have two properties called CompanyId and Deleted. How would be possible to auto insert these two properties for all select requests instead of setting manually on every single query I have along the whole app.
Example:
db.MyEntity.Where(me => me.Id == 1).Select(me => me.Description)
Check dynamically it the entity has the props CompanyId and Deleted.
Case affirmative, transform it like this
db.MyEntity.Where(me => me.Id == 1 && Deleted == false && CompanyId == 1).Select(me => me.Description)
Case negative, keep the same select query.
It would help me having to set these conditions to all my queries in which has the properties available.
You can add filter with the help of nuget package: EntityFramework.Filters. Also, good idea is to create common interface instead of dynamically checking for properties existence:
public interface IMyEntityInterface
{
public int Id {get;set;}
public bool Deleted {get;set;}
}
public class MyEntity : IMyEntityInterface
{
public int Id {get;set;}
public bool Deleted {get;set;}
//other stuff
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions
.Add(FilterConvention.Create<IMyEntityInterface, int, bool>("MyFilter", (entity, Id, Deleted) => entity.Id == Id && entity.Deleted == Deleted);
}
Usage:
var filter = db.EnableFilter("MyFilter");
filter.SetParameter("Id", 1);
filter.SetParameter("Deleted", false);
var data = db.MyEntity.Where(me => me.CompanyId == 1).Select(me => me.Description);
//select Description from MyEntities where Id = 1 and Deleted = false and CompanyId = 1
Related
I have the following 3 models:
class Owner
{
public Guid Id { get; set; }
public IList<AccessShare> AccessShares { get; set; } = new List<AccessShare>()
}
class AccessShare
{
public Guid OwnerId { get; set; }
public Guid AccessorId { get; set; }
}
class File
{
public Guid OwnerId { get; set; }
public Owner Owner { get; set; }
public string Filename { get; set; }
}
The purpose of the system is assign ownership over files to one user, and to only allow other users to see those entities when there is an AccessShare from Accessor to Owner.
I have the following code that I'm using to bring back a file:
Guid fileId = <code that gets the accessor id>
Guid accessorId = <code that gets the accessor id>
File? file = DBContext
.Set<File>()
.Where(e => e.Id == fileId)
.Where(e => e.Owner.Id == accessorId || e.Owner.AccessShares.Any(a => a.AccessorId == accessorId))
.Include(e => e.Owner)
.Include(e => e.Owner.AccessShares)
.FirstOrDefault();
The issue I'm getting is that if null is returned, I don't know it that's because there isn't a File entity with the given id, or if there isn't an access share that allows access.
If this was raw SQL I'd do a left join from Owners to AccessShares with the above condition, this would always give me back the file/owner if found, and then optionally any access shares that meet the criteria.
I can find examples of how to do it in SQL and in Linq, but I can't find any examples using the DbSet fluid style.
From what you describe the part you are having issue with is:
.Where(e => e.Owner.Id == accessorId || e.Owner.AccessShares.Any(a => a.AccessorId == accessorId))
This will only return the desired file If the file is owned by the user, or has an access share.
Now if you want to return a File if it exists and provide an indication to the user whether they have permissions to access it, that could be done via projection rather than returning entities:
For example if I have a File ViewModel/DTO:
public class FileViewModel
{
public int FileId { get; set; }
// any other fields about the file I might display/use...
public bool UserHasAccess { get; set; }
public bool UserIsOwner { get; set; }
}
then I can query and populate these computed values via EF:
FileViewModel? file = DBContext
.Set<File>()
.Where(e => e.Id == fileId)
.Select(e => new FileViewModel
{
FileId = e.FileId,
// other fields...
UserHasAccess = e.OwnerId == accessorId || e.Owner.AccessShares.Any(a => a.AccessorId == accessorId),
UserIsOwner = e.OwnerId == accessorId
}).SingleOrDefault();
This will return a file if it exists, and includes details that your view/logic can use to determine if the file can be accessed by the current user, and/or is owned by the current user. Note that we remove the extra Where condition, and we don't need Include to access those related members when projecting.
Alternatively computed properties like this can be added to the File entity to do the same thing, however for these properties to function would require that the Owner and AccessShares are eager loaded (/w Include) or can be lazy loaded, accepting the potential performance pitfalls that can come with that. IMHO Projection /w Select is almost always preferable as it can also improve the resource footprint and performance of the querying.
I have a Category table and it has a Parent Category, I try to iterate over all the categories and get the parents categories with it's Inverse Parent but some of them returns without the inverse parents from unknown reason.
Categories.cs
public partial class Categories
{
public Categories()
{
InverseParent = new HashSet<Categories>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime CreateDate { get; set; }
public bool? Status { get; set; }
public virtual Categories Parent { get; set; }
public virtual ICollection<Categories> InverseParent { get; set; }
}
This is how I try to iterate them to create a select list items:
var parentCategories = await _context.Categories.
Include(x => x.Parent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
So the problem is that some of my parent categories returns all their children categories and some don't and I don't why.
There are several issues with that code, all having some explaination in the Loading Related Data section of the documentation.
First, you didn't ask EF Core to include InverseParent, so it's more logically to expect it to be always null.
What you get is a result of the following Eager Loading behavior:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Second, since the query is changing it's initial shape (Select, Disctinct), it's falling into Ignored Includes category.
With that being said, you should build the query other way around - starting directly with parent categories and including InverseParent:
var parentCategories = await _context.Categories
.Include(x => x.InverseParent)
.Where(x => x.InverseParent.Any(c => c.Status == true)) // to match your query filter
.ToListAsync();
While you are including Include(x => x.Parent), you don't seem to do the same for InverseParent. This might affect your results exactly the way you describe. Would including it fix it?
parentCategories = await _context.Categories.
Include(x => x.Parent).
Include(x => x.InverseParent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
UPD: Since you are selecting x => x.Parent anyway it might be necessary to use ThenInclude() method instead.
I have the following 3 tables ( 1 base table and other 2 sattelite tables for each vehicle type.)
Vehicles
ID VehicleType
-----------------------------
1 Car
2 Truck
Cars
ID Make Model
-------------------------
1 Toyota Camry
2 Honda Accord
Trucks
ID Make Model
--------------------
1 Ford F150
2 Dodge Ram
Then i have corresponding DTO
public class VehicleDTO
{
public int ID {get;set;}
public int VehicleType {get;set;}
public IEnumerable<CarDTO> Cars {get;set;}
public IEnumerable<TruckDTO> Trucks {get;set;}
}
public class CarDTO
{
public int ID {get;set;}
public string Make {get;set;}
public string Model {get;set;}
}
public class TruckDTO
{
public int ID {get;set;}
public string Make {get;set;}
public string Model {get;set;}
}
Then i have list of Vehicle DTO as an argument to the method. I want find vehicles from DTO list that does not exists in the database by matching Make and Model for that Vehicle Type.
Idea is to then insert the missing vehicles into database.
I have the following query
public void FindMissingVehicles(IEnumerable<VehicleDTO> dtos)
{
var cars = (from dto in dtos
where !(from c in dbcontext.Cars
select new { c.Make, c.Model })
.Any(x => dto.VehicleType == 'Car' && dto.Car.Make == x.Make && dto.Car.Model == x.Model)
select dto).ToList();
var trucs = (from dto in dtos
where !(from t in dbcontext.Trucks
select new { t.Make, t.Model })
.Any(x => dto.VehicleType == 'Truck' && dto.Truck.Make == x.Make && dto.Truck.Model == x.Model)
select dto).ToList();
//insert missing cars and trucks into db here
}
The query above throws exception
Message "Non-static method requires a target." string
Questions
1> How do i construct this query.
2> Can i make this query async by using AnyAsync and ToListAsync. (I know i have to make method async with Task, and use await inside howevere i could not figure out the async query syntax)
Moreover, your approach has performance issue - you perform N queries - one for each of dto, instead of doing only two queries: one for cars and one for trucks:
var allCars = dtos.Where(x => x.VehicleType == "Car").ToList()
.SelectMany(x => x.Cars.Select(y => y.Make + "-" + y.Model).ToList()).ToList();
var existedCars = await dbcontext.Cars.Where(x => allCars.Contains(x.Make + "-" + x.Model))
.Select(x => x.Make + "-" + x.Model).ToListAsync();
var newCars = allCars.Except(existedCars).Select(x =>
{
var temp = x.Split('-');
return new CarDTO
{
Make = temp[0],
Model = temp[1]
};
}).ToList();
//exactly same code for Trucks
This is because of you can't have nested query in linq when one table is from dbContext and the other one is from in-memory enumerable, so if dbcontext.Cars and dbcontext.Trucks don't have a lot of rows, it's a good idea to load them in memory and use nested query like below:
var listCars = dbcontext.Cars.ToList();
var listTrucks = dbcontext.Trucks.ToList();
var cars = (from dto in dtos
where !(from c in listCars
select new { c.Make, c.Model })
.Any(x => dto.VehicleType == 'Car' && dto.Car.Make == x.Make && dto.Car.Model == x.Model)
select dto).ToList();
var trucs = (from dto in dtos
where !(from t in listTrucks
select new { t.Make, t.Model })
.Any(x => dto.VehicleType == 'Truck' && dto.Truck.Make == x.Make && dto.Truck.Model == x.Model)
select dto).ToList();
I have a class that is called User. It has a property called Roles. Each user has many roles and each role can be assigned to many users.
The relationship is established by a 3rd table. I'd like to be able to remove a role from a User. However, I am calling the database twice. Once to fully load the "User" role and once to delte the role.
var user = this.Users.Include(f => f.Roles)
.SingleOrDefault(f => f.CustomerID == customerId && f.UserID == userId);
if (user != null)
{
var role = user.Roles.FirstOrDefault(f => f.RoleID == roleId);
if (role != null)
{
user.Roles.Remove(role);
return this.SaveChanges() > 0;
}
}
I tried doing this but it didn't work.
var user = new User { CustomerID = customerId, UserID = userId };
this.Users.Attach(user);
var role = new Role { RoleID = roleId };
this.Roles.Attach(role);
user.Roles.Add(role);
user.Roles.Remove(role);
return this.SaveChanges() > 0;
My Context has a DbSet<User> and DbSet<Role>. I don't have one for UserRole and I don't intend to have it.
Am i doing it right and do I need to always do 2 database calls?
-- User Class
[DataContract(Namespace = "urn:AES/Schemas/2014/09", IsReference = true)]
public class User
{
.....
[DataMember]
public List<Role> Roles { get; set; }
[DataMember]
public bool Active { get; set; }
}
The Mapping
this.HasMany(u => u.Roles)
.WithMany()
.Map(m =>
{
// The "left" key is the one specified in the HasMany method; the "right" key is the one specified in the WithMany method.
m.MapLeftKey(new string[] { "CustomerID", "UserID" });
m.MapRightKey("RoleID");
m.ToTable("UserRoles");
}
);
// Table & Column Mappings
this.ToTable("Users");
}
Curious why you don't model those other tables like UserRoles, are you just lazy? =)
But seriously, what you're doing is fine however for mass deletes EF doesn't have a bulk delete capability, you are better off simply executing the DELETE statement directly, like this:
using (System.Data.Common.DbCommand cmd = Context.Database.Connection.CreateCommand())
{
cmd.CommandText = #"DELETE FROM Table WHERE KeyColumn = #Id";
YourSqlHelper.AddParameter(cmd, "#Id", id);
cmd.ExecuteNonQuery();
}
I have one to many relationship between ItemPrice and ItemPriceHistory classes my mapping follows below:
public class ItemPrice
{
public long ID {get; set;}
public List<ItemPriceHistory> ItemPriceHistories { get; set; }
public ItemPrice()
{
ItemPriceHistories = new List<ItemPriceHistory>();
}
}
public class ItemPriceHistory
{
public ItemPrice ItemPrice { get; set; }
public long ID {get; set;}
public bool IsCurrent { get; set; }
}
modelBuilder.Entity<ItemPriceHistory>()
.HasRequired(h => h.ItemPrice)
.WithMany(p => p.ItemPriceHistories)
.Map(h => h.MapKey("ItemPrice_ID"));
I am trying to update previous ItemPriceHistory Entries and try to add a new ItemPriceHistory Entry.
var dbItemPrice = repo.Std.Get<ItemPrice>()
.SingleOrDefault(c => c.ID == id);
if (dbItemPrice == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
//query for matching ItemPriceHistory
var dbPriceHistories = repo.Std.Get<ItemPriceHistory>()
.Include(h=>h.ItemPrice, repo.Std)
.Where(h => h.ItemPrice.ID == ItemPrice.ID)
.OrderByDescending(h => h.ModifiedDate);
#region new history entry
var newHistoryEntry = new ItemPriceHistory();
newHistoryEntry.IsCurrent = true;
newHistoryEntry.ItemPrice = dbItemPrice;
//copy the current pirce list and update it with new history entry
var currentPriceHistoryList = dbPriceHistories.ToList();
currentPriceHistoryList.ForEach(h => { h.IsCurrent = false; });
currentPriceHistoryList.Add(newHistoryEntry);
//new price list
var newHistoryList = new List<ItemPriceHistory>();
currentPriceHistoryList.ForEach(h => newHistoryList.Add(new ItemPriceHistory
{
ItemPrice = h.ItemPrice,
IsCurrent = h.IsCurrent,
}
));
#endregion
//delete current price histories
dbItemPrice.ItemPriceHistories.Clear();
// add histories to database
newHistoryList.ForEach(h => dbItemPrice.ItemPriceHistories.Add(h));
Context.SaveChanges();
When it calls SaveChanges(), I get the following error:
{"An error occurred while saving entities that do not expose foreign
key properties for their relationships. The EntityEntries property
will return null because a single entity cannot be identified as the
source of the exception. Handling of exceptions while saving can be
made easier by exposing foreign key properties in your entity types.
See the InnerException for details."}
InnerException: {"A relationship from the 'ItemPriceHistory_ItemPrice'
AssociationSet is in the 'Deleted' state. Given multiplicity
constraints, a corresponding 'ItemPriceHistory_ItemPrice_Source' must
also in the 'Deleted' state."}
I do not want to delete my ItemPrice_Source. I just want to delete current ItemPriceHistories and update previous ItemPriceHistories and add new ItemPriceHistory entry. How can I safely update ItemPriceHistory entries along with new ItemPriceHistory entry?
Thanks!
I use an example that shoudl fit into your STD generic repository class.
Looks like you are using this pattern.
Did you try something like
public virtual IQueryable<T> GetList(Expression<Func<T, bool>> predicate, bool withTracking = false)
{
if (withTracking)
return QuerySet.Where(predicate);
return QuerySet.Where(predicate).AsNoTracking();
}
// then
var iph_list = RepItemPriceHist.GetList(h=>h.ItemPrice.Id == VarID)
// psuedo code... foreach history entity entitity.Iscurrent = false;
so now these entities should be changed and managed by the statemanager.
add teh new entries....
// add histories to database
newHistoryList.ForEach(h => dbItemPrice.ItemPriceHistories.Add(h));
Context.SaveChanges();
I didnt understand why you need to CLEAR the collection
Arent you updating existing records and adding new ones ?
Good luck
Can't you just load the ItemPrice including all current ItemPriceHistories, then set the IsCurrent flag of those histories to false and add a new history with IsCurrent = true? Change tracking should update the current histories and insert a new one to the database:
var dbItemPrice = repo.Std.Get<ItemPrice>()
.Include(i => i.ItemPriceHistories, repo.Std)
.SingleOrDefault(i => i.ID == id);
if (dbItemPrice == null)
return Request.CreateResponse(HttpStatusCode.NotFound);
foreach (var history in dbItemPrice.ItemPriceHistories)
history.IsCurrent = false;
dbItemPrice.ItemPriceHistories.Add(new ItemPriceHistory { IsCurrent = true });
Context.SaveChanges();