I have my models setup like this...
public class Model1 : IEquatable<Model1>
{
public int Model1Id { get; set; }
public string Name1 { get; set; }
public Model2 Model2 { get; set; }
public int Model2Id { get; set; }
public bool Equals(Model1 other)
{
return this.Model2.Equals(other.Model2)
&& this.Name1 == other.Name1;
}
}
public class Model2 : IEquatable<Model2>
{
public int Model2Id { get; set; }
public string Name2 { get; set; }
public bool Equals(Model2 other)
{
return this.Name2 == other.Name2;
}
}
public class ModelContext : DbContext
{
public DbSet<Model1> Model1 { get; set; }
public DbSet<Model2> Model2 { get; set; }
public ModelContext(DbContextOptions<ModelContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Model1>(b =>
{
b.HasOne(m1 => m1.Model2).WithMany().HasForeignKey(m1 => m1.Model2Id);
});
}
}
then I get a null reference exception when I do this...
static void Main(string[] args)
{
var myModel1 = new Model1
{
Name1 = "myName1",
Model2 = new Model2
{
Name2 = "myName2"
}
};
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<ModelContext>()
.UseSqlite(connection)
.Options;
//create database
using(var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
}
//add model objects
using (var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
ctx.Model1.Add(myModel1);
ctx.SaveChanges();
}
//check if exists
using(var ctx = new ModelContext(options))
{
//exception here
bool isExists = ctx.Model1.Include(m1 => m1.Model2).Contains(myModel1);
Console.WriteLine(isExists);
}
}
finally
{
connection.Close();
}
Console.ReadKey();
}
I'm expeting the Model2 instance of my m1 to be populated when I call the Include but it is still null.
but If I add AsEnumerable() to my query like..
ctx.Model1.Include(m1 => m1.Model2).AsEnumerable().Contains(model1);
then everything works fine.
EDIT:
my question is... why do I need to call AsEnumerable()? I was expecting it to work without calling AsEnumerable()..
The difference is one is an entityframe work call the other is linq to objects
Entity Framework Does not understand contains for a CLR Object
public void AddIfNotExists(Model1 model1)
{
//No Need for the include this is executed in sql, assuming the model 2
//property has already been included in your model1 this should work fine
if(false == _context.Model1.Any(x => x.Name1 == model1.Name1
&& x.Model2.Name2 == model1.Model2.Name2))
{
_context.Model1.Add(model1);
}
}
I made this based off of your logic, but chances are you really just want to check if model1.id is the the model1 set. But I have no Idea what your architecture is doing so this is what you probably want
Related
I need to merge data from tables in the database with data based on some logic from third-party sources. I implemented this logic via hashset, for which I overloaded the GetHashCode and Equals methods for entities. Now I don't understand how I can save the result of work in the database via DbSet, with subsequent data loading and subsequent merging (the task of merging/supplementing is periodic)
The directories are quite voluminous, so working through hashset speeds up the process.
class Program
{
private class DummyDbContext { public void SaveChangesAsync() { }}
static void Main(string[] args)
{
var dbContext = new DummyDbContext(); // TODO: Get from DI
// TODO: I don't know how to do it yet with HashSets
var currentFactories = LoadCurrentFactoriesFromDb(dbContext);
var currentProducts = LoadCurrentProductsFromDb(dbContext);
var thirdPartyData = GetThirdPartyData();
foreach (var data in thirdPartyData)
{
/*
In reality, the logic is more complicated, because some data transformation is required.
Some data may be missing. That is why comparing two objects is not quite easy (see the method Product.Equals)
*/
var factory = new Factory(data.otherFactory.Name);
var product = new Product(data.otherProduct.Property1, data.otherProduct.Property2, factory);
if (currentFactories.TryGetValue(factory, out var existedFactory))
factory = existedFactory;
else
currentFactories.Add(factory);
if (currentProducts.TryGetValue(product, out var existedProduct))
{
if (!existedProduct.Factory.Equals(factory))
throw new InvalidOperationException(); // TODO:
product = existedProduct;
factory.Products.Add(product); // TODO:
}
else
currentProducts.Add(product);
}
// **how to implement the saving of combined directories, in hashsets, in the database ?**
dbContext.SaveChangesAsync();
}
private static IEnumerable<(ThirdPartyFactory otherFactory, ThirdPartyProduct otherProduct)> GetThirdPartyData()
{
return new (ThirdPartyFactory otherFactory, ThirdPartyProduct otherProduct)[]
{
( new ThirdPartyFactory () {Name = "SomeFactory"}, new ThirdPartyProduct() {Property1 = "ProductName1"}),
( new ThirdPartyFactory () {Name = "SomeFactory"}, new ThirdPartyProduct() {Property1 = "ProductName2"}),
( new ThirdPartyFactory () {Name = "SomeFactory"}, new ThirdPartyProduct() {Property2 = "Property1"})
};
}
private static HashSet<Factory> LoadCurrentFactoriesFromDb(DummyDbContext context)
{
// DbContext.DbSet<Factory>.GetAll()
return new HashSet<Factory>();
}
private static HashSet<Product> LoadCurrentProductsFromDb(DummyDbContext context)
{
// DbContext.DbSet<Product>.GetAll()
return new HashSet<Product>();
}
}
public class Product
{
public Product(string property1, string property2, Factory factory)
{
Property1 = property1;
Property2 = property2;
Factory = factory;
}
public long Id { get; set; }
public string Property1 { get; }
public string Property2 { get; }
public Factory Factory { get; }
public override bool Equals(object? obj)
{
if (obj == null)
return false;
var product = (Product) obj;
return (string.IsNullOrWhiteSpace(Property1) && string.IsNullOrWhiteSpace(product.Property1)
|| string.CompareOrdinal(this.Property1, product.Property1) == 0)
&& (string.IsNullOrWhiteSpace(Property2) && string.IsNullOrWhiteSpace(product.Property2)
|| string.CompareOrdinal(this.Property2, product.Property2) == 0);
}
public override int GetHashCode()
{
return HashCode.Combine(Property1, Property2).GetHashCode();
}
}
public class Factory
{
public Factory(string name)
{
Name = name;
}
public long Id { get; set; }
public string Name { get; }
public HashSet<Product> Products { get; set; }
}
public class ThirdPartyProduct
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
public class ThirdPartyFactory
{
public string Name { get; set; }
}
Is it possible to implement this ? Or do I need to convert data from DbSet to HashSet and then back ? But won't I lose information about entities inside the context during such transformations ?
I will try to explain my problem as thoroughly as possible with a simplified example. Please note that I am NOT using Entity Framework.
I have this model:
public class Person
{
[Key]
public Guid Id { get; set; }
public string GivenName { get; set; }
public string FamilyName { get; set; }
public List<Employment> Employments { get; set; }
}
public class Employment
{
public string Role { get; set; }
public Guid? ManagerId { get; set; }
public virtual Person Manager { get; set; }
}
I create an in-memory data source:
public class MyDataSource
{
private static MyDataSource instance = null;
public static MyDataSource Instance
{
get
{
if (instance == null)
{
instance = new MyDataSource();
}
return instance;
}
}
public List<Person> Persons { get; set; }
private MyDataSource()
{
this.Persons = new List<Person>();
this.Persons.AddRange(new List<Person>
{
new Person()
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000001"), //Just for simplicity
GivenName = "John",
FamilyName = "Doe",
Employments = new List<Employment>()
{
new Employment()
{
Role = "Boss"
}
}
},
new Person()
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000002"), //Just for simplicity
GivenName = "Clark",
FamilyName = "Kent",
Employments = new List<Employment>()
{
new Employment()
{
Role = "Worker",
ManagerId = Guid.Parse("00000000-0000-0000-0000-000000000001"), //Just for simplicity
}
}
}
});
}
}
I have this controller:
[EnableQuery]
public class PersonsController : ODataController
{
public IHttpActionResult Get()
{
return Ok(MyDataSource.Instance.Persons)
}
}
I configure the EndPoint:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata", CreateEdmModel());
config.Select().Expand().Filter().OrderBy().MaxTop(null).Count()
}
public static IEdmModel CreateEdmModel()
{
var builder = new ODataConventionModelBuilder();
var persons = builder.EntitySet<Person>("Persons");
builder.ComplexType<Employment>().HasOptional(e => e.Manager, (e, p) => e.ManagerId == p.Id);
return builder.GetEdmModel();
}
}
Checking the $metadata I see this:
<NavigationProperty Name="Manager" Type = "MyNamespace.Person">
<ReferentialConstraint Property="ManagerId" ReferenceProperty="Id" />
</NavigationProperty
Everything looks fine from what I can tell but:
https://example.com/odata/persons?$expand=Employments/Manager
receives everything fine but:
Manager is null for both persons. I was expecting to see John Doe on Clark Kents employment.
What am I missing?
I have solved it myself.
I realised that it doesn't work like I thought and that I have to add a reference to the manager directly in MyDataSource. After that it works to $expand the manager.
I have this code:
Class VM
{
var MyVm;
public VM(ExternalEntities externalEntities){
MyVm = externalEntities.Reflcation.VM;
}
public bool IsVmPowerOn(){
//Do something
}
}
[TestMethod]
public void TestVM()
{
private Mock<IExternalEntities> m_externalEntities = new Mock<IExternalEntities>();
private Mock<IReflection> m_reflection = new Mock<IReflection>();
private Mock<IVm> m_vm= new Mock<IVm>();
m_externalEntities.Setup(x => x.Reflaction).Return(m_reflection.object);
m_reflection.Setup(x => x.VM).Return(m_vm.Object);
var testee = new VM(externalEntity.Object)
var ans = testee.IsVmPowerOn();
Assert.IsTrue(ans);
}
The problem is that externalEntities.Reflcation is null and the test throws a NullReferenceException so it can't activate the Vm property.
The test can't pass constructor.
The following code also throws a NullReferenceException:
m_externalEntities.Setup(x => x.Reflaction.VM).Return(m_vm.object);
How do you test this kind of code?
Why do I receive null after the setup and not the mock object?
You had a lot of compilation errors and missing pieces in your code. It was not compiling as-is. That being said, I fixed it up for you. Not sure what your trying to accomplish but this works.
public interface IVm
{
IVm MyVm { get; set; }
}
public class VM : IVm
{
public IVm MyVm { get; set; }
public VM(IExternalEntities externalEntities)
{
MyVm = externalEntities.Reflaction.VM;
}
public bool IsVmPowerOn()
{
//Do something
return true;
}
}
public interface IExternalEntities
{
IReflection Reflaction { get; set; }
}
public class ExternalEntities : IExternalEntities
{
public IReflection Reflaction { get; set; }
public ExternalEntities()
{
Reflaction = new Reflection();
}
}
public interface IReflection
{
IVm VM { get; set; }
}
public class Reflection : IReflection
{
public IVm VM { get; set; }
public Reflection()
{
VM = new VM(null);
}
}
Then using that, your test would look like this.
[TestMethod]
public void TestVM()
{
Mock<IExternalEntities> m_externalEntities = new Mock<IExternalEntities>();
Mock<IReflection> m_reflection = new Mock<IReflection>();
Mock<IVm> m_vm = new Mock<IVm>();
m_externalEntities.Setup(x => x.Reflaction).Returns(m_reflection.Object);
m_reflection.Setup(x => x.VM).Returns(m_vm.Object);
var testee = new VM(m_externalEntities.Object);
var ans = testee.IsVmPowerOn();
Assert.IsTrue(ans);
}
I'm currently working on a small application with WPF, EF6 and SqlServer 2012. I have two entities "Region" and "BctGouvernorats" associated with an optional one to many relationship.
My problem is : When I remove a child (BctGouvernorat) from the relationship , it still appears in the collection related to the parent (Region). here's the code:
//Entities
public partial class BctGouvernorat
{
public long GovId { get; set; }
public string Libelle { get; set; }
public long UserId { get; set; }
public Nullable<long> RegionId { get; set; }
public virtual Region Region { get; set; }
}
public partial class Region
{
public long RegionId { get; set; }
public string Libelle { get; set; }
public long GroupeNumber { get; set; }
public byte Bonus { get; set; }
public long UserId { get; set; }
public virtual RegionsGroupes GroupeRegions { get; set; }
public virtual ICollection<BctGouvernorat> Gouvernorats { get; set; }
public Region()
{
Libelle = "New region";
GroupeNumber = 0;
this. Gouvernorats = new HashSet<BctGouvernorat>() ;
}
//Mapping of BctGouvernorat entity
public BctGouvernoratMapping()
{
this.ToTable("BctGouvernorat");
this.HasKey(t => t.GovId);
this.Property(t => t.GovId);
this.HasOptional(t => t.Region)
.WithMany(t => t.Gouvernorats)
.HasForeignKey(d => d.RegionId)
.WillCascadeOnDelete(false);
}
//Mapping of Region entity
public RegionMapping()
{
this.ToTable("Region");
this.HasKey(t => t.RegionId);
this.Property(t => t.RegionId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
//C# code for "Modify" Method
public void Modify(Region r, List<BctGouvernorat> _ToUnlink, List<BctGouvernorat> _ToLink)
{
//Below the code for unlink child from parent
if (_ToUnlink.Count > 0)
{
r.Gouvernorats.ToList().All(xx =>
{
if (_ToUnlink.Contains(xx))
{
xx.RegionId = null;
xx.Region = null;
}
return true;
}
);
}
//Here the code for link child to the parent
_ToLink.All(xx =>
{
xx.RegionId = r.RegionId;
xx.Region = r;
r.Gouvernorats.Add(xx);
return true;
});
//Mark Childs collection as modified
r.Gouvernorats.All(xx =>
{
_uow.GetEntry<BctGouvernorat>(xx).State = EntityState.Modified;
return true;
});
base.Modify(r);
}
actually the previous method is included in a «RegionRepository» which inherits from a base class Repository. The code of base.Modify() is as follows :
//Method Modify from RegionRepository
public void Modify(T item)
{
_uow.RegisterChanged(item);
_uow.Commit();
}
And Modify Method uses services of a unit of work "_uow" that save data to sqlserver database. Here the code :
//***************************
//_uow is a unit of work
//*****************************
public void RegisterChanged<T>(T item) where T : class
{
base.Entry<T>(item).State = System.Data.Entity.EntityState.Modified;
}
public void Commit()
{
try
{
base.SaveChanges();
}
catch (DbUpdateException e)
{
var innerEx = e.InnerException;
while (innerEx.InnerException != null)
innerEx = innerEx.InnerException;
throw new Exception(innerEx.Message);
}
catch (DbEntityValidationException e)
{
var sb = new StringBuilder();
foreach (var entry in e.EntityValidationErrors)
{
foreach (var error in entry.ValidationErrors)
{
sb.AppendLine(string.Format("{0}-{1}-{2}",
entry.Entry.Entity,
error.PropertyName,
error.ErrorMessage
));
}
}
throw new Exception(sb.ToString());
}
}
Sorry, I should have put the ViewModel code that calls the previous code :
private void SaveRegion()
{
List<BctGouvernorat> _GovToLink = null;
//The following method checks and returns (Added, Deleted, Modified BctGouvernorat)
List<BctGouvernorat> _GovToUnlink = CheckGouvernoratsListStatus(out _GovToLink);
ILogger _currentLog = (Application.Current as App).GetCurrentLogger();
using (UnitOfWork cx = new UnitOfWork(_currentLog))
{
RegionRepository _regionRepository = new RegionRepository(cx, _currentLog);
IRegionManagementService rms = new RegionManagementService(_currentLog, _regionRepository);
if (CurrentRegion.RegionId == 0)
{
CurrentRegion.UserId = Session.GetConnectedUser().UserId;
rms.AddRegion(CurrentRegion);
}
else
rms.ModifyRegion(CurrentRegion, _GovToUnlink,_GovToLink);
}
}
private List<BctGouvernorat> CheckGouvernoratsListStatus(out List<BctGouvernorat> _ToLink)
{
List<BctGouvernorat> AddedGouvernorats = GouvernoratsRegion.Except<BctGouvernorat>(CurrentRegion.Gouvernorats,
new GouvernoratComparer()).ToList();
_ToLink = AddedGouvernorats;
List<BctGouvernorat> DeletedGouvernorats = CurrentRegion.Gouvernorats.Except<BctGouvernorat>(GouvernoratsRegion,
new GouvernoratComparer()).ToList();
return DeletedGouvernorats;
}
The "GouvernoratsRegion" is an observablecollection bound to a datagrid that i edit to add or remove BCTgouvernorat Rows to the region
public void ModifyRegion(Region r, List<BctGouvernorat> _ToUnlik, List<BctGouvernorat> _ToLink)
{
_regionRepository.Modify(r, _ToUnlik, _ToLink);
}
With a model such as ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.ComponentModel.DataAnnotations;
namespace Singleton
{
public class Program
{
public static void Main(string[] args)
{
var builder = new ModelBuilder();
builder.Configurations.Add(new TemplateConfiguration());
builder.Configurations.Add(new UserConfiguration());
builder.Configurations.Add(new UnitConfiguration());
builder.Configurations.Add(new AttributeConfiguration());
var model = builder.CreateModel();
using (var context = new SampleDataContext(model))
{
bool updating = true;
if (updating)
{
var units = new List<Unit>
{
new Unit{ Name = "Unit1" },
new Unit{ Name = "Unit2" }
};
units.ForEach(x => { context.Units.Add(x); });
context.SaveChanges();
var templates = new List<Template>
{
new Template{
Name = "Default",
Attributes = new List<Attribute>
{
new Attribute
{
Unit = context.Units.Single( i => i.Name == "Unit1" )
}
}
}
};
templates.ForEach(x =>
{
context.Templates.Add(x);
});
context.SaveChanges();
var users = new List<User>
{
new User
{
Name = "Stacey"
},
new User
{
Name = "Daniel"
},
new User
{
Name = "Derek"
}
};
users.ForEach(x => { context.Users.Add(x); });
context.SaveChanges();
updating = !updating; // stop updating
}
if (!updating)
{
Single.Instance = context.Templates.Single(i => i.Name == "Default");
}
foreach (User user in context.Users)
{
Console.WriteLine(user.Template.Name); // does template requery?
}
}
}
}
public class Template
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Attribute> Attributes { get; set; }
}
public class TemplateConfiguration : EntityConfiguration<Template>
{
public TemplateConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
//// map the collection entity
HasMany(k => k.Attributes).WithRequired()
.Map("template.attributes",
(template, attribute) => new
{
Template = template.Id,
Attribute = attribute.Id
});
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("templates");
}
}
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
[StoreGenerated(StoreGeneratedPattern.None)]
public Template Template { get { return Single.Instance; } }
}
public class UserConfiguration : EntityConfiguration<User>
{
public UserConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("users");
}
}
public class Unit
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class UnitConfiguration : EntityConfiguration<Unit>
{
public UnitConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("units");
}
}
public class Attribute
{
public virtual int Id { get; set; }
public Unit Unit { get; set; }
}
public class AttributeConfiguration : EntityConfiguration<Attribute>
{
public AttributeConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
// Initialize the Statistic
HasRequired(k => k.Unit);
// map the data type to the context so that it can be queried.
MapHierarchy(c => new
{
c.Id,
Unit = c.Unit.Id
}).ToTable("attributes");
}
}
public class Single
{
public static Template Instance;
}
public class SampleDataContext : DbContext
{
public SampleDataContext(DbModel model)
: base(model)
{
this.ObjectContext.ContextOptions.LazyLoadingEnabled = true;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Template> Templates { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Unit> Units { get; set; }
public DbSet<Attribute> Attributes { get; set; }
}
}
if I assign Singleton.Instance to a query from the DataContext, and then assign the Singleton.Instance to other objects in my code, will the SQL be run once, or each time it is accessed? Can anyone help me here to see if this pattern is going to save some SQL?
From context.SomeQuery, you're almost certainly just returning some kind of queryable object or other iterable (I'm not sure your example reflects how your EF solution is actually architected, I think some elements were lost for the sake of brevity). So, every time you access this singleton, you're just going to iterate over it (run the query) again.
I recommend you use a simple memoization revolving around 4.0 caching, try this.
The SQL will run each time you access it.
What you'r looking for is some kind of caching strategy.
Have a look at the following thread. I think it will help you: How to make Entity Framework cache some objects