Using sets of Entity Framework entities at runtime - entity-framework

I have an EF6 setup against a sql server db with about 60 tables in it.
I have entities for each table. What i'm trying to do is run the same method against a set of these entities that will be known at runtime.
The method is a qa/qc routine that does some data check on particular fields that are assured to be in each table.
I guess what i want to do is make the entity a parameter to the method so i can call it consecutive times.
I would also want to make a set of entities to pass as the parameter.
something like this:
List<string> entList = new List<string>(){"Table1","Table2","Table3"};
foreach (entName in entList)
{
//create an entity with the string name
//call myQAQCMethod with the entity
}
MyQAQCMethod (entity SomeEntity)
{
//run against this entity
doQAQC(SomeEntity);
}
Can this be done? Is it a job for reflection?
EDIT
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
is precisely want i want to do. The thing is I want to avoid typing this loop 60 times. I think i'm looking for a way to "feed" a set of entities to this single method.
Also, thank you very much for helping me. I'm learning a lot.

You need to abstract an interface (entity framework won't even notice):
interface IQaQcable
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
public class EntityA : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
public class EntityB : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
// in some unknown utility class
void MyQaQcMethod<T>(T entity) where T : IQaQcable
{
doSomethingWithIQaQcableProperties(entity.CommonInt, entity.CommonString);
}
// in some unknown test class
void Test()
{
var entities = new List<IQaQcable> { new EntityA(), new EntityB() };
foreach (var e in entities)
MyQaQcMethod(e);
}
Now, you could extract a base class from which each derives that actually implements the CommonInt and CommonString properties for each entity needing them, but that can get kind of tricky with Table-Per-Type/Table-Per-Hierarchy, so I'd start with this, and then consider introducing either an abstract or concrete base class as an improvement.
EDIT
Maybe your looking for something simpler than I first thought, based on your last comment.
Let's give ourselves what the DbContext for this might look like:
class Context : DbContext
{
public virtual DbSet<EntityA> EntityAs { get; set; }
public virtual DbSet<EntityB> EntityBs { get; set; }
}
So, it could just be that you wish to do this:
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
Keeping in mind, if there is some set of properties in common across entity classes, you could still do something like the following:
IEnumerable<T> MyQaQcMethod(IQueryable<T> entities, T referenceEntity) where T : IQaQcAble
{
return entities.Where(e => SomePredicate(e, referenceEntity));
}
void Test()
{
using (var context = new Context())
{
// EntityA implements IQaQcAble
var resultsForA = MyQaQcMethod(context.EntityAs, defaultEntity).ToArray();
// so does EntityB, so can call with either
var resultsForB = MyQaQcMethod(context.EntityBs, defaultEntity).ToArray();
}
}
Keep in mind, to avoid modifying the generated entity classes, you could implement the interface members — and the interface — in a separate source file using partial classes. E.g.
// IQaQcAble.cs
internal interface IQaQcAble
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
// a class whose existing property names match the interface
public partial class EntityA : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return CommonInt; }
set { CommonInt = value; }
}
string IQaQcAble.CommonString
{
get { return CommonString; }
set { CommonString = value; }
}
}
// a class whose property names differ
public partial class EntityB : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return SomeOtherInt; }
set { SomeOtherInt = value; }
}
string IQaQcAble.CommonString
{
get { return SomeOtherInt.ToString(); }
set { SomeOtherInt = Convert.ToInt32(value); }
}
}

Related

Update Navigation Property with Entity.CurrentValues.SetValues

