Transactions in Entity Framwork6 - ado.net

I have the code with following structure
public class Migration
{
public bool addNewData()
{
using(TransactionScope tScope=new TransactionScope())
{
Entities dataContext = new Entities(); //context object
foreach (XmlNode node in nodeList)
{
Issue issuelst = new Issue(); //Table Name
//Add data
dataContext.Issues.Add(issuelst);
dataContext.SaveChanges();
}
foreach (XmlNode node in nodeList)
{
IssueList lst = new IssueList(); //Table Name
//Add data
dataContext.IssueLists.Add(lst);
dataContext.SaveChanges();
}
ReadFileData readFileData = new ReadFileData(); //Class object
readFileData.ReadData();
tScope.Complete();
}
}
}
Class ReadFileData
{
public bool ReadData()
{
//Data
ReadData2();
}
public bool ReadData2()
{
//Data
}
}
So the issue I am facing is with transactions. In addNewData transaction is working fine.
But its not working in ReadData and ReadData2 function which is inside ReadFileData class
All the functions are dependent. I mean to day that if there is some issue or exception in ReadData2 function, data inside function ReadData and addNewData should not get saved.
How to do this. Need Help

Related

Viewmodel not updating model after add or insert to list

I'm fairly new to MVVM and Entity framework and I've hit a problem trying to add new records to my SQL database through MVVM. Below is the first and last part of my viewmodel which loads from my entity framework and this is working fine.
internal class MainWindowViewModel : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
private TEAMSEntities ctx = new TEAMSEntities();
public MainWindowViewModel()
{
FillSales();
}
public void AddASale()
{
Tbl_Sales newsale = new Tbl_Sales();
newsale.SALEID = "2018...";
newsale.SALE = "....";
newsale.SALEDESC = "....";
newsale.START = Convert.ToDateTime("12/04/2018");
newsale.SESSIONS = 2;
newsale.DAYS = 2;
newsale.LOTS = 100;
newsale.FIRSTLOT = 1;
newsale.LASTLOT = 100;
Sales.Add(newsale);
SaveChanges();
}
#region Sale
private void FillSales()
{
var q = (from a in ctx.Tbl_Sales
select a).ToList();
this.Sales = q;
}
private List<Tbl_Sales> _sales;
public List<Tbl_Sales> Sales
{
get
{
return _sales;
}
set
{
_sales = value;
NotifyPropertyChanged();
}
}
private Tbl_Sales _selectedSale;
public Tbl_Sales SelectedSale
{
get
{
return _selectedSale;
}
set
{
_selectedSale = value;
NotifyPropertyChanged();
}
}
#endregion Sale
.
.
.
.
public void SaveChanges()
{
ctx.SaveChanges();
}
private void NotifyPropertyChanged([CallerMemberName] String
propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Dispose()
{
((IDisposable)ctx).Dispose();
}
}
When I make changes to existing data in the view bound to the viewmodel and call the SaveChanges method in the viewmodel it saves the change back to the SQL database every time. If I call the AddASale method it adds that sale to the list but doesn't refresh the UI control bound to Sales and doesn't pass the newly created sale back to the SQL DB. Through debugging I can see the set being called in the Sales property when the LINQ code runs but it doesn't fire when I add a new sale through the AddASale code which is probably why the UI isn't updating...?
Can anyone offer any guidance as to what I'm doing wrong?
Thanks in advance.
Alex
First, add your new entity in your context and save it like this :
Tbl_Sales newsale = new Tbl_Sales
{
SALEID = "2018...",
SALE = "....",
SALEDESC = "....",
START = Convert.ToDateTime("12/04/2018"),
SESSIONS = 2,
DAYS = 2,
LOTS = 100,
FIRSTLOT = 1,
LASTLOT = 100
};
ctx.Tbl_Sales.Add(newsale);
SaveChanges();
after that, I think you have to refresh your list manually :
FillSales();
Here, a link to MSDN
Hope I helped you

Retrieving Navigation Properties changes context

I have a repository class with the following code to update entities that have changed.
However I get an error
"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."
However I cant see that the context has changed since it is a property of my repository.
Inside the repository I have
protected void InnerUpdate(params T[] items)
{
DbSet<T> dbSet = ((DbContext)this.context).Set<T>();
foreach (T item in items)
{
object id1 = item.GetProperty("Id");
T originalEntity = dbSet.Find(id1);
((DbContext)this.context).Entry(originalEntity).CurrentValues.SetValues(item);
var navProps = GetNavigationProperties(originalEntity);
foreach (var navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
navProp.SetValue(originalEntity, navProp.GetValue(item));
}
}
}
this.context.SaveChanges(); // Error occurs here
}
public List<PropertyInfo> GetNavigationProperties(T entity )
{
var t = entity.GetType();
ObjectContext objectContex = ((IObjectContextAdapter)((DbContext)this.context)).ObjectContext;
var elementType = objectContex.CreateObjectSet<T>().EntitySet.ElementType;
var properties = new List<PropertyInfo>();
var entityType = entity.GetType();
foreach (var navigationProperty in elementType.NavigationProperties)
{
properties.Add(entityType.GetProperty(navigationProperty.Name));
}
return properties;
}
I wonder if the problem is due to calling CreateObjectSet ? Is there a different way to do this?
I am using EF6.1
I changed the code as follows and got it working
var navProps = GetNavigationProperties(originalEntity);
foreach (var navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
var newval = (LoggedEntity) navProp.GetValue(item);
object entity = null;
if (newval != null)
{
var tp = navProp.PropertyType;
var entities = ((DbContext)this.context).Set(tp);
entity = entities.Find(newval.Id);
}
navProp.SetValue(originalEntity, entity);
}

EF: How to enclose context object in a using statement?

Let's say I have the following classes Customer.cs, a context OfficeContext.cs, and a repository OfficeRepository.cs. Knowing that the context use a connection object, so it's advised to enclose it in a using statement:
public List<Customer> GetAllCustomersWithOrders()
{
using(var oContext = new OfficeContext())
{
//Code here....
}
}
My question is what if I want to re-use some of the code already in the repository? For instance, what if I want to display all the customers that ordered products but didn't receive them yet, do I need to duplicate the code?
public List<Customer> GetCustomersNotReceiveProducts()
{
using(var oContext = new OfficeContext())
{
//Re-use GetAllCustomersWithOrders() here???...
}
}
But as you can see, each time access a method, I also open instantiate a new context object. Is there any way to deal with that?
What I do is have my repositories implement IDisposable.
Then have two constructors (one default) that instaniates a new context that holds it as a class level variable. And another constructor that takes a context and uses that internally.
The on the dispose of the class the context is disposed (if the current repository instatiated it).
This removes the context out of the method level and moves it to the class level. My functions keep everything in IQueryable so one function can call another function and perform additional refinements before the database it hit.
Exmaple:
public class MemberRepository : IDisposable
{
OfficeContext db;
bool isExternalDb = false;
public MemberRepository()
{
db = new OfficeContext();
isExternalDb = false;
}
public MemberRepository(OfficeContext db)
{
this.db = db;
isExternalDb = true;
}
public IQueryable<Member> GetAllMembers()
{
var members= db.Members
return members;
}
public IQueryable<Member> GetActiveMembers()
{
var members = GetAllMembers();
var activeMembers = members.Where(m => m.isActive == true);
return activeMembers;
}
public void Dispose()
{
if (isExternalDb == false)
{
db.Dispose();
}
}
}
Then where I use the repository, I do a using at that level:
using(var memberRepository = new MemberRepository())
{
var members = memberRepository.GetActiveMembers();
}

Entity Framework as DAL how to implement Update and Delete correctly

I'm writing a DAL class using EF4.0, I've read
http://www.codeproject.com/Articles/43367/ADO-NET-Entity-Framework-as-Data-Access-Layer
and
http://msdn.microsoft.com/en-us/magazine/cc700340.aspx
But when I test their code, I meet some problem with the Update and Delete method.
The DAL class all code is below:
public class FriendlinkDA : IDisposable
{
private EdiBlogEntities context;
public FriendlinkDA()
{
context = new EdiBlogEntities();
}
public void Dispose()
{
context.Dispose();
}
public FriendLink GetFriendLink(Guid id)
{
return context.FriendLink.FirstOrDefault(f => f.Id == id);
}
public void Update(FriendLink model)
{
// Way 1: (throw exception)
//context.Attach(model);
//model.SetAllModified(context);
//context.SaveChanges();
// Way 2:
EntityKey key;
object originalItem;
key = context.CreateEntityKey("FriendLink", model);
if (context.TryGetObjectByKey(key, out originalItem))
{
context.ApplyCurrentValues(key.EntitySetName, model);
//context.ApplyPropertyChanges(key.EntitySetName, model);
}
context.SaveChanges();
}
public void Delete(FriendLink model)
{
// Way 1:
context.Attach(model);
context.DeleteObject(model);
context.SaveChanges();
// Way 2:
//var item = context.FriendLink.FirstOrDefault(f => f.Id == model.Id);
//context.DeleteObject(item);
//context.SaveChanges();
}
}
The extension method is:
public static void SetAllModified<T>(this T entity, ObjectContext context) where T : IEntityWithKey
{
var stateEntry = context.ObjectStateManager.GetObjectStateEntry(entity.EntityKey);
var propertyNameList = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select
(pn => pn.FieldType.Name);
foreach (var propName in propertyNameList)
stateEntry.SetModifiedProperty(propName);
}
In the application, I am use the DAL like this:
// Delete
using (var optFriendlink = new FriendlinkDA())
{
var test = optFriendlink.GetFriendLink(new Guid("81F58198-D396-41DE-A240-FC306C7343E8"));
optFriendlink.Delete(test);
}
// Update
using (var optFriendlink = new FriendlinkDA())
{
var testLink = optFriendlink.GetFriendLink(new Guid("62FD0ACF-40C3-4BAD-B438-38BB540A6080"));
testLink.Title = "ABC";
optFriendlink.Update(testLink);
}
Question 1:
In Delete(), both way 1 and way 2 can work. Which one is better?
Question 2:
In Update(), way 1 give me an exception: The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.
on this statment: context.Attach(model);
but way 2 is fine.
why is this happening? I also attach the model in Delete(), why Delete() is working fine? how I can write the update correctly?
The exception says it all:
An object can only be reattached when it is in an unchanged state.
You change the object in the code snippet under // Update, so that's why it cannot be re-attached.
As to which method is better. Normally you would get an object from a context, dispose of the context, do something with the object and then use a new context to save the object. In that case using Attach is much more comfortable then getting an object by Id first.