I have a Kalem Entity with a collection of DigerKalemMaliyetleri property, which is a collection of MaliyetBirimi objects. DigerKalemMaliyetleri is of JSON type and stored at the same table as a JSON column.
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}
When I try to update entity with only DigerKalemMaliyetleri property changed:
DataContext.Entry<Kalem>(first).CurrentValues.SetValues(second);
SQL Update command isn't executed and database record is not updated.
How could I update the entity without explicitly setting DigerKalemMaliyetleri property?
Regards
I had the same problem, you cann't actually use SetValues to update navigation property, you nead instead use DataContext.Update(YourNewObj) and then DataContext.SaveChanges();, or if you want to use SetValues approach, you need:
-Get the exist entry
Kalem existObj = DataContext.Kalems.Find(YourNewObj.Id);
-Loop in navigations of updating entry and the existing one to set the values of updating entry:
foreach(var navObj in DataContext.Entry(YourNewObj).Navigations)
{
foreach(var navExist in DatatContext.Entry(existObj).Navigations)
{
if(navObj.Metadata.Name == navExist.MetaData.Name)
navExist.CurrentValue = navObj.CurrentValue;
}
}
-Update also changes of direct properties:
DataContext.Entry(existObj).CurrentValues.SetValues(YourNewObj);
-Save your Updating:
DataContext.SaveChanges();
You can also check if you need to load your Navigations before going in foreach loop, otherwise you will get an error.
Please if you see beter scenario, correct me.
It's hard to know exactly what you're doing without a complete code sample. Note also that you're trying to set all properties of first from second, including e.g. the Id, which is probably not what you want.
Here's a complete code sample which works for me:
await using (var ctx = new BlogContext())
{
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
ctx.Kalem.Add(new()
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 1, Maliyet = 2 }
}
});
await ctx.SaveChangesAsync();
}
await using (var ctx = new BlogContext())
{
var first = ctx.Kalem.Find(1);
var second = new Kalem
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 3, Maliyet = 4 }
}
};
ctx.Entry(first).Property(k => k.DigerKalemMaliyetleri).CurrentValue = second.DigerKalemMaliyetleri;
await ctx.SaveChangesAsync();
}
public class BlogContext : DbContext
{
public DbSet<Kalem> Kalem { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(#"Host=localhost;Username=test;Password=test")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
}
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}

EF6 - Get entity for DbUpdateCommandTree in DbCommandTreeInterceptor

I am trying to get the value of a "NotMapped" property for a Entity/class when intercepting a DbUpdateCommandTree.
I have looked through the various metadata, but I cannot find the "link" to the Entity from the CommandTree, so unfortunately I am stuck.
Is it even possible ?
public class SomeEntity
{
public int ID { get; set; }
[NotMapped]
public int SomeUnmappedProperty { get; set; }
}
public class CommandTreeInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext ctx)
{
if (ctx.OriginalResult.DataSpace == DataSpace.SSpace)
{
var updateCommand = ctx.OriginalResult as DbUpdateCommandTree;
if (updateCommand != null)
{
// I would like to get a value of a specific property here.
// Pseudo code
var val = updateCommand.Entity.GetPropertyValue("SomeUnmappedProperty") as int;
}
}
}
}

Entity framework with IN Clause in Repository Pattern

I looking for some help on how to implement IN clause in the repository pattern. Rather than making single call for each and every record, I will have set of IDs, pass on this IDs to Context to get entities which satisfies the condition using Repository Pattern with EF.
I knew we can have something like this:
context.Students.Where( x => StudentIDs.contains(x.ID))
How to implement same in the repository layer or pattern with single call to DB?
If you really are a purist, yes you should abstract the DbContext entirely as you seem to imply.
I'm not sure I completely understand the issue, but something like that should do the job:
namespace EFRepo
{
class Student
{
public long Id { get; set; }
public string Name { get; set; }
}
class SchoolContext : DbContext
{
public DbSet<Student> Students { get; set; }
}
class SchoolRepository
{
private SchoolContext context = new SchoolContext();
public Student Add(string name)
{
Student student = new Student { Name = name };
context.Students.Add(student);
context.SaveChanges();
return student;
}
public IEnumerable<Student> GetStudentsByIds(IEnumerable<long> ids)
{
return context.Students.Where(x => ids.Contains(x.Id));
}
}
class Program
{
static void Main(string[] args)
{
SchoolRepository repo = new SchoolRepository();
repo.Add("Bully");
repo.Add("Crawler");
repo.Add("Tart");
foreach (Student s in repo.GetStudentsByIds(new[] { 1L, 3 }))
{
Console.WriteLine(s.Name);
}
}
}
}

Entity Framework Fluent API ICollection.count (one to many)