Efficient way of checking if many-to-many relationship exists in EF4.1

I have a many-to-many relationship between two entities - Media and MediaCollection. I want to check if a certain Media already exists in a collection. I can do this as follows:
mediaCollection.Media.Any(m => m.id == mediaId)
However, mediaCollection.Media is an ICollection, so to me this looks like it will have to retrieve every Media in the collection from the database just to make this check. As there could be many media in a collection, this seems very inefficient. I'n thinking that I should use a method of IQueryable, but I can't see how to do this for many-to-many relationships.
How can I check for the existence of the relationship without retrieving the whole collection?
EDIT
I am generating the EF data model from my database, then using the built in VS POCO T4 templates to generate my data context and entity classes. I think the problem is that the generated code does not return EntityCollection for the navigation properties, but instead ObjectSet. ObjectSet implements IQueryable, but does not expose a CreateSourceQuery() method.
Here is a stripped down version of the relevant lines from the context:
public partial class Entities : ObjectContext
{
public const string ConnectionString = "name=Entities";
public const string ContainerName = "Entities";
#region Constructors
public Entities()
: base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(string connectionString)
: base(connectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(EntityConnection connection)
: base(connection, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
#endregion
#region ObjectSet Properties
public ObjectSet<MediaCollection> MediaCollections
{
get { return _mediaCollections ?? (_mediaCollections = CreateObjectSet<MediaCollection>("MediaCollections")); }
}
private ObjectSet<MediaCollection> _mediaCollections;
// snipped many more
#endregion
}
And here is a stripped down version of the class for the MediaCollection entity:
public partial class MediaCollection
{
#region Primitive Properties
// snipped
#endregion
#region Navigation Properties
public virtual ICollection<Medium> Media
{
get
{
if (_media == null)
{
var newCollection = new FixupCollection<Medium>();
newCollection.CollectionChanged += FixupMedia;
_media = newCollection;
}
return _media;
}
set
{
if (!ReferenceEquals(_media, value))
{
var previousValue = _media as FixupCollection<Medium>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupMedia;
}
_media = value;
var newValue = value as FixupCollection<Medium>;
if (newValue != null)
{
newValue.CollectionChanged += FixupMedia;
}
}
}
}
private ICollection<Medium> _media;
private void FixupMedia(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Medium item in e.NewItems)
{
if (!item.MediaCollections.Contains(this))
{
item.MediaCollections.Add(this);
}
}
}
if (e.OldItems != null)
{
foreach (Medium item in e.OldItems)
{
if (item.MediaCollections.Contains(this))
{
item.MediaCollections.Remove(this);
}
}
}
}
// snip
#endregion
}
And finally, here is the FixupCollection that the template also generates:
public class FixupCollection<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
new List<T>(this).ForEach(t => Remove(t));
}
protected override void InsertItem(int index, T item)
{
if (!this.Contains(item))
{
base.InsertItem(index, item);
}
}
}
You can do that but you need a context for that:
bool exists = context.Entry(mediaCollection)
.Collection(m => m.Media)
.Query()
.Any(x => x.Id == mediaId);
Edit:
If you are using ObjectContext API with proxied POCOs instead of DbContext API the former sample will not work. You can try this:
context.ContextOptions.LazyLoadingEnabled = false;
bool exists = ((EntityCollection<Media>)mediaCollection.Media).CreateSourceQuery()
.Any(x => x.Id == mediaId);
context.ContextOptions.LazyLoadingEnabled = true;
So it seems that the built in VS POCO T4 template does not generate anything equivalent to CreateSourceQuery(). No matter! We can code it ourselves. If you add the following code at to the context's .tt file and regenerate:
public ObjectQuery<T> CreateNavigationSourceQuery<T>(object entity, string navigationProperty)
{
var ose = ObjectStateManager.GetObjectStateEntry(entity);
var rm = ObjectStateManager.GetRelationshipManager(entity);
var entityType = (System.Data.Metadata.Edm.EntityType)ose.EntitySet.ElementType;
var navigation = entityType.NavigationProperties[navigationProperty];
var relatedEnd = rm.GetRelatedEnd(navigation.RelationshipType.FullName, navigation.ToEndMember.Name);
return ((dynamic)relatedEnd).CreateSourceQuery();
}
then we can check for the existence of a many-to-many as follows:
var exists = _context.CreateNavigationSourceQuery<Medium>(mediaCollection, "Media")
.Any(m => m.Id == medium.Id);
Props to Rowan's answer on Using CreateSourceQuery in CTP4 Code First for this one.
Try,
mediaCollection.CreateSourceQuery()
.Any(....
CreateSourceQuery will create IQueryable for the association.