I am new to EF and i have the following scenario:
I want to load the IHave apples without loading the whole Apples collection.
The model:
public class Category
{
public virtual ICollection<Classified> Apples { get; set; }
[NotMapped]
public bool IHaveApples {get { return Apples.count > 0; } }
}
And the FLUENT API Config:
public class CategoryConfiguration : EntityTypeConfiguration<Category>
{
public CategoryConfiguration()
{
HasMany(o => o.Apples ).WithRequired().HasForeignKey(o => o.CategoryId);
}
}
And in my controller i go with
//The controller returns IQueryable<Category>
var category = _contextProvider.Context.Categories;
Thanks in advance,
Stelios K.
As IHaveApples is a bool, you should simply use Apples.Any() instead of Count.
EDIT:
If you want this to be automatically set (i.e. the boolean is set without even accessing manually to your collection), what you could do is add an handler to the ObjectMaterialized event:
public class Category
{
...
public bool IHaveApples { get; set; }
}
((IObjectContextAdapter)yourDbContext).ObjectContext.ObjectMaterialized += (sender, e) =>
{
var entityAsCategory = e.Entity as Category;
if (entityAsCategory != null)
{
entityAsCategory.IHaveApples = yourDbContext
.Entry(entityAsCategory)
.Collection(z => z.Apples)
.Query()
.Any();
}
};

The entity or complex type cannot be constructed in a LINQ to Entities query [duplicate]

This question already has answers here:
The entity cannot be constructed in a LINQ to Entities query
(14 answers)
Closed 10 years ago.
I have two functions that look exactly the same except they create lists of two different objects. The two different objects look very much alike, but when I try to run one of the functions on one of the objects, I get the error message, "The entity or complex type cannot be constructed in a LINQ to Entities query.". Can someone explain to me what is happening in very simple terms? Also, can you tell me how to change my code so that it works? Thanks, Allan.
Function 1 (works):
public static List<ChartApp> ListChartApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new ChartApp()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Function 2 (throws error on "return appNames.ToList();"):
public static List<LoginApplication> ListApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new LoginApplication()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Classes:
public class ChartApp
{
public ChartApp()
{
this.LoginHistories = new List<ChartHist>();
}
public int? LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<ChartHist> LoginHistories { get; set; }
public int Step { get; set; }
}
public class LoginApplication
{
public LoginApplication()
{
this.LoginHistories = new List<LoginHistory>();
}
public int LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<LoginHistory> LoginHistories { get; set; }
}
Edit: Could the difference possibly be that one of the objects are mapped to the database?
public class LoginApplicationMap : EntityTypeConfiguration<LoginApplication>
{
public LoginApplicationMap()
{
// Primary Key
this.HasKey(t => t.LoginApplicationID);
// Properties
this.Property(t => t.LoginAppName)
.HasMaxLength(500);
// Table & Column Mappings
this.ToTable("LoginApplication");
this.Property(t => t.LoginApplicationID).HasColumnName("LoginApplicationID");
this.Property(t => t.LoginAppName).HasColumnName("LoginAppName");
}
}
My solution in this case was to just delete the non-working function and use the working one in all places. For, similar functions that are mapped, I use the following function to return values.
public static List<LoginEnvironment> ListEnvironments(bool allSelection)
{
using (var db = new LatencyDBContext())
{
//GET ALL THE ENVIRONMENT NAMES
var envNames = from e in db.LoginEnvironments
orderby e.LoginEnvName
select e;
//PUT ALL THE ENVIRONMENTS INTO A LOCAL LIST
var listEnv = new List<LoginEnvironment>();
if (allSelection)
{
var defaultAll = new LoginEnvironment();
defaultAll.LoginEnvironmentID = 0;
defaultAll.LoginEnvName = "All";
listEnv.Add(defaultAll);
}
foreach (var item in envNames)
{
var localEnv = new LoginEnvironment();
localEnv.LoginEnvironmentID = item.LoginEnvironmentID;
localEnv.LoginEnvName = item.LoginEnvName;
listEnv.Add(localEnv);
}
return listEnv;
}
}